Merge branch 'bug2026' into spatial-wind

Conflicts:
	java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java
	java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java
	java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
	java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java
	java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java
	java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
	java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java
	java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
	java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java
This commit is contained in:
Axel Uhl committed 2014-06-28 14:46:52 +02:00
commit 212ca8f153
110 files changed
+1549 -417

No files matched your search

+1
View File
@@ -15,6 +15,7 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[EC2 Backup Strategy|wiki/amazon-ec2-backup-strategy]]
* [[TracTrac|wiki/tractrac-lifecycle]]
* [[Analytics on a stick|wiki/analytics-on-a-stick]]
* [[Monitoring Apache and RabbitMQ|wiki/monitoring-apache-and-rabbitmq]]
* Information for Developers
* [[Cook Book|wiki/cook-book]]
* [[OnBoarding Information|wiki/onboarding]]
@@ -34,4 +34,9 @@ public class FlagPole {
this.isDisplayed == otherPole.isDisplayed;
}
public boolean describesSameButForDisplayed(FlagPole otherPole) {
return this.upperFlag == otherPole.upperFlag &&
this.lowerFlag == otherPole.lowerFlag;
}
}
@@ -21,7 +21,7 @@ public class AbstractTestWithIgtimiConnection {
public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("9fded995cf21c8ed91ddaec13b220e8d5e44c65808d22ec2b1b7c32261121f26");
connection = connectionFactory.connect(account, /* correctByDeclination */ true);
connection = connectionFactory.connect(account);
}
}
@@ -159,7 +159,7 @@ public class WebSocketTest {
// the following is an access token for an account allowing axel.uhl@gmx.de to access
// the data from baur@stg-academy.org, particularly containing the Berlin test data
Account account = igtimiConnectionFactory.registerAccountForWhichClientIsAuthorized("9fded995cf21c8ed91ddaec13b220e8d5e44c65808d22ec2b1b7c32261121f26");
IgtimiConnection conn = igtimiConnectionFactory.connect(account, /* correctByDeclination */ true);
IgtimiConnection conn = igtimiConnectionFactory.connect(account);
LiveDataConnection liveDataConnection = conn.getOrCreateLiveConnection(Collections.singleton("GA-EN-AAEJ"));
LiveDataConnection redundantSecondSharedConnection = conn.getOrCreateLiveConnection(Collections.singleton("GA-EN-AAEJ"));
assertTrue(liveDataConnection instanceof LiveDataConnectionWrapper);
@@ -8,7 +8,6 @@ import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import org.xml.sax.SAXException;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback;
/**
@@ -21,7 +20,7 @@ import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback;
public interface IgtimiConnectionFactory {
Iterable<Account> getAllAccounts();
IgtimiConnection connect(Account account, boolean correctByDeclination);
IgtimiConnection connect(Account account);
/**
* Removes the account and the credentials associated with it permanently. The account is identified by the e-mail
@@ -39,7 +38,7 @@ public interface IgtimiConnectionFactory {
/**
* Using the "account" service, retrieves the {@link Account} information for this access token and stores the token
* that authorizes this factory's client to access the account together with the {@link Account} data. This information is
* used when a caller wants to {@link #connect(Account, DeclinationService)} to a specific account on behalf of the {@link Client} to
* used when a caller wants to {@link #connect(Account)} to a specific account on behalf of the {@link Client} to
* which this factory belongs.
* @return
*/
@@ -55,13 +55,15 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
private static final Logger logger = Logger.getLogger(IgtimiConnectionFactoryImpl.class.getName());
private final Map<Account, String> accessTokensByAccount;
private Map<String, Account> accountsByEmail;
private final Map<String, Account> accountsByEmail;
private final Map<Account, IgtimiConnection> connectionsByAccount;
private final Client client;
private final MongoObjectFactory mongoObjectFactory;
public IgtimiConnectionFactoryImpl(Client client, DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) {
this.accessTokensByAccount = new HashMap<>();
this.accountsByEmail = new HashMap<>();
connectionsByAccount = new HashMap<>();
this.client = client;
this.mongoObjectFactory = mongoObjectFactory;
for (String accessToken : domainObjectFactory.getAccessTokens()) {
@@ -130,8 +132,16 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
}
@Override
public IgtimiConnection connect(Account account, boolean correctByDeclination) {
return new IgtimiConnectionImpl(this, account);
public IgtimiConnection connect(Account account) {
IgtimiConnection connection;
synchronized (connectionsByAccount) {
connection = connectionsByAccount.get(account);
if (connection == null) {
connection = new IgtimiConnectionImpl(this, account);
connectionsByAccount.put(account, connection);
}
}
return connection;
}
private String getAccessTokenForAccount(Account account) {
@@ -44,7 +44,7 @@ public class IgtimiWindTracker extends AbstractWindTracker implements WindTracke
for (Account account : accounts) {
try {
if (!stopping) {
IgtimiConnection connection = connectionFactory.connect(account, correctByDeclination);
IgtimiConnection connection = connectionFactory.connect(account);
Iterable<String> devicesWeShouldListenTo = connection.getWindDevices();
if (!stopping) {
LiveDataConnection liveConnection = connection.getOrCreateLiveConnection(devicesWeShouldListenTo);
@@ -378,8 +378,14 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
if (declinationService == null) {
trueHeading = hdgm;
} else {
Declination declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds 5s */ 5000);
trueHeading = hdgm.add(declination.getBearingCorrectedTo(timePoint));
try {
Declination declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds 5s */ 5000);
trueHeading = hdgm.add(declination.getBearingCorrectedTo(timePoint));
} catch (Exception e) {
logger.log(Level.SEVERE, "Correction of declination was requested but unsuccessful. Can't correct heading "+
hdgm+"@"+timePoint+" by declination. Forwarding exception.");
throw e;
}
}
} else {
trueHeading = null;
@@ -154,7 +154,11 @@ public class WebSocketConnectionManager extends WebSocketAdapter implements Live
private void notifyListeners(List<Fix> fixes) {
for (BulkFixReceiver listener : listeners.keySet()) {
listener.received(fixes);
try {
listener.received(fixes);
} catch (Exception e) {
logger.log(Level.SEVERE, "Error notifying listener "+listener+" of Igtimi fixes "+fixes, e);
}
}
}
@@ -1151,10 +1151,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name());
Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name());
final BasicDBList dbFleets = (BasicDBList) dbSeries.get(FieldNames.SERIES_FLEETS.name());
Map<String, Fleet> fleetsByName = loadFleets(dbFleets);
List<Fleet> fleets = loadFleets(dbFleets);
BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name());
Iterable<String> raceColumnNames = loadRaceColumnNames(dbRaceColumns, fleetsByName);
Series series = new SeriesImpl(name, isMedal, fleetsByName.values(), raceColumnNames, trackedRegattaRegistry);
Iterable<String> raceColumnNames = loadRaceColumnNames(dbRaceColumns);
Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry);
if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) {
ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS);
series.setResultDiscardingRule(resultDiscardingRule);
@@ -1172,11 +1172,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return series;
}
/**
* @param fleetsByName used to ensure the {@link RaceColumn#getFleets()} points to the same {@link Fleet} objects also
* used in the {@link Series#getFleets()} collection.
*/
private Iterable<String> loadRaceColumnNames(BasicDBList dbRaceColumns, Map<String, Fleet> fleetsByName) {
private Iterable<String> loadRaceColumnNames(BasicDBList dbRaceColumns) {
List<String> result = new ArrayList<String>();
for (Object o : dbRaceColumns) {
DBObject dbRaceColumn = (DBObject) o;
@@ -1201,12 +1197,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
}
private Map<String, Fleet> loadFleets(BasicDBList dbFleets) {
Map<String, Fleet> result = new HashMap<String, Fleet>();
private List<Fleet> loadFleets(BasicDBList dbFleets) {
List<Fleet> result = new ArrayList<Fleet>();
for (Object o : dbFleets) {
DBObject dbFleet = (DBObject) o;
Fleet fleet = loadFleet(dbFleet);
result.put(fleet.getName(), fleet);
result.add(fleet);
}
return result;
}
@@ -1,17 +1,25 @@
package com.sap.sailing.domain.racelog.analyzing.impl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
public class IndividualRecallDisplayedFinder extends IndividualRecallFinder {
private final TimePoint at;
public IndividualRecallDisplayedFinder(RaceLog raceLog) {
this(raceLog, null);
}
public IndividualRecallDisplayedFinder(RaceLog raceLog, TimePoint at) {
super(raceLog);
this.at = at;
}
protected boolean isRelevant(RaceLogFlagEvent flagEvent) {
return flagEvent.getUpperFlag().equals(Flags.XRAY) && flagEvent.isDisplayed();
boolean disregardBecauseAfterAt = at != null && flagEvent.getTimePoint().after(at);
return ! disregardBecauseAfterAt && flagEvent.getUpperFlag().equals(Flags.XRAY) && flagEvent.isDisplayed();
}
}
@@ -1,17 +1,26 @@
package com.sap.sailing.domain.racelog.analyzing.impl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
public class IndividualRecallRemovedFinder extends IndividualRecallFinder {
private final TimePoint at;
public IndividualRecallRemovedFinder(RaceLog raceLog) {
this(raceLog, null);
}
public IndividualRecallRemovedFinder(RaceLog raceLog, TimePoint at) {
super(raceLog);
this.at = at;
}
protected boolean isRelevant(RaceLogFlagEvent flagEvent) {
return flagEvent.getUpperFlag().equals(Flags.XRAY) && !flagEvent.isDisplayed();
boolean disregardBecauseAfterAt = at != null && flagEvent.getTimePoint().after(at);
return ! disregardBecauseAfterAt && flagEvent.getUpperFlag().equals(Flags.XRAY) && !flagEvent.isDisplayed();
}
}
@@ -9,11 +9,15 @@ public class IsIndividualRecallDisplayedAnalyzer extends RaceLogAnalyzer<Boolean
private final IndividualRecallFinder removedFinder;
public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog) {
this(raceLog, new IndividualRecallDisplayedFinder(raceLog), new IndividualRecallRemovedFinder(raceLog));
this(raceLog, null);
}
public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog, TimePoint at) {
this(raceLog, new IndividualRecallDisplayedFinder(raceLog, at), new IndividualRecallRemovedFinder(raceLog, at), at);
}
public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog, IndividualRecallDisplayedFinder displayedFinder,
IndividualRecallRemovedFinder removedFinder) {
IndividualRecallRemovedFinder removedFinder, TimePoint at) {
super(raceLog);
this.displayedFinder = displayedFinder;
this.removedFinder = removedFinder;
@@ -8,25 +8,29 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.racelog.FlagPole;
/**
* Allows you to query the current and state of displayed and remove flags.
* Allows you to query the current state of displayed and removed flags.
*/
public class FlagPoleState {
private final List<FlagPole> currentState;
private final List<FlagPole> nextState;
private final TimePoint currentStateValidFrom;
private final TimePoint nextStateValidFrom;
private final boolean hasNextState;
public FlagPoleState(List<FlagPole> currentState) {
this(currentState, null, null, false);
public FlagPoleState(List<FlagPole> currentState, TimePoint currentStateValidFrom) {
this(currentState, currentStateValidFrom, null, null, false);
}
public FlagPoleState(List<FlagPole> currentState, List<FlagPole> nextState, TimePoint nextStateValidFrom) {
this(currentState, nextState, nextStateValidFrom, true);
public FlagPoleState(List<FlagPole> currentState, TimePoint currentStateValidFrom,
List<FlagPole> nextState, TimePoint nextStateValidFrom) {
this(currentState, currentStateValidFrom, nextState, nextStateValidFrom, true);
}
private FlagPoleState(List<FlagPole> currentState, List<FlagPole> nextState, TimePoint nextStateValidFrom, boolean hasNextState) {
private FlagPoleState(List<FlagPole> currentState, TimePoint currentStateValidFrom,
List<FlagPole> nextState, TimePoint nextStateValidFrom, boolean hasNextState) {
this.currentState = currentState;
this.currentStateValidFrom = currentStateValidFrom;
this.nextState = nextState;
this.nextStateValidFrom = nextStateValidFrom;
this.hasNextState = hasNextState;
@@ -42,30 +46,51 @@ public class FlagPoleState {
public TimePoint getNextStateValidFrom() {
return nextStateValidFrom;
}
public TimePoint getCurrentStateValidFrom() {
return currentStateValidFrom;
}
public boolean hasNextState() {
return hasNextState;
}
public static List<FlagPole> computeChanges(List<FlagPole> before, List<FlagPole> after) {
before = before == null ? Collections.<FlagPole>emptyList() : before;
after = after == null ? Collections.<FlagPole>emptyList() : after;
List<FlagPole> beforeWithoutMatchInAfter = new ArrayList<FlagPole>(before);
List<FlagPole> changes = new ArrayList<FlagPole>();
for (FlagPole poleAfter : after) {
boolean hasSamePoleBefore = false;
for (FlagPole poleBefore : before) {
if (poleAfter.describesSameButForDisplayed(poleBefore)) {
beforeWithoutMatchInAfter.remove(poleBefore);
}
if (poleAfter.describesSame(poleBefore)) {
hasSamePoleBefore = true;
break;
}
}
if (!hasSamePoleBefore) {
changes.add(poleAfter);
}
}
for (FlagPole poleBefore : beforeWithoutMatchInAfter) {
if (poleBefore.isDisplayed()) {
changes.add(new FlagPole(poleBefore.getUpperFlag(), poleBefore.getLowerFlag(), false));
}
}
return changes;
}
public List<FlagPole> computeUpcomingChanges() {
if (!hasNextState) {
return Collections.emptyList();
}
List<FlagPole> changes = new ArrayList<FlagPole>();
for (FlagPole nextPole : nextState) {
boolean hasSamePoleInCurrentState = false;
for (FlagPole currentPole : currentState) {
if (nextPole.describesSame(currentPole)) {
hasSamePoleInCurrentState = true;
break;
}
}
if (!hasSamePoleInCurrentState) {
changes.add(nextPole);
}
}
return changes;
return computeChanges(currentState, nextState);
}
public static boolean describesSameState(FlagPoleState left, FlagPoleState right) {
@@ -99,5 +124,52 @@ public class FlagPoleState {
return true;
}
public FlagPoleState getPreviousState(ReadonlyRacingProcedure racingProcedure, TimePoint startTime) {
if (currentStateValidFrom == null) {
return this;
}
return racingProcedure.getActiveFlags(startTime, currentStateValidFrom.minus(1));
}
public static FlagPole getMostInterestingFlagPole(List<FlagPole> poles) {
return getMostInterestingFlagPole(poles, poles);
}
public static FlagPole getMostInterestingFlagPole(List<FlagPole> previousPoles, List<FlagPole> currentPoles) {
List<FlagPole> changes = computeChanges(previousPoles, currentPoles);
FlagPole poleRemoved = null;
for (FlagPole changedPole : changes) {
if (changedPole.isDisplayed()) {
return changedPole;
} else {
poleRemoved = changedPole;
}
}
if (poleRemoved != null) {
return poleRemoved;
}
for (FlagPole currentPole : currentPoles) {
if (currentPole.isDisplayed()) {
return currentPole;
}
}
return currentPoles.size() == 0 ? null : currentPoles.get(0);
}
public boolean hasPoleChanged(FlagPole newPole) {
boolean found = false;
for (FlagPole current : currentState) {
if (current.describesSameButForDisplayed(newPole)) {
found = true;
if (current.isDisplayed() != newPole.isDisplayed()) {
return true;
}
}
}
if (! found && newPole.isDisplayed()) {
return true;
}
return false;
}
}
@@ -68,6 +68,16 @@ public interface ReadonlyRacingProcedure extends RaceStateEventProcessor, RaceSt
*/
boolean isIndividualRecallDisplayed();
/**
* Returns <code>true</code> if there is an individual recall signaled in the {@link RaceLog} at the TimePoint {@code at}.
*/
boolean isIndividualRecallDisplayed(TimePoint at);
/**
* Gets the time the individual recall flag was displayed (or <code>null</code>).
*/
TimePoint getIndividualRecallDisplayedTime();
/**
* Gets the time the individual recall flag was removed (or <code>null</code>).
*/
@@ -47,7 +47,7 @@ public class BasicRacingProcedureImpl extends BaseRacingProcedure {
@Override
public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) {
return new FlagPoleState(Collections.<FlagPole>emptyList());
return new FlagPoleState(Collections.<FlagPole>emptyList(), null);
}
@Override
@@ -91,37 +91,43 @@ public class ESSRacingProcedureImpl extends BaseRacingProcedure implements ESSRa
public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) {
if (now.before(startTime.minus(startPhaseAPDownInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.AP, true), new FlagPole(Flags.ESSTHREE, false)),
Arrays.asList(new FlagPole(Flags.AP, true), new FlagPole(Flags.ESSTHREE, false)), null,
Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)),
startTime.minus(startPhaseAPDownInterval));
} else if (now.before(startTime.minus(startPhaseESSThreeUpInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)),
Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)),
startTime.minus(startPhaseAPDownInterval),
Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)),
startTime.minus(startPhaseESSThreeUpInterval));
} else if (now.before(startTime.minus(startPhaseESSTwoUpInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)),
Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)),
startTime.minus(startPhaseESSThreeUpInterval),
Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)),
startTime.minus(startPhaseESSTwoUpInterval));
} else if (now.before(startTime.minus(startPhaseESSOneUpInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)),
Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)),
startTime.minus(startPhaseESSTwoUpInterval),
Arrays.asList(new FlagPole(Flags.ESSONE, true)),
startTime.minus(startPhaseESSOneUpInterval));
} if (now.before(startTime)) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.ESSONE, true)),
Arrays.asList(new FlagPole(Flags.ESSONE, true)),
startTime.minus(startPhaseESSOneUpInterval),
Arrays.asList(new FlagPole(Flags.ESSONE, false)),
startTime);
} else {
if (isIndividualRecallDisplayed()) {
if (isIndividualRecallDisplayed(now)) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.XRAY, true)),
getIndividualRecallDisplayedTime(),
Arrays.asList(new FlagPole(Flags.XRAY, false)),
getIndividualRecallRemovalTime());
} else {
return new FlagPoleState(Collections.<FlagPole>emptyList());
TimePoint recallRemoved = getIndividualRecallRemovalTime();
return new FlagPoleState(Collections.<FlagPole>emptyList(), recallRemoved == null ? startTime : recallRemoved);
}
}
}
@@ -146,24 +146,27 @@ public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements
TimePoint gateShutdownTime = getGateShutdownTimePoint(startTime);
if (now.before(startTime.minus(startPhaseClassOverGolfUpIntervall))) {
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, false), new FlagPole(Flags.PAPA,
false)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
false)), null, Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
startTime.minus(startPhaseClassOverGolfUpIntervall));
} else if (now.before(startTime.minus(startPhasePapaUpInterval))) {
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA,
false)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, true)),
false)), startTime.minus(startPhaseClassOverGolfUpIntervall),
Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, true)),
startTime.minus(startPhasePapaUpInterval));
} else if (now.before(startTime.minus(startPhasePapaDownInterval))) {
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA,
true)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
true)), startTime.minus(startPhasePapaUpInterval),
Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
startTime.minus(startPhasePapaDownInterval));
} else if (now.before(startTime)) {
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA,
false)), Arrays.asList(new FlagPole(classFlag, false), new FlagPole(Flags.GOLF, true)), startTime);
false)), startTime.minus(startPhasePapaDownInterval),
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(Flags.GOLF, true)), startTime);
} else if (now.before(gateShutdownTime)) {
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, true)), Arrays.asList(new FlagPole(
Flags.GOLF, false)), gateShutdownTime);
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, true)), startTime,
Arrays.asList(new FlagPole(Flags.GOLF, false)), gateShutdownTime);
} else {
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, false)));
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, false)), gateShutdownTime);
}
}
@@ -13,6 +13,7 @@ import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogEventFactory;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.analyzing.impl.IndividualRecallDisplayedFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.IndividualRecallRemovedFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.IsIndividualRecallDisplayedAnalyzer;
import com.sap.sailing.domain.racelog.impl.RaceLogChangedVisitor;
import com.sap.sailing.domain.racelog.state.RaceStateEvent;
@@ -46,6 +47,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
private final RacingProcedureChangedListeners<? extends RacingProcedureChangedListener> changedListeners;
private final IsIndividualRecallDisplayedAnalyzer isRecallDisplayedAnalyzer;
private final IndividualRecallDisplayedFinder recallDisplayedFinder;
private final IndividualRecallRemovedFinder recallRemovedFinder;
private final RaceLogEventVisitor raceLogListener;
private boolean cachedIsIndividualRecallDisplayed;
@@ -67,6 +69,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
this.changedListeners = createChangedListenerContainer();
this.isRecallDisplayedAnalyzer = new IsIndividualRecallDisplayedAnalyzer(raceLog);
this.recallDisplayedFinder = new IndividualRecallDisplayedFinder(raceLog);
this.recallRemovedFinder = new IndividualRecallRemovedFinder(raceLog);
this.raceLogListener = new RaceLogChangedVisitor(this);
this.raceLog.addListener(raceLogListener);
@@ -126,17 +129,34 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
public boolean isIndividualRecallDisplayed() {
return cachedIsIndividualRecallDisplayed;
}
@Override
public boolean isIndividualRecallDisplayed(TimePoint at) {
if (hasIndividualRecall()) {
return new IsIndividualRecallDisplayedAnalyzer(getRaceLog(), at).analyze();
}
return false;
}
@Override
public TimePoint getIndividualRecallDisplayedTime() {
if (hasIndividualRecall()) {
return recallDisplayedFinder.analyze();
}
return null;
}
@Override
public TimePoint getIndividualRecallRemovalTime() {
TimePoint displayed = getIndividualRecallDisplayedTime();
if (hasIndividualRecall()) {
if (isIndividualRecallDisplayed()) {
TimePoint displayTime = recallDisplayedFinder.analyze();
if (displayTime != null) {
return displayTime.plus(individualRecallRemovalTimeout);
}
TimePoint removedEvent = recallRemovedFinder.analyze();
if (removedEvent != null && (displayed == null || removedEvent.after(displayed))) {
return removedEvent;
}
}
if (displayed != null) {
return displayed.plus(individualRecallRemovalTimeout);
}
return null;
}
@@ -177,10 +197,6 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
protected void update() {
boolean isRecallDisplayed = isRecallDisplayedAnalyzer.analyze();
// always call listeners for changed flag, as this does not only affect recal, but also
// changes start procedure, for which some text elements need to be updated
// ({@link BaseRaceInfoFragmen#renderFlagChangesCountdown}
changedListeners.onActiveFlagsChanged(this);
if (cachedIsIndividualRecallDisplayed != isRecallDisplayed) {
cachedIsIndividualRecallDisplayed = isRecallDisplayed;
@@ -192,6 +208,11 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT);
}
}
// always call listeners for changed flag, as this does not only affect recal, but also
// changes start procedure, for which some text elements need to be updated
// ({@link BaseRaceInfoFragmen#renderFlagChangesCountdown}
changedListeners.onActiveFlagsChanged(this);
}
private void rescheduleIndividualRecallTimeout(TimePoint displayTime) {
@@ -126,32 +126,38 @@ public class RRS26RacingProcedureImpl extends BaseRacingProcedure implements RRS
getConfiguration().getClassFlag() : Flags.CLASS;
if (now.before(startTime.minus(startPhaseClassUpInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)),
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)),
null,
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
startTime.minus(startPhaseClassUpInterval));
} else if (now.before(startTime.minus(startPhaseStartModeUpInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
startTime.minus(startPhaseClassUpInterval),
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)),
startTime.minus(startPhaseStartModeUpInterval));
} else if (now.before(startTime.minus(startPhaseStartModeDownInterval))) {
return new FlagPoleState(
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)),
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)),
startTime.minus(startPhaseStartModeUpInterval),
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
startTime.minus(startPhaseStartModeDownInterval));
} else if (now.before(startTime)) {
return new FlagPoleState(
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
startTime.minus(startPhaseStartModeDownInterval),
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)),
startTime);
} else {
if (isIndividualRecallDisplayed()) {
if (isIndividualRecallDisplayed(now)) {
return new FlagPoleState(
Arrays.asList(new FlagPole(Flags.XRAY, true)),
getIndividualRecallDisplayedTime(),
Arrays.asList(new FlagPole(Flags.XRAY, false)),
getIndividualRecallRemovalTime());
} else {
return new FlagPoleState(Collections.<FlagPole>emptyList());
TimePoint recallRemoved = getIndividualRecallRemovalTime();
return new FlagPoleState(Collections.<FlagPole>emptyList(), recallRemoved == null ? startTime : recallRemoved);
}
}
}
@@ -117,7 +117,7 @@ public class LeaderboardCourseChangeTest {
DomainFactory baseDomainFactory = new DomainFactoryImpl();
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false,
trackedRegattaRegistry, baseDomainFactory);
trackedRegattaRegistry, baseDomainFactory, /* fillNetPointsUncorrected */ false);
assertEquals(6, leaderboardDTO.rows.values().iterator().next().fieldsByRaceColumnName.values().iterator()
.next().legDetails.size());
@@ -126,7 +126,7 @@ public class LeaderboardCourseChangeTest {
course.removeWaypoint(3);
leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false, trackedRegattaRegistry,
baseDomainFactory);
baseDomainFactory, /* fillNetPointsUncorrected */ false);
assertEquals(4, leaderboardDTO.rows.values().iterator().next().fieldsByRaceColumnName.values().iterator()
.next().legDetails.size());
@@ -147,17 +147,17 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
InterruptedException {
}
};
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE);
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false);
assertNotNull(leaderboardDTO);
assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // assert it's cached
assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false)); // assert it's cached
leaderboard.getRaceColumnByName(columnName).releaseTrackedRace(defaultFleet); // this should clear the cache
LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE);
LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false);
assertNotSame(leaderboardDTO, leaderboardDTO2);
assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again
assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false)); // and cached again
leaderboard.getRaceColumnByName(columnName).setTrackedRace(defaultFleet, testRaces.iterator().next()); // clear cache again; requires listener(s) to still be attached
LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE);
LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false);
assertNotSame(leaderboardDTO2, leaderboardDTO3);
assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE)); // and cached again
assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false)); // and cached again
}
@Test
@@ -28,6 +28,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
@@ -54,6 +55,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
@@ -539,6 +541,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
return null;
}
@Override
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public Wind getDirectionFromStartToNextMark(TimePoint at) {
@@ -679,6 +687,13 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource) {
// TODO Auto-generated method stub
@@ -958,6 +973,13 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to,
boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException {
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
@@ -42,6 +43,7 @@ import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
@@ -316,6 +318,12 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
// TODO Auto-generated method stub
return null;
}
@Override
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedRegatta getTrackedRegatta() {
@@ -416,6 +424,13 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return null;
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public WindStore getWindStore() {
// TODO Auto-generated method stub
@@ -605,6 +620,13 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to,
boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException {
@@ -76,7 +76,8 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
for (Fleet fleet : fleets) {
this.fleetsByName.put(fleet.getName(), fleet);
}
fleetsInAscendingOrder = new ArrayList<Fleet>(fleetsByName.values());
fleetsInAscendingOrder = new ArrayList<Fleet>();
Util.addAll(fleets, fleetsInAscendingOrder);
Collections.sort(fleetsInAscendingOrder);
List<RaceColumnInSeries> myRaceColumns = new ArrayList<RaceColumnInSeries>();
this.raceColumns = myRaceColumns;
@@ -73,7 +73,7 @@ public interface Leaderboard extends LeaderboardBase {
LeaderboardDTO computeDTO(final TimePoint timePoint,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry,
DomainFactory baseDomainFactory) throws NoWindException;
DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException;
/**
* Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this
@@ -480,10 +480,11 @@ public interface Leaderboard extends LeaderboardBase {
* used to determine which of the races are still being tracked and which ones are not
* @param baseDomainFactory
* required as factory and cache for various DTO types
* @param fillNetPointsUncorrected TODO
*/
LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException,
InterruptedException, ExecutionException;
NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher();
@@ -88,7 +88,7 @@ public class LeaderboardDTOCache implements LeaderboardCache {
private static final long serialVersionUID = 7287916997229815039L;
@Override
protected boolean removeEldestEntry(Map.Entry<Util.Triple<TimePoint, Collection<String>, Boolean>, FutureTask<LeaderboardDTO>> e) {
return size() > 10; // remember 10 LeaderboardDTOs per leaderborad
return size() > 10; // remember 10 LeaderboardDTOs per leaderboard
}
};
leaderboardCacheLock = new NamedReentrantReadWriteLock("leaderboardCacheLock for "+leaderboard.getName(), /* fair */ false);
@@ -119,7 +119,6 @@ public class LeaderboardDTOCache implements LeaderboardCache {
* The {@link #waitForLatestAnalyses} field is passed on to
* {@link SailingServiceImpl#computeLeaderboardByName(Leaderboard, TimePoint, Collection, boolean)} if a new cache
* entry needs to be computed. Caching distinguished between
*
* @param addOverallDetails
* tells whether the columns containing the overall details shall be added
*/
@@ -167,7 +166,7 @@ public class LeaderboardDTOCache implements LeaderboardCache {
try {
LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory);
waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillNetPointsUncorrected */ false);
return result;
} finally {
LockUtil.unpropagateLockSetFrom(callerThread);
@@ -0,0 +1,107 @@
package com.sap.sailing.domain.leaderboard.caching;
import java.util.concurrent.ConcurrentHashMap;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.impl.AbstractSimpleLeaderboardImpl;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Triple;
/**
* A cache structure that is used for a single call to
* {@link AbstractSimpleLeaderboardImpl#computeDTO(com.sap.sailing.domain.common.TimePoint, java.util.Collection, boolean, boolean, com.sap.sailing.domain.tracking.TrackedRegattaRegistry, com.sap.sailing.domain.base.DomainFactory)}.
* It is to be passed on to various query methods that may required common data that is expensive to compute and
* depends on equal parameters, such as an equal time point. The underlying assumption is that during one leaderboard (re-)calculation cycle the
* dynamic changes in the wind field and the mark positions can safely be ignored so that wind data for competitors and legs and the legs'
* bearings only need to be calculated once.
* <p>
*
* This cache is equipped with the references necessary to compute the information if need be. The cache is thread safe. Note that it wouldn't be
* a good idea to use {@link ThreadLocal}s for this because there is a lot of concurrency involved, and the {@link ThreadLocal} would have to be set
* on each thread involved to be helpful which seems too complicated.
*
* @author Axel Uhl (D043530)
*
*/
public class LeaderboardDTOCalculationReuseCache implements WindLegTypeAndLegBearingCache {
/**
* The reference time point for all queries to which values will be cached here. Queries that don't match this time point are
* neither cached nor looked up in the cache.
*/
private final TimePoint timePoint;
/**
* the leg types at "timePoint"
*/
final ConcurrentHashMap<Leg, LegType> legTypeCache;
/**
* the wind at competitor's position at timePoint
*/
final ConcurrentHashMap<Triple<TrackedRace, Competitor, TimePoint>, Wind> windCache;
/**
* the leg's bearing at timePoint
*/
final ConcurrentHashMap<Leg, Bearing> legBearingCache;
public LeaderboardDTOCalculationReuseCache(TimePoint timePoint) {
legTypeCache = new ConcurrentHashMap<>();
windCache = new ConcurrentHashMap<>();
legBearingCache = new ConcurrentHashMap<>();
this.timePoint = timePoint;
}
public LegType getLegType(TrackedLeg trackedLeg, TimePoint timePoint) throws NoWindException {
LegType result;
if (Util.equalsWithNull(this.timePoint, timePoint)) {
result = legTypeCache.get(trackedLeg.getLeg());
if (result == null) {
result = trackedLeg.getLegType(timePoint);
legTypeCache.put(trackedLeg.getLeg(), result);
}
} else {
result = trackedLeg.getLegType(timePoint); // different time point; don't cache
}
return result;
}
public Bearing getLegBearing(TrackedLeg trackedLeg, TimePoint timePoint) {
Bearing result;
if (Util.equalsWithNull(this.timePoint, timePoint)) {
result = legBearingCache.get(trackedLeg.getLeg());
if (result == null) {
result = trackedLeg.getLegBearing(timePoint);
legBearingCache.put(trackedLeg.getLeg(), result);
}
} else {
result = trackedLeg.getLegBearing(timePoint); // different time point; don't cache
}
return result;
}
/**
* Determines the wind at the <code>competitor</code>'s {@link GPSFixTrack#getEstimatedPosition(TimePoint, boolean) estimated position} at
* <code>timePoint</code>. The result is cached for subsequent calls with equal parameters.
*/
public Wind getWind(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint) {
Triple<TrackedRace, Competitor, TimePoint> cacheKey = new Triple<>(trackedRace, competitor, timePoint);
Wind result = windCache.get(cacheKey);
if (result == null) {
result = trackedRace.getWind(trackedRace.getTrack(competitor).getEstimatedPosition(timePoint, false), timePoint);
windCache.put(cacheKey, result);
}
return result;
}
}
@@ -188,7 +188,8 @@ public class LiveLeaderboardUpdater implements Runnable {
}
}
}
logger.info(""+LiveLeaderboardUpdater.class.getSimpleName()+" cache hits/misses: "+cacheHitCount+"/"+cacheMissCount);
logger.info(""+LiveLeaderboardUpdater.class.getSimpleName()+" for "+getLeaderboard().getName()+
" cache hits/misses: "+cacheHitCount+"/"+cacheMissCount);
return result;
}
@@ -217,7 +218,7 @@ public class LiveLeaderboardUpdater implements Runnable {
* <code>result</code> has the details.
*
* @param addOverallDetails tells whether overall details are requested for the resulting leaderboard; see also
* {@link Leaderboard#computeDTO(TimePoint, Collection, boolean, boolean, TrackedRegattaRegistry, DomainFactory)}.
* {@link Leaderboard#computeDTO(TimePoint, Collection, boolean, boolean, TrackedRegattaRegistry, DomainFactory, boolean)}.
*/
private synchronized void updateRequestTimes(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails) {
lastRequest = getLeaderboard().getNowMinusDelay();
@@ -275,7 +276,7 @@ public class LiveLeaderboardUpdater implements Runnable {
final boolean addOverallDetails = getOverallDetails(timePoint);
LeaderboardDTO newCacheValue = leaderboard.computeDTO(timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
/* waitForLatestAnalyses */false, trackedRegattaRegistry, baseDomainFactory);
/* waitForLatestAnalyses */false, trackedRegattaRegistry, baseDomainFactory, /* fillNetPointsUncorrected */ false);
updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, newCacheValue);
} catch (NoWindException e) {
logger.info("Unable to update cached leaderboard results for leaderboard " + leaderboard.getName() + ": "
@@ -66,6 +66,7 @@ import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
@@ -83,6 +84,7 @@ import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.util.impl.LockUtil;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
import com.sap.sailing.util.impl.RaceColumnListeners;
@@ -164,19 +166,20 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
class EntryImpl implements Entry {
private final Callable<Integer> trackedRankProvider;
private final Double netPoints;
private final Double netPointsUncorrected;
private final Callable<Double> netPointsUncorrectedProvider;
private final boolean isNetPointsCorrected;
private final Double totalPoints;
private final MaxPointsReason maxPointsReason;
private final boolean discarded;
private final Fleet fleet;
private EntryImpl(Callable<Integer> trackedRankProvider, Double netPoints, Double netPointsUncorrected, boolean isNetPointsCorrected,
private EntryImpl(Callable<Integer> trackedRankProvider, Double netPoints,
Callable<Double> netPointsUncorrectedProvider, boolean isNetPointsCorrected,
Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
super();
this.trackedRankProvider = trackedRankProvider;
this.netPoints = netPoints;
this.netPointsUncorrected = netPointsUncorrected;
this.netPointsUncorrectedProvider = netPointsUncorrectedProvider;
this.isNetPointsCorrected = isNetPointsCorrected;
this.totalPoints = totalPoints;
this.maxPointsReason = maxPointsReason;
@@ -217,7 +220,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
@Override
public Double getNetPointsUncorrected() throws NoWindException {
return netPointsUncorrected;
try {
return netPointsUncorrectedProvider.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -690,7 +697,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
boolean discarded = isDiscarded(competitor, race, timePoint);
final Double correctedScore = correctedResults.getCorrectedScore();
return new EntryImpl(trackedRankProvider, correctedScore, correctedResults.getUncorrectedScore(),
return new EntryImpl(trackedRankProvider, correctedScore,
new Callable<Double>() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } },
correctedResults.isCorrected(),
discarded ? DOUBLE_0
: correctedScore == null ? null : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
@@ -759,7 +767,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
return getTrackedRank(competitor, raceColumn, timePoint);
}
};
Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
if (discardedRacesForCompetitor == null) {
@@ -769,7 +777,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
final Double correctedScore = correctedResults.getCorrectedScore();
Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
correctedResults.getUncorrectedScore(), correctedResults.isCorrected(),
new Callable<Double>() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } },
correctedResults.isCorrected(),
discarded ? DOUBLE_0 : (correctedScore==null?null:
Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(),
discarded, raceColumn.getFleetOfCompetitor(competitor));
@@ -1159,9 +1168,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public LeaderboardDTO computeDTO(final TimePoint timePoint,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory)
final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory,
final boolean fillNetPointsUncorrected)
throws NoWindException {
long startOfRequestHandling = System.currentTimeMillis();
final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
: this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), this
@@ -1273,7 +1284,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails != null
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory);
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
fillNetPointsUncorrected, cache);
} catch (NoWindException e) {
logger.info("Exception trying to compute leaderboard entry for competitor "
+ competitor.getName() + " in race column " + raceColumn.getName() + ": "
@@ -1345,15 +1357,24 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
* may not reflect all data already received; otherwise, the method will always block for the latest
* cache updates to have happened before returning.
* @param fillNetPointsUncorrected
* tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather
* expensive, especially when compared to simply retrieving a score correction, and particularly if in a
* larger fleet a number of competitors haven't properly finished the race. This should only be used for
* leaderboard editing where a user needs to see what the uncorrected score was that would be used when
* the correction was removed.
*/
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory) throws NoWindException {
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory,
boolean fillNetPointsUncorrected, LeaderboardDTOCalculationReuseCache cache) throws NoWindException {
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
entryDTO.netPoints = entry.getNetPoints();
entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected();
if (fillNetPointsUncorrected) {
entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected();
}
entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
entryDTO.totalPoints = entry.getTotalPoints();
entryDTO.reasonForMaxPoints = entry.getMaxPointsReason();
@@ -1373,7 +1394,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
if (addLegDetails && trackedRace != null) {
try {
RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache);
RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache);
entryDTO.legDetails = raceDetails.getLegDetails();
entryDTO.windwardDistanceToOverallLeaderInMeters = raceDetails.getWindwardDistanceToOverallLeader() == null ? null
: raceDetails.getWindwardDistanceToOverallLeader().getMeters();
@@ -1492,19 +1513,20 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
* cache updates to have happened before returning.
*/
private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException, InterruptedException, ExecutionException {
RaceDetails raceDetails;
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
LeaderboardDTOCalculationReuseCache cache) throws NoWindException, InterruptedException, ExecutionException {
final RaceDetails raceDetails;
if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache);
raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, cache);
} else {
raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache);
raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache);
}
return raceDetails;
}
private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache)
throws NoWindException, InterruptedException, ExecutionException {
final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
final LeaderboardDTOCalculationReuseCache cache) throws NoWindException, InterruptedException, ExecutionException {
final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor);
RunnableFuture<RaceDetails> raceDetails;
synchronized (raceDetailsAtEndOfTrackingCache) {
@@ -1521,7 +1543,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
// TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock
/* TODO old comment when it was still true: "because this is done only once after end of tracking" */
/* waitForLatestAnalyses (maneuver and cross track error) */ false,
legRanksCache);
legRanksCache, cache);
}
});
raceDetailsExecutor.execute(raceDetails);
@@ -1535,9 +1557,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
List<LegEntryDTO> legDetails;
legDetails = new ArrayList<LegEntryDTO>();
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
LeaderboardDTOCalculationReuseCache cache) throws NoWindException {
final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>();
final Course course = trackedRace.getRace().getCourse();
course.lockForRead(); // hold back any course re-configurations while looping over the legs
try {
@@ -1548,7 +1570,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
// immediately. That's why we've acquired a read lock for the course above.
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) {
legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache);
legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, cache);
} else {
legEntry = null;
}
@@ -1557,9 +1579,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
final Distance windwardDistanceToOverallLeader = trackedRace == null ? null : trackedRace
.getWindwardDistanceToOverallLeader(competitor, timePoint, WindPositionMode.LEG_MIDDLE);
final Distance averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError(
competitor, timePoint, waitForLatestAnalyses);
competitor, timePoint, waitForLatestAnalyses, cache);
final Distance averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError(
competitor, timePoint, waitForLatestAnalyses);
competitor, timePoint, waitForLatestAnalyses, cache);
return new RaceDetails(legDetails, windwardDistanceToOverallLeader, averageAbsoluteCrossTrackError, averageSignedCrossTrackError);
} finally {
course.unlockAfterRead();
@@ -1567,7 +1589,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, LeaderboardDTOCalculationReuseCache cache) throws NoWindException {
LegEntryDTO result;
if (trackedLeg == null || trackedLeg.getTime(timePoint) == null) {
result = null;
@@ -1599,7 +1621,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
// FIXME problem: asking just after the beginning of the leg yields very different values from asking for the end of the previous leg.
// This is because for the previous leg it's decided based on the mark passings; for the next (current) leg it's decided based on
// windward distance and VMG
Double gapAtEndOfPreviousLegInSeconds = getGapAtEndOfPreviousLegInSeconds(trackedLeg);
Double gapAtEndOfPreviousLegInSeconds = getGapAtEndOfPreviousLegInSeconds(trackedLeg, cache);
if (gapAtEndOfPreviousLegInSeconds != null) {
result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLegInSeconds;
}
@@ -1684,7 +1706,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
return result;
}
private Double getGapAtEndOfPreviousLegInSeconds(TrackedLegOfCompetitor trackedLeg) throws NoWindException {
private Double getGapAtEndOfPreviousLegInSeconds(TrackedLegOfCompetitor trackedLeg, WindLegTypeAndLegBearingCache cache) throws NoWindException {
final Double result;
final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse();
course.lockForRead();
@@ -1715,7 +1737,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
} else {
TrackedLeg previousTrackedLeg = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLeg(course.getLegs().get(indexOfStartWaypoint-1));
TrackedLegOfCompetitor previousTrackedLegOfCompetitor = previousTrackedLeg.getTrackedLeg(trackedLeg.getCompetitor());
result = previousTrackedLegOfCompetitor.getGapToLeaderInSeconds(previousTrackedLegOfCompetitor.getFinishTime(), WindPositionMode.LEG_MIDDLE);
result = previousTrackedLegOfCompetitor.getGapToLeaderInSeconds(previousTrackedLegOfCompetitor.getFinishTime(), WindPositionMode.LEG_MIDDLE, cache);
}
return result;
} finally {
@@ -1766,7 +1788,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException,
InterruptedException, ExecutionException {
LeaderboardDTO result = null;
if (timePoint == null) {
@@ -1776,7 +1798,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
// when the tracked race associations or score corrections or tracked race contents changes:
final TimePoint nowMinusDelay = this.getNowMinusDelay();
final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification();
if (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification)) {
if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) {
// if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode
// and pull the result from the regular leaderboard cache:
timePoint = timePointOfLatestModification;
@@ -1787,11 +1809,18 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
}
if (timePoint != null) {
// in replay we'd like up-to-date results; they are still cached
// which is OK because the cache is invalidated whenever any of the tracked races attached to the
// leaderboard changes.
result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
addOverallDetails, baseDomainFactory, trackedRegattaRegistry);
if (fillNetPointsUncorrected) {
// explicitly filling the uncorrected net points requires uncached recalculation
result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true,
trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected);
} else {
// in replay we'd like up-to-date results; they are still cached
// which is OK because the cache is invalidated whenever any of the tracked races attached to the
// leaderboard changes.
result = getLeaderboardDTOCache().getLeaderboardByName(timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory,
trackedRegattaRegistry);
}
}
return result;
}
@@ -337,9 +337,10 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
* <p>
*/
@Override
public Result getCorrectedScore(Callable<Integer> trackedRankProvider, final Competitor competitor,
final RaceColumn raceColumn, final TimePoint timePoint, final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher,
ScoringScheme scoringScheme) {
public Result getCorrectedScore(final Callable<Integer> trackedRankProvider, final Competitor competitor,
final RaceColumn raceColumn, final TimePoint timePoint,
final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher,
final ScoringScheme scoringScheme) {
Double result;
final MaxPointsReason maxPointsReason = getMaxPointsReason(competitor, raceColumn, timePoint);
if (maxPointsReason == MaxPointsReason.NONE) {
@@ -356,19 +357,6 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
result = correctedNonMaxedScore;
}
}
// also compute uncorrected score
Double resultUncorrected = 0.0;
try {
resultUncorrected = scoringScheme.getScoreForRank(raceColumn, competitor, trackedRankProvider.call(), new Callable<Integer>() {
@Override
public Integer call() {
return getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher);
}
}, numberOfCompetitorsInLeaderboardFetcher);
} catch (Exception e) {
throw new RuntimeException(e);
}
final Double uncorrectedScore = resultUncorrected;
final Double correctedScore = result;
return new Result() {
@Override
@@ -393,7 +381,19 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
@Override
public Double getUncorrectedScore() {
return uncorrectedScore;
Double resultUncorrected = 0.0;
try {
resultUncorrected = scoringScheme.getScoreForRank(raceColumn, competitor,
trackedRankProvider.call(), new Callable<Integer>() {
@Override
public Integer call() {
return getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher);
}
}, numberOfCompetitorsInLeaderboardFetcher);
} catch (Exception e) {
throw new RuntimeException(e);
}
return resultUncorrected;
}
};
}
@@ -0,0 +1,16 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
/**
* When comprehensive calculations are prone to query a leg's bearing several times for the same leg for
* the same time point, a cache of this type can be used to eliminate repeated evaluation of common sub-expressions that
* are expensive to calculate.
*
* @author Axel Uhl (D043530)
*
*/
public interface LegBearingCache {
Bearing getLegBearing(TrackedLeg trackedLeg, TimePoint timePoint);
}
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
/**
* When comprehensive calculations are prone to query the leg type several times for the same leg for the same time
* point, a cache of this type can be used to eliminate repeated evaluation of common sub-expressions that are expensive
* to calculate.
*
* @author Axel Uhl (D043530)
*
*/
public interface LegTypeCache {
LegType getLegType(TrackedLeg trackedLeg, TimePoint timePoint) throws NoWindException;
}
@@ -126,6 +126,11 @@ public interface TrackedLegOfCompetitor extends Serializable {
*/
Double getGapToLeaderInSeconds(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException;
/**
* Same as {@link #getGapToLeaderInSeconds(TimePoint)}, only that a cache for wind and leg type data is used.
*/
Double getGapToLeaderInSeconds(TimePoint timePoint, WindLegTypeAndLegBearingCache cache) throws NoWindException;
/**
* If a caller already went through the effort of computing the leg's leader at <code>timePoint</code>, it
* can share this knowledge to speed up computation as compared to {@link #getGapToLeaderInSeconds(TimePoint, WindPositionMode)}.
@@ -133,6 +138,13 @@ public interface TrackedLegOfCompetitor extends Serializable {
*/
Double getGapToLeaderInSeconds(TimePoint timePoint, Competitor leaderInLegAtTimePoint, WindPositionMode windPositionMode) throws NoWindException;
/**
* Same as {@link #getGapToLeaderInSeconds(TimePoint, Competitor)}, only that an additional cache is used
* to avoid redundant evaluations of leg types and wind field information across various calculations that
* all can use the same basic information.
*/
Double getGapToLeaderInSeconds(TimePoint timePoint, Competitor leaderInLegAtTimePoint, WindLegTypeAndLegBearingCache cache) throws NoWindException;
boolean hasStartedLeg(TimePoint timePoint);
boolean hasFinishedLeg(TimePoint timePoint);
@@ -213,4 +225,16 @@ public interface TrackedLegOfCompetitor extends Serializable {
* Computes the angle between the competitors direction and the wind.
*/
Bearing getBeatAngle(TimePoint at) throws NoWindException;
/**
* Same as {@link #getBeatAngle}, only that additionally a cache is provided that can allow the method to use
* cached wind and leg type values.
*/
Bearing getBeatAngle(TimePoint at, WindLegTypeAndLegBearingCache cache) throws NoWindException;
/**
* Like {@link #getAverageVelocityMadeGood(TimePoint)}, only with an additional cache argument that allows the method to
* use already computed values for wind and leg type, potentially also updating the cache as it goes.
*/
Speed getAverageVelocityMadeGood(TimePoint timePoint, WindLegTypeAndLegBearingCache cache) throws NoWindException;
}
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.TimingConstants;
@@ -381,6 +382,17 @@ public interface TrackedRace extends Serializable {
*/
Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException;
/**
* Determines whether the <code>competitor</code> is sailing on port or starboard tack at the <code>timePoint</code>
* requested.
* <p>
* This method outperforms {@link #getTack(Competitor, TimePoint)}, based on being passed an already calculated wind
* for the given time and competitor position as well as the competitors speed and course over ground.
* <p>
* This method will acquire the read lock for the competitor's track.
*/
Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint);
TrackedRegatta getTrackedRegatta();
/**
@@ -506,12 +518,26 @@ public interface TrackedRace extends Serializable {
Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis)
throws NoWindException;
/**
* Same as {@link #getAverageAbsoluteCrossTrackError(Competitor, TimePoint, boolean)}, only that a cache for leg type,
* wind on leg and leg bearing is provided.
*/
Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalyses,
WindLegTypeAndLegBearingCache cache) throws NoWindException;
Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly,
boolean waitForLatestAnalyses) throws NoWindException;
Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis)
throws NoWindException;
/**
* Same as {@link #getAverageSignedCrossTrackError(Competitor, TimePoint, boolean)}, only that a cache for leg type,
* wind direction and leg bearing is provided.
*/
Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalyses,
WindLegTypeAndLegBearingCache cache) throws NoWindException;
Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly,
boolean waitForLatestAnalysis) throws NoWindException;
@@ -674,4 +700,5 @@ public interface TrackedRace extends Serializable {
SpeedWithConfidence<TimePoint> getAverageWindSpeedWithConfidence(long resolutionInMillis);
GPSFixStore getGPSFixStore();
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.TimePoint;
/**
* When comprehensive calculations are prone to query wind several times for the same competitor in the same race for
* the same time point, a cache of this type can be used to eliminate repeated evaluation of common sub-expressions that
* are expensive to calculate.
*
* @author Axel Uhl (D043530)
*
*/
public interface WindCache {
/**
* Estimates the <code>competitor</code>'s position at <code>timePoint</code> and determines the wind at that position and
* at that time.
*/
Wind getWind(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint);
}
@@ -0,0 +1,4 @@
package com.sap.sailing.domain.tracking;
public interface WindLegTypeAndLegBearingCache extends WindCache, LegTypeCache, LegBearingCache {
}
@@ -41,13 +41,17 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
}
public void addTo(RaceLog raceLog) {
raceLog.addListener(this);
trackedRace.invalidateStartTime();
trackedRace.invalidateEndTime();
courseDesignFinder = new LastPublishedCourseDesignFinder(raceLog);
startTimeFinder = new StartTimeFinder(raceLog);
initializeWindTrack(raceLog);
analyze();
if (raceLog == null) {
logger.severe("Trying to add "+this+" as listener to a null race log for tracked race "+trackedRace.getRace());
} else {
raceLog.addListener(this);
trackedRace.invalidateStartTime();
trackedRace.invalidateEndTime();
courseDesignFinder = new LastPublishedCourseDesignFinder(raceLog);
startTimeFinder = new StartTimeFinder(raceLog);
initializeWindTrack(raceLog);
analyze();
}
}
/**
@@ -10,6 +10,7 @@ import java.util.NavigableSet;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.util.impl.ArrayListNavigableSet;
@@ -262,9 +263,14 @@ public class MaxSpeedCache<ItemType, FixType extends GPSFix> implements GPSTrack
if (fix.getTimePoint().after(to)) {
break;
}
Speed averagedSpeedAtFixTime = track.getEstimatedSpeed(fix.getTimePoint());
if (averagedSpeedAtFixTime != null && averagedSpeedAtFixTime.compareTo(max) > 0) {
max = averagedSpeedAtFixTime;
Speed speedAtFixTime = null;
if (fix instanceof GPSFixMoving) {
speedAtFixTime = ((GPSFixMoving)fix).getSpeed();
} else {
speedAtFixTime = track.getEstimatedSpeed(fix.getTimePoint());
}
if (speedAtFixTime != null && speedAtFixTime.compareTo(max) > 0) {
max = speedAtFixTime;
maxSpeedFix = fix;
}
}
@@ -0,0 +1,36 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
/**
* This trivial "cache" implementation doesn't cache and may be used as a default for those cases where only few
* calculations are to be done and creating the caching structure would cost spend cycles than simply doing the
* calculation.
*
* @author Axel Uhl (D043530)
*
*/
public class NoCachingWindLegTypeAndLegBearingCache implements WindLegTypeAndLegBearingCache {
@Override
public Wind getWind(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint) {
return trackedRace.getWind(trackedRace.getTrack(competitor).getEstimatedPosition(timePoint, false), timePoint);
}
@Override
public LegType getLegType(TrackedLeg trackedLeg, TimePoint timePoint) throws NoWindException {
return trackedLeg.getLegType(timePoint);
}
@Override
public Bearing getLegBearing(TrackedLeg trackedLeg, TimePoint timePoint) {
return trackedLeg.getLegBearing(timePoint);
}
}
@@ -31,6 +31,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sse.common.Util;
public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
@@ -344,21 +345,25 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
@Override
public Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
LegType legType = getLegType(at);
return getWindwardDistance(pos1, pos2, at, new NoCachingWindLegTypeAndLegBearingCache());
}
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindLegTypeAndLegBearingCache cache) throws NoWindException {
LegType legType = cache.getLegType(this, at);
if (legType != LegType.REACHING) { // upwind or downwind
final Position effectivePosition = getEffectiveWindPosition(
pos1.translateGreatCircle(pos1.getBearingGreatCircle(pos2), pos1.getDistance(pos2).scale(0.5)), at,
windPositionMode);
Wind wind = getTrackedRace().getWind(effectivePosition, at);
if (wind == null) {
return pos2.alongTrackDistance(pos1, getLegBearing(at));
return pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
} else {
Position projectionToLineThroughPos2 = pos1.projectToLineThrough(pos2, wind.getBearing());
return pos2.alongTrackDistance(projectionToLineThroughPos2, legType == LegType.UPWIND ? wind.getFrom() : wind.getBearing());
}
} else {
// reaching leg, return distance projected onto leg's bearing
return pos2.alongTrackDistance(pos1, getLegBearing(at));
return pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
}
}
@@ -12,6 +12,7 @@ import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Duration;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
@@ -29,6 +30,7 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.util.impl.ArrayListNavigableSet;
import com.sap.sse.common.Util;
@@ -216,18 +218,23 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
*
* @throws NoWindException in case the wind direction is not known
*/
private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException {
SpeedWithBearing result = null;
if (speed != null) {
Bearing projectToBearing;
if (getTrackedLeg().isUpOrDownwindLeg(at)) {
Wind wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at, windPositionMode);
if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) {
final Wind wind;
if (windPositionMode == WindPositionMode.EXACT) {
wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
} else {
wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at, windPositionMode);
}
if (wind == null) {
throw new NoWindException("Need at least wind direction to determine windward speed");
}
projectToBearing = wind.getBearing();
} else {
projectToBearing = getTrackedLeg().getLegBearing(at);
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
}
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
if (cos < 0) {
@@ -249,10 +256,6 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
* is passed as position to {@link TrackedRace#getWind(Position, TimePoint)} which yields a general average
* of the various wind sources available for the race, independent of any position.
*/
Wind getWind(Position p, TimePoint at, WindPositionMode windPositionMode) {
return getTrackedRace().getWind(getTrackedLeg().getEffectiveWindPosition(p, at, windPositionMode), at);
}
@Override
public int getRank(TimePoint timePoint) {
int result = 0;
@@ -265,6 +268,11 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
@Override
public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException {
return getAverageVelocityMadeGood(timePoint, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Speed getAverageVelocityMadeGood(TimePoint timePoint, WindLegTypeAndLegBearingCache cache) throws NoWindException {
Speed result = null;
MarkPassing start = getMarkPassingForLegStart();
if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) {
@@ -429,35 +437,46 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
@Override
public Double getGapToLeaderInSeconds(TimePoint timePoint, final Competitor leaderInLegAtTimePoint, WindPositionMode windPositionMode)
throws NoWindException {
return getGapToLeaderInSeconds(timePoint, leaderInLegAtTimePoint, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Double getGapToLeaderInSeconds(TimePoint timePoint, final Competitor leaderInLegAtTimePoint, WindLegTypeAndLegBearingCache cache)
throws NoWindException {
return getGapToLeaderInSeconds(timePoint, new LeaderGetter() {
@Override
public Competitor getLeader() {
return leaderInLegAtTimePoint;
}
}, windPositionMode);
}, windPositionMode, cache);
}
private static interface LeaderGetter {
Competitor getLeader();
}
@Override
public Double getGapToLeaderInSeconds(final TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException {
return getGapToLeaderInSeconds(timePoint, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Double getGapToLeaderInSeconds(final TimePoint timePoint, WindLegTypeAndLegBearingCache cache) throws NoWindException {
return getGapToLeaderInSeconds(timePoint, new LeaderGetter() {
@Override
public Competitor getLeader() {
return getTrackedLeg().getLeader(hasFinishedLeg(timePoint) ? getFinishTime() : timePoint);
}
}, windPositionMode);
}, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
}
private Double getGapToLeaderInSeconds(TimePoint timePoint, LeaderGetter leaderGetter, WindPositionMode windPositionMode) throws NoWindException {
private Double getGapToLeaderInSeconds(TimePoint timePoint, LeaderGetter leaderGetter, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException {
// If the leader already completed this leg, compute the estimated arrival time at the
// end of this leg; if this leg's competitor also already finished the leg, return the
// difference between this competitor's leg completion time point and the leader's completion
// time point; else, calculate the windward distance to the leader and divide by
// the windward speed
Speed windwardSpeed = getWindwardSpeed(getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(timePoint), timePoint, windPositionMode);
Speed windwardSpeed = getWindwardSpeed(getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(timePoint), timePoint, windPositionMode, cache);
Double result = null;
// Has our competitor started the leg already? If not, we won't be able to compute a gap
if (hasStartedLeg(timePoint)) {
@@ -563,7 +582,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
timePoint = at;
}
SpeedWithBearing speedOverGround = getSpeedOverGround(timePoint);
return speedOverGround == null ? null : getWindwardSpeed(speedOverGround, timePoint, windPositionMode);
return speedOverGround == null ? null : getWindwardSpeed(speedOverGround, timePoint, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
} else {
return null;
}
@@ -775,24 +794,26 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
@Override
public Bearing getBeatAngle(TimePoint at) throws NoWindException {
return getBeatAngle(at, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Bearing getBeatAngle(TimePoint at, WindLegTypeAndLegBearingCache cache) throws NoWindException {
Bearing beatAngle = null;
Bearing projectToBearing;
if (getTrackedLeg().isUpOrDownwindLeg(at)) {
Wind wind = getWind(getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false), at, WindPositionMode.EXACT);
if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) {
Wind wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
if (wind == null) {
throw new NoWindException("Need at least wind direction to determine windward speed");
}
projectToBearing = wind.getBearing();
} else {
projectToBearing = getTrackedLeg().getLegBearing(at);
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
}
SpeedWithBearing speed = getSpeedOverGround(at);
if (speed != null) {
beatAngle = projectToBearing.getDifferenceTo(speed.getBearing());
}
return beatAngle;
}
}
@@ -17,6 +17,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableSet;
import java.util.Set;
import java.util.Timer;
@@ -102,6 +103,7 @@ import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
@@ -182,7 +184,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
private long updateCount;
private transient Map<TimePoint, List<Competitor>> competitorRankings;
/**
* Limit for the cache size in {@link #competitorRankings} and respectively in {@link #competitorRankingsLocks}.
*/
private static final int MAX_COMPETITOR_RANKINGS_CACHE_SIZE = 10;
private transient LinkedHashMap<TimePoint, List<Competitor>> competitorRankings;
/**
* The locks managed here correspond with the {@link #competitorRankings} structure. When
@@ -190,7 +197,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* time point. Readers use the read lock. Checking / entering a lock into this map uses <code>synchronized</code> on
* the map itself.
*/
private transient Map<TimePoint, NamedReentrantReadWriteLock> competitorRankingsLocks;
private transient LinkedHashMap<TimePoint, NamedReentrantReadWriteLock> competitorRankingsLocks;
/**
* legs appear in the order in which they appear in the race's course
@@ -378,8 +385,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
WindSource trackBasedWindSource = new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION);
windTracks.put(trackBasedWindSource,
getOrCreateWindTrack(trackBasedWindSource, delayForWindEstimationCacheInvalidation));
competitorRankings = new HashMap<TimePoint, List<Competitor>>();
competitorRankingsLocks = new HashMap<TimePoint, NamedReentrantReadWriteLock>();
competitorRankings = createCompetitorRankingsCache();
competitorRankingsLocks = createCompetitorRankingsLockMap();
// now wait until wind loading has at least started; then we know that the serialization lock is safely held by the loader
try {
waitUntilLoadingFromWindStoreComplete();
@@ -388,6 +395,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
}
private LinkedHashMap<TimePoint, NamedReentrantReadWriteLock> createCompetitorRankingsLockMap() {
return new LinkedHashMap<TimePoint, NamedReentrantReadWriteLock>() {
private static final long serialVersionUID = 6298801656693955386L;
@Override
protected boolean removeEldestEntry(Entry<TimePoint, NamedReentrantReadWriteLock> eldest) {
return size() > MAX_COMPETITOR_RANKINGS_CACHE_SIZE;
}
};
}
private LinkedHashMap<TimePoint, List<Competitor>> createCompetitorRankingsCache() {
return new LinkedHashMap<TimePoint, List<Competitor>>() {
private static final long serialVersionUID = -6044369612727021861L;
@Override
protected boolean removeEldestEntry(Entry<TimePoint, List<Competitor>> eldest) {
return size() > MAX_COMPETITOR_RANKINGS_CACHE_SIZE;
}
};
}
/**
* Assuming that the tracks were loaded from the persistent store, this method updates
* the time stamps that frame the data held by this tracked race. See {@link #timePointOfLastEvent}, {@link #timePointOfNewestEvent}
@@ -430,8 +457,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
markPassingsTimes = new ArrayList<com.sap.sse.common.Util.Pair<Waypoint, com.sap.sse.common.Util.Pair<TimePoint, TimePoint>>>();
cacheInvalidationTimerLock = new Object();
windStore = EmptyWindStore.INSTANCE;
competitorRankings = new HashMap<TimePoint, List<Competitor>>();
competitorRankingsLocks = new HashMap<TimePoint, NamedReentrantReadWriteLock>();
competitorRankings = createCompetitorRankingsCache();
competitorRankingsLocks = createCompetitorRankingsLockMap();
directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
crossTrackErrorCache = new CrossTrackErrorCache(this);
maneuverCache = createManeuverCache();
@@ -999,49 +1026,45 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
}
List<Competitor> rankedCompetitors;
final boolean lockForWrite;
synchronized (competitorRankings) {
rankedCompetitors = competitorRankings.get(timePoint);
if (rankedCompetitors == null) {
lockForWrite = true;
} else {
lockForWrite = false;
}
}
if (lockForWrite) {
if (rankedCompetitors == null) {
LockUtil.lockForWrite(readWriteLock);
} else {
LockUtil.lockForRead(readWriteLock);
}
try {
if (rankedCompetitors == null) {
rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the write
// lock after updating the cache
try {
if (rankedCompetitors == null) {
RaceRankComparator comparator = new RaceRankComparator(this, timePoint);
rankedCompetitors = new ArrayList<Competitor>();
for (Competitor c : getRace().getCompetitors()) {
rankedCompetitors.add(c);
}
Collections.sort(rankedCompetitors, comparator);
synchronized (competitorRankings) {
competitorRankings.put(timePoint, rankedCompetitors);
rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the
// write
// lock after updating the cache
if (rankedCompetitors == null) {
RaceRankComparator comparator = new RaceRankComparator(this, timePoint);
rankedCompetitors = new ArrayList<Competitor>();
for (Competitor c : getRace().getCompetitors()) {
rankedCompetitors.add(c);
}
Collections.sort(rankedCompetitors, comparator);
synchronized (competitorRankings) {
competitorRankings.put(timePoint, rankedCompetitors);
}
}
}
}
return rankedCompetitors;
} finally {
if (lockForWrite) {
} finally {
LockUtil.unlockAfterWrite(readWriteLock);
} else {
LockUtil.unlockAfterRead(readWriteLock);
}
}
return rankedCompetitors;
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis)
throws NoWindException {
return getAverageAbsoluteCrossTrackError(competitor, timePoint, waitForLatestAnalysis, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis,
WindLegTypeAndLegBearingCache cache)
throws NoWindException {
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
TimePoint from = null;
lockForRead(markPassings);
@@ -1064,6 +1087,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis)
throws NoWindException {
return getAverageSignedCrossTrackError(competitor, timePoint, waitForLatestAnalysis, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) throws NoWindException {
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
TimePoint from = null;
lockForRead(markPassings);
@@ -1076,7 +1105,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
Distance result;
if (from != null) {
result = getAverageSignedCrossTrackError(competitor, from, timePoint, /* upwindOnly */true, waitForLatestAnalysis);
result = getAverageSignedCrossTrackError(competitor, from, timePoint, /* upwindOnly */true, waitForLatestAnalyses);
} else {
result = null;
}
@@ -1895,6 +1924,42 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return result;
}
/**
* This is probably best explained by example. If the wind bearing is from port to starboard, the situation looks
* like this:
*
* <pre>
* ^
* Wind | Boat
* -----------> |
* |
*
* </pre>
*
* In this case, the boat gets the wind from port, so the result has to be {@link Tack#PORT}. The angle between the
* boat's heading (which we can only approximate by the boat's course over ground) and the wind bearing in this case
* is 90 degrees. <code>wind.{@link Bearing#getDifferenceTo(Bearing) getDifferenceTo}(boat)</code> in this case will
* return a bearing representing -90 degrees.
* <p>
*
* If the wind is blowing the other way, the angle returned by {@link Bearing#getDifferenceTo(Bearing)} will
* correspond to +90 degrees. In other words, a negative angle means starboard tack, a positive angle represents
* port tack.
* <p>
*
* For the unlikely case of 0 degrees difference, {@link Tack#STARBOARD} will result.
*
* @return <code>null</code> in case the boat's bearing cannot be determined for <code>timePoint</code>
*/
@Override
public Tack getTack(SpeedWithBearing estimatedSpeed, Wind wind, TimePoint timePoint) {
Tack result = null;
if (estimatedSpeed != null) {
result = getTack(wind, estimatedSpeed.getBearing());
}
return result;
}
/**
* Based on the wind direction at <code>timePoint</code> and at position <code>where</code>, compares the
* <code>boatBearing</code> to the wind's bearing at that time and place and determined the tack.
@@ -1905,15 +1970,22 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
private Tack getTack(Position where, TimePoint timePoint, Bearing boatBearing) throws NoWindException {
final Wind wind = getWind(where, timePoint);
Tack result;
if (wind == null) {
throw new NoWindException("Can't determine wind direction in position " + where + " at " + timePoint
+ ", therefore cannot determine tack");
}
return getTack(wind, boatBearing);
}
/**
* Based on the wind, compares the <code>boatBearing</code> to the wind's bearing at
* that time and place and determined the tack.
*/
private Tack getTack(Wind wind, Bearing boatBearing) {
Bearing windBearing = wind.getBearing();
Bearing difference = windBearing.getDifferenceTo(boatBearing);
result = difference.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD;
return result;
return difference.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD;
}
@Override
@@ -178,7 +178,8 @@ public class GwtTestCaseColumnToggling extends GWTTestCase {
private void getLeaderboardByNameAndDeleteColumn(){
ArrayList<String> al = new ArrayList<String>();
al.add(COLUMN1_NAME);
service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* addOverallDetails */ false, /* previousLeaderboardId */ null, new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* addOverallDetails */ false, /* previousLeaderboardId */ null,
/* fillUncorrectedNetPoints */ false, new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
@Override
public void onFailure(Throwable caught) {
fail("Failed to get leaderboard.");
@@ -27,6 +27,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
@@ -53,6 +54,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
@@ -540,6 +542,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
return null;
}
@Override
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public Wind getDirectionFromStartToNextMark(TimePoint at) {
@@ -680,6 +688,13 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource) {
// TODO Auto-generated method stub
@@ -959,6 +974,13 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to,
boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException {
@@ -98,7 +98,8 @@ public class TestColumnSwapping {
sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null);
lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null,
/* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null);
} catch (Exception e) {
// e.printStackTrace();
fail(e.getLocalizedMessage());
@@ -139,7 +140,8 @@ public class TestColumnSwapping {
public void testColumnSwappingFabian() {
service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
try {
leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ true, /* previous leaderboard ID */ null).getLeaderboardDTO(/* previousVersion */ null);
leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */
true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
@@ -1,4 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5SYWNlQm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuQWRtaW5Db25zb2xlPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
@@ -350,6 +350,13 @@ body ::-webkit-scrollbar-track {
margin: 0 14px 0 0;
}
.timePanel-timeslider-play {
float:left;
padding: 10px;
height: 24px;
margin: 5px 14px 5px 0;
}
.timePanel-controls-playSpeed {
vertical-align: middle;
line-height: 25px;
@@ -37,10 +37,12 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private final Timer timerToAdjustOffetIn;
private final boolean fillNetPointsUncorrected;
public GetLeaderboardByNameAction(SailingServiceAsync sailingService, String leaderboardName, Date date,
final Collection<String> namesOfRacesForWhichToLoadLegDetails, boolean addOverallDetails,
LeaderboardDTO previousLeaderboard, Timer timerToAdjustOffsetIn, ErrorReporter errorReporter, StringMessages stringMessages) {
LeaderboardDTO previousLeaderboard, boolean fillNetPointsUncorrected, Timer timerToAdjustOffsetIn,
ErrorReporter errorReporter, StringMessages stringMessages) {
this.sailingService = sailingService;
this.errorReporter = errorReporter;
this.stringMessages = stringMessages;
@@ -50,13 +52,15 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
this.addOverallDetails = addOverallDetails;
this.previousLeaderboard = previousLeaderboard;
this.timerToAdjustOffetIn = timerToAdjustOffsetIn;
this.fillNetPointsUncorrected = fillNetPointsUncorrected;
}
@Override
public void execute(final AsyncCallback<LeaderboardDTO> callback) {
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, addOverallDetails,
previousLeaderboard==null?null:previousLeaderboard.getId(), new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
previousLeaderboard==null?null:previousLeaderboard.getId(), fillNetPointsUncorrected,
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -0,0 +1,25 @@
package com.sap.sailing.gwt.ui.actions;
import java.util.Collection;
import java.util.List;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.gwt.client.async.AsyncAction;
public class GetRaceTimesInfoAction implements AsyncAction<List<RaceTimesInfoDTO>> {
private final SailingServiceAsync sailingService;
private final Collection<RegattaAndRaceIdentifier> raceIdentifiers;
public GetRaceTimesInfoAction(SailingServiceAsync sailingService, Collection<RegattaAndRaceIdentifier> raceIdentifiers) {
this.sailingService = sailingService;
this.raceIdentifiers = raceIdentifiers;
}
@Override
public void execute(AsyncCallback<List<RaceTimesInfoDTO>> callback) {
sailingService.getRaceTimesInfos(raceIdentifiers, callback);
}
}
@@ -103,7 +103,7 @@ public class AddDeviceMappingDialog extends DataEntryDialog<DeviceMappingDTO> {
final SingleSelectionModel<MarkDTO> markSelectionModel = new SingleSelectionModel<MarkDTO>();
competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter,
new SingleSelectionModel<CompetitorDTO>());
new SingleSelectionModel<CompetitorDTO>(), true);
markTable = new MarkTableWrapper<SingleSelectionModel<MarkDTO>>(
markSelectionModel, sailingService, stringMessages, errorReporter);
@@ -200,8 +200,8 @@ public class AddDeviceMappingDialog extends DataEntryDialog<DeviceMappingDTO> {
tablesPanel.add(marksPanel);
tablesPanel.add(competitorsPanel);
marksPanel.setContentWidget(markTable.getTable());
competitorsPanel.setContentWidget(competitorTable.getTable());
marksPanel.setContentWidget(markTable.asWidget());
competitorsPanel.setContentWidget(competitorTable.asWidget());
return panel;
}
@@ -51,7 +51,7 @@ public class CompetitorPanel extends SimplePanel {
this.errorReporter = errorReporter;
this.leaderboardName = leaderboardName;
this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter,
new MultiSelectionModel<CompetitorDTO>());
new MultiSelectionModel<CompetitorDTO>(), false);
this.competitorSelectionModel = competitorTable.getSelectionModel();
VerticalPanel mainPanel = new VerticalPanel();
this.setWidget(mainPanel);
@@ -30,8 +30,8 @@ public class CompetitorTableWrapper<S extends SelectionModel<CompetitorDTO>> ext
private final LabeledAbstractFilterablePanel<CompetitorDTO> filterField;
public CompetitorTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,ErrorReporter errorReporter,
S selectionModel) {
super(sailingService, stringMessages, errorReporter, selectionModel);
S selectionModel, boolean enablePager) {
super(sailingService, stringMessages, errorReporter, selectionModel, enablePager);
ListHandler<CompetitorDTO> competitorColumnListHandler = new ListHandler<CompetitorDTO>(dataProvider.getList());
@@ -130,14 +130,13 @@ public class CompetitorTableWrapper<S extends SelectionModel<CompetitorDTO>> ext
}
};
mainPanel.add(filterField);
mainPanel.insert(filterField, 0);
table.addColumnSortHandler(competitorColumnListHandler);
table.addColumn(sailIdColumn, stringMessages.sailNumber());
table.addColumn(competitorNameColumn, stringMessages.name());
table.addColumn(boatClassColumn, stringMessages.boatClass());
table.addColumn(displayColorColumn, stringMessages.color());
table.addColumn(competitorIdColumn, stringMessages.id());
mainPanel.add(table);
}
public Iterable<CompetitorDTO> getAllCompetitors() {
@@ -12,9 +12,7 @@ import com.sap.sailing.gwt.ui.shared.MarkDTO;
public class ControlPointTableWrapper<S extends SelectionModel<ControlPointDTO>> extends TableWrapper<ControlPointDTO, S> {
public ControlPointTableWrapper(S selectionModel, SailingServiceAsync sailingService, final StringMessages stringMessages,
ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, selectionModel);
mainPanel.add(table);
super(sailingService, stringMessages, errorReporter, selectionModel, true);
TextColumn<ControlPointDTO> nameColumn = new TextColumn<ControlPointDTO>() {
@Override
@@ -11,7 +11,7 @@ public class DeviceIdentifierTableWrapper extends TableWrapper<DeviceIdentifierD
public DeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, new MultiSelectionModel<DeviceIdentifierDTO>());
super(sailingService, stringMessages, errorReporter, new MultiSelectionModel<DeviceIdentifierDTO>(), false);
TextColumn<DeviceIdentifierDTO> typeColumn = new TextColumn<DeviceIdentifierDTO>() {
@Override
@@ -10,8 +10,6 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
@@ -25,8 +23,6 @@ public class DeviceMappingTableWrapper extends TableWrapper<DeviceMappingDTO, Si
static interface FilterChangedHandler {
void onFilterChanged(List<DeviceMappingDTO> filteredList);
}
private final Panel mainPanel;
private List<DeviceMappingDTO> allMappings = new ArrayList<>();
private final CheckBox showPingMappingsCb;
@@ -34,9 +30,8 @@ public class DeviceMappingTableWrapper extends TableWrapper<DeviceMappingDTO, Si
public DeviceMappingTableWrapper(SailingServiceAsync sailingService, final StringMessages stringMessages,
ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, new SingleSelectionModel<DeviceMappingDTO>());
super(sailingService, stringMessages, errorReporter, new SingleSelectionModel<DeviceMappingDTO>(), true);
mainPanel = new VerticalPanel();
showPingMappingsCb = new CheckBox(stringMessages.showPingMarkMappings());
showPingMappingsCb.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
@@ -44,8 +39,7 @@ public class DeviceMappingTableWrapper extends TableWrapper<DeviceMappingDTO, Si
filterPingMappings();
}
});
mainPanel.add(showPingMappingsCb);
mainPanel.add(table);
mainPanel.insert(showPingMappingsCb, 0);
table.setWidth("1000px", true);
table.addStyleName("wrap-cols");
@@ -145,7 +139,6 @@ public class DeviceMappingTableWrapper extends TableWrapper<DeviceMappingDTO, Si
table.addColumn(toCol, stringMessages.to());
table.addColumnSortHandler(listHandler);
mainPanel.add(table);
}
private void notifyFilterHandlers() {
@@ -61,7 +61,7 @@ public class ImportFixesAndAddMappingsDialog extends DataEntryDialog<Collection<
});
competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter,
new SingleSelectionModel<CompetitorDTO>());
new SingleSelectionModel<CompetitorDTO>(), true);
markTable = new MarkTableWrapper<SingleSelectionModel<MarkDTO>>(
new SingleSelectionModel<MarkDTO>(), sailingService, stringMessages, errorReporter);
@@ -181,8 +181,8 @@ public class ImportFixesAndAddMappingsDialog extends DataEntryDialog<Collection<
tablesPanel.add(marksPanel);
tablesPanel.add(competitorsPanel);
marksPanel.setContentWidget(markTable.getTable());
competitorsPanel.setContentWidget(competitorTable.getTable());
marksPanel.setContentWidget(markTable.asWidget());
competitorsPanel.setContentWidget(competitorTable.asWidget());
return panel;
}
@@ -21,9 +21,7 @@ import com.sap.sailing.gwt.ui.shared.MarkDTO;
public class MarkTableWrapper<S extends SelectionModel<MarkDTO>> extends TableWrapper<MarkDTO, S> {
public MarkTableWrapper(S selectionModel, SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, selectionModel);
mainPanel.add(table);
super(sailingService, stringMessages, errorReporter, selectionModel, true);
TextColumn<MarkDTO> markNameColumn = new TextColumn<MarkDTO>() {
@Override
@@ -82,9 +82,9 @@ public class RaceLogTrackingCompetitorRegistrationsDialog extends RaceLogTrackin
CaptionPanel allCompetitorsPanel = new CaptionPanel(stringMessages.competitorPool());
CaptionPanel registeredCompetitorsPanel = new CaptionPanel(stringMessages.registeredCompetitors());
allCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter,
new MultiSelectionModel<CompetitorDTO>());
new MultiSelectionModel<CompetitorDTO>(), true);
registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter,
new MultiSelectionModel<CompetitorDTO>());
new MultiSelectionModel<CompetitorDTO>(), true);
allCompetitorsPanel.add(allCompetitorsTable);
registeredCompetitorsPanel.add(registeredCompetitorsTable);
VerticalPanel movePanel = new VerticalPanel();
@@ -61,6 +61,7 @@ public class RaceLogTrackingDeviceMappingsDialog extends RaceLogTrackingDialog {
super(sailingService, stringMessages, errorReporter, leaderboardName, raceColumnName, fleetName, false);
deviceMappingTable = new DeviceMappingTableWrapper(sailingService, stringMessages, errorReporter);
deviceMappingTable.addFilterChangedHandler(new FilterChangedHandler() {
@Override
public void onFilterChanged(List<DeviceMappingDTO> filteredList) {
@@ -25,7 +25,7 @@ extends TableWrapper<RaceColumnDTOAndFleetDTOWithNameBasedEquality, S> {
public RaceTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter, S selectionModel) {
super(sailingService, stringMessages, errorReporter, selectionModel);
super(sailingService, stringMessages, errorReporter, selectionModel, false);
Column<RaceColumnDTOAndFleetDTOWithNameBasedEquality, SafeHtml> raceNameColumn =
new Column<RaceColumnDTOAndFleetDTOWithNameBasedEquality, SafeHtml>(new AnchorCell()) {
@@ -47,7 +47,7 @@ public class SelectRacesDialog extends DataEntryDialog<Set<RaceColumnDTOAndFleet
@Override
protected Widget getAdditionalWidget() {
return racesTable.getTable();
return racesTable.asWidget();
}
@Override
@@ -0,0 +1,63 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class SetStartTimeReceivedDialog extends DataEntryDialog<Date> {
private final StringMessages stringMessages;
private BetterDateTimeBox timeBox;
public SetStartTimeReceivedDialog(StringMessages stringMessages, DataEntryDialog.DialogCallback<Date> callback) {
super(stringMessages.setStartTimeReceived(), stringMessages.setStartTimeReceivedDescription(), stringMessages.ok(), stringMessages.cancel(), new ReceivedStartTimeDialog(stringMessages), callback);
this.stringMessages = stringMessages;
}
@Override
protected Widget getAdditionalWidget() {
Grid content = new Grid(1, 2);
Label timeBoxLabel = new Label(stringMessages.startTime() + ":");
content.setWidget(0, 0, timeBoxLabel);
timeBox = new BetterDateTimeBox();
timeBox.addValueChangeHandler(new ValueChangeHandler<Date>() {
@Override
public void onValueChange(ValueChangeEvent<Date> event) {
validate();
}
});
content.setWidget(0, 1, timeBox);
return content;
}
@Override
protected Date getResult() {
return timeBox.getValue();
}
private static class ReceivedStartTimeDialog implements Validator<Date> {
private StringMessages stringMessages;
public ReceivedStartTimeDialog(StringMessages stringMessages) {
this.stringMessages = stringMessages;
}
@Override
public String getErrorMessage(Date valueToValidate) {
return valueToValidate == null ? stringMessages.pleaseEnterAValue() : null;
}
}
}
@@ -4,8 +4,8 @@ import java.util.Collection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
@@ -21,7 +21,7 @@ public abstract class TableWrapper<T, S extends SelectionModel<T>> implements Is
protected final CellTable<T> table;
protected final S selectionModel;
protected final ListDataProvider<T> dataProvider;
protected Panel mainPanel;
protected VerticalPanel mainPanel;
protected final SailingServiceAsync sailingService;
protected final ErrorReporter errorReporter;
@@ -33,7 +33,7 @@ public abstract class TableWrapper<T, S extends SelectionModel<T>> implements Is
}
public TableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter,
S selectionModel) {
S selectionModel, boolean enablePager) {
this.sailingService = sailingService;
this.errorReporter = errorReporter;
this.selectionModel = selectionModel;
@@ -43,6 +43,13 @@ public abstract class TableWrapper<T, S extends SelectionModel<T>> implements Is
dataProvider.addDataDisplay(table);
table.setSelectionModel(selectionModel);
mainPanel.add(table);
if (enablePager) {
table.setPageSize(8);
SimplePager pager = new SimplePager();
pager.setDisplay(table);
mainPanel.add(pager);
}
}
public CellTable<T> getTable() {
@@ -533,7 +533,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}
}
} else {
if (!tracTracBoatClassName.equalsIgnoreCase(selectedRegatta.boatClass.getName())) {
if (!tracTracBoatClassName.equals(selectedRegatta.boatClass.getName())) {
return false;
}
}
@@ -13,7 +13,7 @@ SingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, new SingleSelectionModel<TrackFileImportDeviceIdentifierDTO>());
super(sailingService, stringMessages, errorReporter, new SingleSelectionModel<TrackFileImportDeviceIdentifierDTO>(), true);
TextColumn<TrackFileImportDeviceIdentifierDTO> uuidColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
@@ -2,15 +2,22 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.i18n.client.TimeZone;
import com.google.gwt.text.client.DateTimeFormatRenderer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
private final DateTimeFormatRenderer dateFormatter = new DateTimeFormatRenderer(
@@ -25,6 +32,37 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
public TrackedRacesManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, StringMessages stringConstants) {
super(sailingService, errorReporter, regattaRefresher, stringConstants);
HorizontalPanel controlsPanel = new HorizontalPanel();
Button setStartTimeButton = new Button(stringMessages.setStartTimeReceived(), new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
new SetStartTimeReceivedDialog(stringMessages, new DialogCallback<Date>() {
@Override
public void ok(Date newStartTimeReceived) {
sailingService.setStartTimeReceivedForRace(selectedRaceDTO.getRaceIdentifier(), newStartTimeReceived, new AsyncCallback<RaceDTO>() {
@Override
public void onFailure(Throwable caught) {
TrackedRacesManagementPanel.this.errorReporter.reportError("Error setting the received start time: " + caught.getMessage());
}
@Override
public void onSuccess(RaceDTO result) {
if (result != null) {
selectedRaceDTO = result;
refreshSelectedRaceData();
TrackedRacesManagementPanel.this.regattaRefresher.fillRegattas();
}
}
});
}
@Override
public void cancel() { }
}).show();
}
});
controlsPanel.add(setStartTimeButton);
selectedRaceContentPanel.add(controlsPanel);
raceDataGrid = new Grid(6,2);
this.selectedRaceContentPanel.add(raceDataGrid);
@@ -13,9 +13,7 @@ import com.sap.sailing.gwt.ui.shared.WaypointDTO;
public class WaypointTableWrapper<S extends SelectionModel<WaypointDTO>> extends TableWrapper<WaypointDTO, S> {
public WaypointTableWrapper(S selectionModel, SailingServiceAsync sailingService, final StringMessages stringMessages,
ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, selectionModel);
mainPanel.add(table);
super(sailingService, stringMessages, errorReporter, selectionModel, true);
TextColumn<WaypointDTO> nameColumn = new TextColumn<WaypointDTO>() {
@Override
@@ -17,26 +17,29 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.actions.GetRaceTimesInfoAction;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
public class RaceTimesInfoProvider {
private final SailingServiceAsync sailingService;
private final AsyncActionsExecutor asyncActionsExecutor;
private final ErrorReporter errorReporter;
private final Set<RegattaAndRaceIdentifier> raceIdentifiers;
private long requestIntervalInMillis;
private final HashMap<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos;
private final Set<RaceTimesInfoProviderListener> listeners;
/**
* The <code>raceIdentifiers</code> has to be non-<code>null</code>, but can be empty.
*/
public RaceTimesInfoProvider(SailingServiceAsync sailingService, ErrorReporter errorReporter,
public RaceTimesInfoProvider(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter,
Collection<RegattaAndRaceIdentifier> raceIdentifiers, long requestIntervalInMillis) {
this.sailingService = sailingService;
this.asyncActionsExecutor = asyncActionsExecutor;
this.errorReporter = errorReporter;
this.raceIdentifiers = new HashSet<RegattaAndRaceIdentifier>(raceIdentifiers);
this.requestIntervalInMillis = requestIntervalInMillis;
@@ -97,7 +100,8 @@ public class RaceTimesInfoProvider {
private void readTimesInfos() {
if (!raceIdentifiers.isEmpty()) {
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
sailingService.getRaceTimesInfos(raceIdentifiers, new AsyncCallback<List<RaceTimesInfoDTO>>() {
GetRaceTimesInfoAction getRaceTimesInfoAction = new GetRaceTimesInfoAction(sailingService, raceIdentifiers);
asyncActionsExecutor.execute(getRaceTimesInfoAction, new AsyncCallback<List<RaceTimesInfoDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to obtain the race time infos: " + caught.getMessage(),
@@ -19,6 +19,7 @@ import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.PolarSheetGenerationResponse;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType;
@@ -149,7 +150,7 @@ public interface SailingService extends RemoteService {
IncrementalOrFullLeaderboardDTO getLeaderboardByName(String leaderboardName, Date date,
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
String previousLeaderboardId) throws Exception;
String previousLeaderboardId, boolean fillNetPointsUncorrected) throws Exception;
List<StrippedLeaderboardDTO> getLeaderboards();
@@ -511,5 +512,10 @@ public interface SailingService extends RemoteService {
* by {@link #getSearchServerNames()} which corresponds to a name of a
* {@link RemoteSailingServerReference}, to search a remote server.
*/
Iterable<LeaderboardSearchResultDTO> search(String serverNameOrNullForMain, KeywordQuery query) throws Exception;
Iterable<LeaderboardSearchResultDTO> search(String serverNameOrNullForMain, KeywordQuery query) throws Exception;
/**
* @return The RaceDTO of the modified race or <code>null</code>, if the given newStartTimeReceived was null.
*/
RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived);
}
@@ -188,7 +188,8 @@ public interface SailingServiceAsync {
void getLeaderboardByName(String leaderboardName, Date date,
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
boolean addOverallDetails, String previousLeaderboardId, AsyncCallback<IncrementalOrFullLeaderboardDTO> callback);
boolean addOverallDetails, String previousLeaderboardId, boolean fillNetPointsUncorrected,
AsyncCallback<IncrementalOrFullLeaderboardDTO> callback);
void getLeaderboardNames(AsyncCallback<List<String>> callback);
@@ -633,4 +634,6 @@ public interface SailingServiceAsync {
*/
void search(String serverNameOrNullForMain, KeywordQuery query,
AsyncCallback<Iterable<LeaderboardSearchResultDTO>> callback);
void setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived, AsyncCallback<RaceDTO> callback);
}
@@ -1049,4 +1049,10 @@ public interface StringMessages extends Messages {
String showNumberOfRacesScored();
String racesScored();
String racesScoredTooltip();
String showUncorrectedNetPoints();
String setStartTimeReceived();
String setStartTimeReceivedDescription();
String lastScoreCorrectionsTime();
String lastScoreCorrectionsComment();
String setTimeToNow();
}
@@ -1035,3 +1035,9 @@ errorFetchingWindStreamletData=Error fetching wind streamlet data: {0}
showWindStreamletOverlay=Show wind streamlets overlay
racesScored=Races scored
racesScoredTooltip=Number of races the competitor has completed or gotten a score for in this leaderboard.
showUncorrectedNetPoints=Show uncorrected net points
setStartTimeReceived=Set start time received
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace that isn''t persistent. This means that the new value will be forgotten after the server has been restarted.
lastScoreCorrectionsTime=Last score correction time
lastScoreCorrectionsComment=Last score correction comment
setTimeToNow=Set time to ''now''
@@ -1028,4 +1028,11 @@ showWindStreamletOverlay=Wind als Streamlets überlagern
regattaNameMustNotContainSlashes=Regattaname darf keine Schrägstriche enthalten
showNumberOfRacesScored=Anzahl bepunkteter Rennen
racesScored=Bepunktete Rennen
racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat
racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat
showUncorrectedNetPoints=Unkorrigierte Netto-Punkte anzeigen
setStartTimeReceived=Setze die erhaltene Startzeit
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace, that isn''t persistent. This means, that the new value will be forgotten after the server has been restarted.
setStartTimeReceivedDescription=Dies setzt die startTimeReceived des selektierten TrackedRaces, welches nicht persistent ist. Das beudeutet, dass der neue Wert vergessen wird, sobald der Server neu gestartet wurde.
lastScoreCorrectionsTime=Letzter Zeitpunkt der Punktzahl-Korrektur
lastScoreCorrectionsComment=Letzter Kommentar der Punktzahl-Korrektur
setTimeToNow=Setze den Zeitpunkt auf ''Jetzt''
@@ -987,4 +987,5 @@ showWindStreamletOverlay=Show wind streamlets overlay
showNumberOfRacesScored=Показать количество завершенных гонок
racesScored=Завершенные гонки
racesScoredTooltip=Количество гонок завершенное участником в данной таблице лидеров
regattaNameMustNotContainSlashes=Regatta name must not contain slashes
regattaNameMustNotContainSlashes=Regatta name must not contain slashes
showUncorrectedNetPoints=Show uncorrected net points
@@ -71,6 +71,12 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
private final Button slowDownButton;
private final Button speedUpButton;
private final FlowPanel controlsPanel;
private final SimplePanel timePanelSlider;
private final FlowPanel timePanelSliderFlowWrapper;
private final FlowPanel playControlPanel;
private final FlowPanel timePanelInnerWrapper;
/**
* the minimum time the slider extends it's time when the end of the slider is reached
*/
@@ -99,14 +105,16 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
timer.addTimeListener(this);
timer.addPlayStateListener(this);
timeRangeProvider.addTimeRangeChangeListener(this);
FlowPanel timePanelInnerWrapper = new FlowPanel();
timePanelInnerWrapper = new FlowPanel();
timePanelInnerWrapper.setStyleName("timePanelInnerWrapper");
timePanelInnerWrapper.setSize("100%", "100%");
SimplePanel timePanelSlider = new SimplePanel();
timePanelSlider = new SimplePanel();
timePanelSliderFlowWrapper = new FlowPanel();
timePanelSlider.setStyleName("timePanelSlider");
timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX);
timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
timePanelSliderFlowWrapper.add(timePanelSlider);
playSpeedImg = resources.timesliderPlaySpeedIcon();
playPauseButton = new Button("");
@@ -141,16 +149,16 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
}
});
timePanelInnerWrapper.add(timePanelSlider);
timePanelInnerWrapper.add(timePanelSliderFlowWrapper);
timePanelSlider.add(timeSlider);
FlowPanel controlsPanel = new FlowPanel();
controlsPanel = new FlowPanel();
controlsPanel.setStyleName("timePanel-controls");
timePanelInnerWrapper.add(controlsPanel);
// play button control
FlowPanel playControlPanel = new FlowPanel();
playControlPanel = new FlowPanel();
playControlPanel.setStyleName("timePanel-controls-play");
controlsPanel.add(playControlPanel);
@@ -293,6 +301,20 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
controlsPanel.add(timeToStartControlPanel);
}
public void hideControlsPanelAndMovePlayButtonUp() {
controlsPanel.remove(playControlPanel);
timePanelInnerWrapper.remove(controlsPanel);
timePanelSliderFlowWrapper.insert(playControlPanel, 0);
playControlPanel.setStyleName("timePanel-timeslider-play");
}
public void showControlsPanelAndMovePlayButtonDown() {
timePanelInnerWrapper.add(controlsPanel);
timePanelSliderFlowWrapper.remove(playControlPanel);
controlsPanel.insert(playControlPanel, 0);
playControlPanel.setStyleName("timePanel-controls-play");
}
@Override
public void timeChanged(Date newTime, Date oldTime) {
if (timeRangeProvider.isZoomed()) {
@@ -465,7 +465,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
getSailingService().getLeaderboardByName(getLeaderboardName(),
timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(),
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(),
shallAddOverallDetails(), previousLeaderboard.getId(), new MarkedAsyncCallback<IncrementalOrFullLeaderboardDTO>(
shallAddOverallDetails(), previousLeaderboard.getId(), /* fillNetPointsUncorrected */ false,
new MarkedAsyncCallback<IncrementalOrFullLeaderboardDTO>(
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
@Override
public void onSuccess(IncrementalOrFullLeaderboardDTO result) {
@@ -518,7 +519,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
} else {
trackedRacesIdentifiers = Collections.emptyList();
}
raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), errorReporter,
raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), asyncActionsExecutor, errorReporter,
trackedRacesIdentifiers, timer.getRefreshInterval());
}
return raceTimesInfoProvider;
@@ -1896,8 +1897,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
if (needsDataLoading()) {
GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService,
getLeaderboardName(), useNullAsTimePoint() ? null : date,
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(),
shallAddOverallDetails(), /* previousLeaderboard */ getLeaderboard(), timer, errorReporter, stringMessages);
/* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), shallAddOverallDetails(), /* previousLeaderboard */
getLeaderboard(), isFillNetPointsUncorrected(), timer, errorReporter, stringMessages);
this.asyncActionsExecutor.execute(getLeaderboardByNameAction, LOAD_LEADERBOARD_DATA_CATEGORY,
new AsyncCallback<LeaderboardDTO>() {
@Override
@@ -1919,6 +1920,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
}
}
protected boolean isFillNetPointsUncorrected() {
return false;
}
/**
* In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, <code>null</code>
* is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change
@@ -2716,4 +2721,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
timer.removeTimeListener(this);
}
}
protected Timer getTimer() {
return timer;
}
}
@@ -17,6 +17,8 @@ import com.google.gwt.cell.client.TextCell;
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.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -34,6 +36,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwt.user.client.ui.Label;
@@ -83,6 +86,8 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
final private CellTable<CompetitorDTO> suppressedCompetitorsTable;
final private ListDataProvider<CompetitorDTO> suppressedCompetitorsShown;
private CheckBox showUncorrectedNetPointsCheckbox;
private class SettingsClickHandler implements ClickHandler {
private final StringMessages stringMessages;
@@ -630,13 +635,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
Grid scoreCorrectionInfoGrid = new Grid(3,3);
Grid scoreCorrectionInfoGrid = new Grid(3,4);
scoreCorrectionInfoGrid.setCellPadding(3);
scoreCorrectionInfoGrid.setWidget(0, 0, new Label("Last score correction time:"));
scoreCorrectionInfoGrid.setWidget(0, 0, new Label(stringMessages.lastScoreCorrectionsTime() + ":"));
lastScoreCorrectionTimeBox = new DateBox();
scoreCorrectionInfoGrid.setWidget(0, 1, lastScoreCorrectionTimeBox);
scoreCorrectionInfoGrid.setWidget(1, 0, new Label("Last score correction comment:"));
scoreCorrectionInfoGrid.setWidget(1, 0, new Label(stringMessages.lastScoreCorrectionsComment() + ":"));
lastScoreCorrectionCommentBox = new TextBox();
scoreCorrectionInfoGrid.setWidget(1, 1, lastScoreCorrectionCommentBox);
@@ -664,9 +669,17 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
}
});
final Button setScoreCorrectionDefaultTimeBtn = new Button("Set time to 'now'");
final Button setScoreCorrectionDefaultTimeBtn = new Button(stringMessages.setTimeToNow()); // FIXME i18n
setScoreCorrectionDefaultTimeBtn.addStyleName("inlineButton");
scoreCorrectionInfoGrid.setWidget(0, 2, setScoreCorrectionDefaultTimeBtn);
scoreCorrectionInfoGrid.setWidget(0, 2, setScoreCorrectionDefaultTimeBtn);
showUncorrectedNetPointsCheckbox = new CheckBox(stringMessages.showUncorrectedNetPoints());
showUncorrectedNetPointsCheckbox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
timeChanged(getTimer().getTime(), getTimer().getTime());
}
});
scoreCorrectionInfoGrid.setWidget(0, 3, showUncorrectedNetPointsCheckbox);
setScoreCorrectionDefaultTimeBtn.addClickHandler(new ClickHandler() {
@Override
@@ -883,6 +896,14 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
maxPointsDropDownCellProvider, netPointsEditCellProvider));
return list;
}
/**
* Computing the uncorrected scores can be expensive for large leaderboards; disable by default but allow user to enable
*/
@Override
protected boolean isFillNetPointsUncorrected() {
return showUncorrectedNetPointsCheckbox==null?false:showUncorrectedNetPointsCheckbox.getValue();
}
@Override
protected void updateLeaderboard(LeaderboardDTO leaderboard) {
@@ -29,6 +29,7 @@ import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback;
import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder;
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.client.SailingService;
@@ -40,6 +41,7 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.UserDTO;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
import com.sap.sse.gwt.client.useragent.UserAgentChecker;
@@ -65,7 +67,7 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
private final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class);
private boolean toolbarHidden;
private boolean toolbarAndLogoAndTitleBarHidden;
@Override
protected void doOnModuleLoad() {
@@ -172,8 +174,9 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
raceSelectionModel.setSelection(singletonList);
Timer timer = new Timer(PlayModes.Replay, 1000l);
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, this, singletonList, 5000l /* requestInterval*/);
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, user, timer, raceSelectionModel, leaderboardName,
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, asyncActionsExecutor, user, timer, raceSelectionModel, leaderboardName,
leaderboardGroupName, raceboardViewConfig, RaceBoardEntryPoint.this, stringMessages, userAgent, raceTimesInfoProvider, showMapControls);
raceBoardPanel.fillRegattas(regattas);
createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
@@ -238,15 +241,26 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
lbl.setStyleName("browserOptimizedMessage");
toolbarPanel.add(lbl);
}
FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel);
final FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel);
FlowPanel timePanel = createTimePanel(raceBoardPanel);
p.addNorth(logoAndTitlePanel, 68);
p.addNorth(toolbarPanel, 40);
toolbarHidden = false;
toolbarAndLogoAndTitleBarHidden = false;
if (!raceboardViewConfiguration.isShowNavigationPanel()) {
p.setWidgetHidden(toolbarPanel, true);
toolbarHidden = true;
globalNavigationPanel.setVisible(false);
toolbarAndLogoAndTitleBarHidden = true;
}
p.addSouth(timePanel, 90);
p.add(raceBoardPanel);
p.addStyleName("dockLayoutPanel");
addModeratorShortkeyFunctionality(p, toolbarPanel, raceBoardPanel, timePanel);
}
private void addModeratorShortkeyFunctionality(final DockLayoutPanel p, final Panel toolbarPanel,
final RaceBoardPanel raceBoardPanel, final Panel timeWrapperPanel) {
Event.addNativePreviewHandler(new NativePreviewHandler() {
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
@@ -257,16 +271,22 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
Scheduler.get().scheduleDeferred(new Command() {
@Override
public void execute() {
p.setWidgetHidden(toolbarPanel, !toolbarHidden);
toolbarHidden = !toolbarHidden;
p.setWidgetHidden(toolbarPanel, !toolbarAndLogoAndTitleBarHidden);
globalNavigationPanel.setVisible(toolbarAndLogoAndTitleBarHidden);
toolbarAndLogoAndTitleBarHidden = !toolbarAndLogoAndTitleBarHidden;
RaceTimePanel timePanel = raceBoardPanel.getTimePanel();
if (toolbarAndLogoAndTitleBarHidden) {
timePanel.hideControlsPanelAndMovePlayButtonUp();
p.setWidgetSize(timeWrapperPanel, 53);
} else {
timePanel.showControlsPanelAndMovePlayButtonDown();
p.setWidgetSize(timeWrapperPanel, 90);
}
}
});
}
}
});
p.addSouth(timePanel, 90);
p.add(raceBoardPanel);
p.addStyleName("dockLayoutPanel");
}
}
@@ -131,7 +131,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
private final static String LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY = "sailingAnalytics.raceBoard.competitorsFilterSets";
public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, UserDTO theUser, Timer timer,
public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, AsyncActionsExecutor asyncActionsExecutor, UserDTO theUser, Timer timer,
RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName,
RaceBoardViewConfiguration raceboardViewConfiguration, ErrorReporter errorReporter, final StringMessages stringMessages,
UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) {
@@ -150,7 +150,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
racesByIdentifier = new HashMap<RaceIdentifier, RaceDTO>();
selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
asyncActionsExecutor = new AsyncActionsExecutor();
this.asyncActionsExecutor = asyncActionsExecutor;
FlowPanel mainPanel = new FlowPanel();
mainPanel.setSize("100%", "100%");
setWidget(mainPanel);
@@ -538,7 +538,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
return toolbarPanel;
}
public Panel getTimePanel() {
public RaceTimePanel getTimePanel() {
return timePanel;
}
@@ -38,17 +38,19 @@ public class FlagImageResolver {
flagImageMap.put(Flags.ZULU, resources.flagZulu());
}
public ImageResource resolveFlagToImage(Flags flag) {
return flagImageMap.get(flag);
public ImageResource resolveFlagToImage(Flags flag, boolean isDisplayed, boolean displayStateChanged) {
return isDisplayed || displayStateChanged ? flagImageMap.get(flag) : null;
}
public ImageResource resolveFlagDirectionToImage(boolean isDisplayed) {
ImageResource result;
public ImageResource resolveFlagDirectionToImage(boolean isDisplayed, boolean displayStateChanged) {
ImageResource result = null;
if (isDisplayed) {
result = resources.arrowUp();
} else {
result = resources.arrowDown();
if (displayStateChanged) {
if (isDisplayed) {
result = resources.arrowUp();
} else {
result = resources.arrowDown();
}
}
return result;
@@ -31,7 +31,7 @@ public class RaceDetailPanel extends SimplePanel {
d = -d;
}
NumberFormat decimalFormat = NumberFormat.getFormat("##.###");
return "" + d + '\u00B0' + m + '\u2032' + decimalFormat.format(s) + '\u2033'; // add º, ', " symbols
return "" + d + '\u00B0' + m + '\u2032' + decimalFormat.format(s) + '\u2033'; // add �, ', " symbols
}
private static final int MAX_PROCEDURE_FIELDS = 2;
@@ -50,7 +50,9 @@ public class RaceDetailPanel extends SimplePanel {
private final Label finishTimeLabel = new Label();
private final Label finishDurationLabel = new Label();
private final Label protestTimeLabel = new Label();
private final Label protestDateLabel = new Label();
private final Label updateTimeLabel = new Label();
private final Label updateDateLabel = new Label();
private final Label vesselPositionLabel = new Label();
private final Label windLabel = new Label();
private final Label startProcedureLabel = new Label();
@@ -94,9 +96,9 @@ public class RaceDetailPanel extends SimplePanel {
basicsGrid.setWidget(2, 1, protestTimeLabel);
basicsGrid.setWidget(0, 2, startDateLabel);
basicsGrid.setWidget(1, 2, finishDurationLabel);
basicsGrid.setWidget(2, 2, new Label(""));
basicsGrid.setWidget(2, 2, protestDateLabel);
Grid managementGrid = new Grid(3, 2);
Grid managementGrid = new Grid(3, 3);
managementGrid.setCellSpacing(cellSpacing);
managementGrid.setWidget(0, 0, new Label("Last update at"));
managementGrid.setWidget(1, 0, new Label("Vessel position"));
@@ -104,6 +106,7 @@ public class RaceDetailPanel extends SimplePanel {
managementGrid.setWidget(0, 1, updateTimeLabel);
managementGrid.setWidget(1, 1, vesselPositionLabel);
managementGrid.setWidget(2, 1, windLabel);
managementGrid.setWidget(0, 2, updateDateLabel);
procedureGrid = new FlexTable();
procedureGrid.setCellSpacing(cellSpacing);
@@ -138,7 +141,7 @@ public class RaceDetailPanel extends SimplePanel {
return;
}
String statusText = flagInterpreter.getMeaningOfRaceStateAndFlags(data.raceInfo.lastStatus,
data.raceInfo.lastUpperFlag, data.raceInfo.lastLowerFlag, data.raceInfo.isLastFlagDisplayed);
data.raceInfo.lastUpperFlag, data.raceInfo.lastLowerFlag, data.raceInfo.lastFlagsAreDisplayed);
String headerLabelText = data.regattaDisplayName + " " + data.raceInfo.fleetName + " " + data.raceInfo.raceName
+ " (" + statusText + ")";
headerLabel.setText(stringMessages.showingDetailsOfRace(headerLabelText));
@@ -166,10 +169,18 @@ public class RaceDetailPanel extends SimplePanel {
.format(data.raceInfo.protestFinishTime);
protestTimeLabel.setText(protestTimeText);
String protestDateText = data.raceInfo.protestFinishTime == null ? "" : dateFormatter
.format(data.raceInfo.protestFinishTime);
protestDateLabel.setText(protestDateText);
String updateTimeText = data.raceInfo.lastUpdateTime == null ? "-" : timeFormatter
.format(data.raceInfo.lastUpdateTime);
updateTimeLabel.setText(updateTimeText);
String updateDateText = data.raceInfo.lastUpdateTime == null ? "" : dateFormatter
.format(data.raceInfo.lastUpdateTime);
updateDateLabel.setText(updateDateText);
String vesselPositionText = stringMessages.unknown();
String windText = stringMessages.unknown();
if (data.raceInfo.lastWind != null) {
@@ -16,6 +16,7 @@ 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.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
@@ -27,6 +28,7 @@ import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
@@ -38,6 +40,7 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.CalendarUtil;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.PassingInstruction;
@@ -83,9 +86,11 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
private ListDataProvider<RegattaOverviewEntryDTO> regattaOverviewDataProvider;
private final VerticalPanel mainPanel;
private final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss");
private final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT);
private final SailingServiceAsync sailingService;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private final UUID eventId;
private EventDTO eventDTO;
@@ -126,6 +131,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
final StringMessages stringMessages, final UUID eventId, RegattaRaceStatesSettings settings, Timer timerToSynchronize) {
this.sailingService = sailingService;
this.stringMessages = stringMessages;
this.errorReporter = errorReporter;
this.eventId = eventId;
this.allEntries = new ArrayList<RegattaOverviewEntryDTO>();
this.timerToSynchronize = timerToSynchronize;
@@ -190,7 +196,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
new AsyncCallback<List<RegattaOverviewEntryDTO>>() {
@Override
public void onFailure(Throwable cause) {
errorReporter.reportError(cause.getMessage());
}
@Override
@@ -207,6 +213,18 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
}
}));
}
private String formatTime(Date time) {
String result = "-";
if (time != null) {
result = "";
if (! CalendarUtil.isSameDate(time, new Date())) {
result += dateFormatter.format(time) + " ";
}
result += timeFormatter.format(time);
}
return result;
}
private CellTable<RegattaOverviewEntryDTO> createRegattaTable() {
CellTable<RegattaOverviewEntryDTO> table = new CellTable<RegattaOverviewEntryDTO>(/* pageSize */10000, tableRes);
@@ -341,11 +359,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
TextColumn<RegattaOverviewEntryDTO> raceStartTimeColumn = new TextColumn<RegattaOverviewEntryDTO>() {
@Override
public String getValue(RegattaOverviewEntryDTO entryDTO) {
String result = "-";
if (entryDTO.raceInfo.startTime != null) {
result = timeFormatter.format(entryDTO.raceInfo.startTime);
}
return result;
return formatTime(entryDTO.raceInfo.startTime);
}
};
raceStartTimeColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
@@ -375,7 +389,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
@Override
public SafeHtml getValue(final RegattaOverviewEntryDTO entryDTO) {
String status = flagInterpreter.getMeaningOfRaceStateAndFlags(entryDTO.raceInfo.lastStatus, entryDTO.raceInfo.lastUpperFlag,
entryDTO.raceInfo.lastLowerFlag, entryDTO.raceInfo.isLastFlagDisplayed);
entryDTO.raceInfo.lastLowerFlag, entryDTO.raceInfo.lastFlagsAreDisplayed);
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant("<a class=\"pointeredLink\">");
sb.appendEscaped(status);
@@ -444,12 +458,13 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
tooltip += " " + FlagsMeaningExplanator.getFlagsMeaning(stringMessages,
entryDTO.raceInfo.lastUpperFlag,
entryDTO.raceInfo.lastLowerFlag,
entryDTO.raceInfo.isLastFlagDisplayed);
entryDTO.raceInfo.lastFlagsAreDisplayed);
}
return SailingFlagsBuilder.render(
entryDTO.raceInfo.lastUpperFlag,
entryDTO.raceInfo.lastLowerFlag,
entryDTO.raceInfo.isLastFlagDisplayed,
entryDTO.raceInfo.lastFlagsAreDisplayed,
entryDTO.raceInfo.lastFlagsDisplayedStateChanged,
tooltip);
}
@@ -459,18 +474,25 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
TextColumn<RegattaOverviewEntryDTO> lastUpdateColumn = new TextColumn<RegattaOverviewEntryDTO>() {
@Override
public String getValue(RegattaOverviewEntryDTO entryDTO) {
String lastUpdateTime = "-";
if (entryDTO.raceInfo.lastUpdateTime != null) {
lastUpdateTime = timeFormatter.format(entryDTO.raceInfo.lastUpdateTime);
}
return lastUpdateTime;
return formatTime(entryDTO.raceInfo.lastUpdateTime);
}
};
lastUpdateColumn.setSortable(true);
regattaOverviewListHandler.setComparator(lastUpdateColumn, new Comparator<RegattaOverviewEntryDTO>() {
@Override
public int compare(RegattaOverviewEntryDTO left, RegattaOverviewEntryDTO right) {
return left.raceInfo.lastUpdateTime.compareTo(right.raceInfo.lastUpdateTime);
boolean nullGreater = false;
Date leftLastUpdateTime = left.raceInfo.lastUpdateTime;
Date rightLastUpdateTime = right.raceInfo.lastUpdateTime;
if (leftLastUpdateTime == rightLastUpdateTime) {
return 0;
} else if (leftLastUpdateTime == null) {
return (nullGreater ? 1 : -1);
} else if (rightLastUpdateTime == null) {
return (nullGreater ? -1 : 1);
}
return leftLastUpdateTime.compareTo(rightLastUpdateTime);
}
});
lastUpdateColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
@@ -486,7 +508,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
} else if (entryDTO.raceInfo.lastLowerFlag != null && entryDTO.raceInfo.lastLowerFlag.equals(Flags.HOTEL)) {
additionalInformation.append(stringMessages.furtherSignalsAshore());
isInfoBefore = true;
} else if (entryDTO.raceInfo.lastUpperFlag != null && entryDTO.raceInfo.lastUpperFlag.equals(Flags.XRAY) && entryDTO.raceInfo.isLastFlagDisplayed) {
} else if (entryDTO.raceInfo.lastUpperFlag != null && entryDTO.raceInfo.lastUpperFlag.equals(Flags.XRAY) && entryDTO.raceInfo.lastFlagsAreDisplayed) {
additionalInformation.append(stringMessages.earlyStarters());
isInfoBefore = true;
}
@@ -530,7 +552,8 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
table.addColumn(raceAdditionalInformationColumn, stringMessages.additionalInformation());
table.addColumnSortHandler(regattaOverviewListHandler);
table.getColumnSortList().push(courseAreaColumn);
ColumnSortInfo lastUpdateColumnSortInfo = new ColumnSortInfo(lastUpdateColumn, /*ascending*/ false);
table.getColumnSortList().push(lastUpdateColumnSortInfo);
return table;
}
@@ -40,15 +40,15 @@ public class SailingFlagsBuilder {
return SafeStylesUtils.fromTrustedString("float:left;padding-right:10px;");
}
public static SafeHtml render(Flags upperFlag, Flags lowerFlag, boolean isDisplayed, String tooltip) {
public static SafeHtml render(Flags upperFlag, Flags lowerFlag, boolean isDisplayed, boolean displayStateChanged, String tooltip) {
FlagImageResolver flagImageResolver = new FlagImageResolver();
ImageResource upperFlagImage = flagImageResolver.resolveFlagToImage(upperFlag);
ImageResource lowerFlagImage = flagImageResolver.resolveFlagToImage(lowerFlag);
ImageResource directionImage = flagImageResolver.resolveFlagDirectionToImage(isDisplayed);
ImageResource upperFlagImage = flagImageResolver.resolveFlagToImage(upperFlag, isDisplayed, displayStateChanged);
ImageResource lowerFlagImage = flagImageResolver.resolveFlagToImage(lowerFlag, isDisplayed, displayStateChanged);
ImageResource directionImage = flagImageResolver.resolveFlagDirectionToImage(isDisplayed, displayStateChanged);
SafeHtml upperFlagHtml = (upperFlagImage != null) ? makeImagePrototype(upperFlagImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
SafeHtml lowerFlagHtml = (lowerFlagImage != null) ? makeImagePrototype(lowerFlagImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
SafeHtml directionHtml = (upperFlagImage != null) ? makeImagePrototype(directionImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
SafeHtml directionHtml = (directionImage != null) ? makeImagePrototype(directionImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
SafeHtmlBuilder builder = new SafeHtmlBuilder();
SafeStyles imgStyle = getImageStyle();
@@ -67,6 +67,7 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.LeaderboardGroupBase;
import com.sap.sailing.domain.base.LeaderboardSearchResult;
import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.RaceColumn;
@@ -117,6 +118,7 @@ import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.PolarSheetsData;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RaceFetcher;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaFetcher;
import com.sap.sailing.domain.common.RegattaIdentifier;
@@ -651,7 +653,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
@Override
public IncrementalOrFullLeaderboardDTO getLeaderboardByName(final String leaderboardName, final Date date,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
String previousLeaderboardId) throws NoWindException, InterruptedException, ExecutionException,
String previousLeaderboardId, boolean fillNetPointsUncorrected) throws NoWindException, InterruptedException, ExecutionException,
IllegalArgumentException {
try {
long startOfRequestHandling = System.currentTimeMillis();
@@ -665,7 +667,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
timePoint = new MillisecondsTimePoint(date);
}
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, getService(), baseDomainFactory);
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, getService(), baseDomainFactory, fillNetPointsUncorrected);
LeaderboardDTO previousLeaderboardDTO = null;
synchronized (leaderboardByNameResultsCacheById) {
leaderboardByNameResultsCacheById.put(leaderboardDTO.getId(), leaderboardDTO);
@@ -831,11 +833,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
if (startTime != null) {
FlagPoleState activeFlagState = state.getRacingProcedure().getActiveFlags(startTime, MillisecondsTimePoint.now());
List<FlagPole> activeFlags = activeFlagState.getCurrentState();
FlagPoleState previousFlagState = activeFlagState.getPreviousState(state.getRacingProcedure(), startTime);
List<FlagPole> previousFlags = previousFlagState.getCurrentState();
FlagPole mostInterestingFlagPole = FlagPoleState.getMostInterestingFlagPole(previousFlags, activeFlags);
// TODO: adapt the LastFlagFinder#getMostRecent method!
if (!activeFlags.isEmpty()) {
raceInfoDTO.lastUpperFlag = activeFlags.get(0).getUpperFlag();
raceInfoDTO.lastLowerFlag = activeFlags.get(0).getLowerFlag();
raceInfoDTO.isLastFlagDisplayed = activeFlags.get(0).isDisplayed();
if (mostInterestingFlagPole != null) {
raceInfoDTO.lastUpperFlag = mostInterestingFlagPole.getUpperFlag();
raceInfoDTO.lastLowerFlag = mostInterestingFlagPole.getLowerFlag();
raceInfoDTO.lastFlagsAreDisplayed = mostInterestingFlagPole.isDisplayed();
raceInfoDTO.lastFlagsDisplayedStateChanged = previousFlagState.hasPoleChanged(mostInterestingFlagPole);
}
}
@@ -849,7 +856,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
if (raceInfoDTO.lastStatus.equals(RaceLogRaceStatus.UNSCHEDULED)) {
raceInfoDTO.lastUpperFlag = abortingFlagEvent.getUpperFlag();
raceInfoDTO.lastLowerFlag = abortingFlagEvent.getLowerFlag();
raceInfoDTO.isLastFlagDisplayed = abortingFlagEvent.isDisplayed();
raceInfoDTO.lastFlagsAreDisplayed = abortingFlagEvent.isDisplayed();
raceInfoDTO.lastFlagsDisplayedStateChanged = true;
}
}
@@ -866,7 +874,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
raceInfoDTO.protestFinishTime = protestStartTime.plus(protestDuration).asDate();
raceInfoDTO.lastUpperFlag = Flags.BRAVO;
raceInfoDTO.lastLowerFlag = Flags.NONE;
raceInfoDTO.isLastFlagDisplayed = true;
raceInfoDTO.lastFlagsAreDisplayed = true;
raceInfoDTO.lastFlagsDisplayedStateChanged = true;
}
}
@@ -885,7 +894,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
raceInfoDTO.isTracked = raceColumn.getTrackedRace(fleet) != null ? true : false;
return raceInfoDTO;
}
}
private void fillStartProcedureSpecifics(RaceInfoDTO raceInfoDTO, ReadonlyRaceState state) {
RaceInfoExtensionDTO info = null;
@@ -1403,12 +1412,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
public CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate) throws NoWindException {
return new CompactRaceMapDataDTO(getBoatPositions(raceIdentifier, fromPerCompetitorIdAsString,
toPerCompetitorIdAsString, extrapolate), getCoursePositions(raceIdentifier, date), getCourseSidelines(raceIdentifier, date), getQuickRanks(
raceIdentifier, date));
final Map<CompetitorDTO, List<GPSFixDTO>> boatPositions = getBoatPositions(raceIdentifier,
fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate);
final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date);
final List<SidelineDTO> courseSidelines = getCourseSidelines(raceIdentifier, date);
final List<QuickRankDTO> quickRanks = getQuickRanks(raceIdentifier, date);
return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks);
}
/**
* {@link LegType}s are cached within the method with a resolution of one minute. The cache key is a pair of
* {@link TrackedLegOfCompetitor} and {@link TimePoint}.
*
* @param from
* for the list of competitors provided as keys of this map, requests the GPS fixes starting with the
* date provided as value
@@ -1425,7 +1440,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
*/
private Map<CompetitorDTO, List<GPSFixDTO>> getBoatPositions(RegattaAndRaceIdentifier raceIdentifier,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate) throws NoWindException {
boolean extrapolate)
throws NoWindException {
Map<Pair<Leg, TimePoint>, LegType> legTypeCache = new HashMap<>();
Map<CompetitorDTO, List<GPSFixDTO>> result = new HashMap<CompetitorDTO, List<GPSFixDTO>>();
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
if (trackedRace != null) {
@@ -1475,19 +1492,28 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
GPSFixMoving fix = fixIter.next();
while (fix != null && (fix.getTimePoint().compareTo(toTimePointExcluding) < 0 ||
(fix.getTimePoint().equals(toTimePointExcluding) && toTimePointExcluding.equals(fromTimePoint)))) {
Tack tack;
try {
tack = trackedRace.getTack(competitor, fix.getTimePoint());
} catch (NoWindException nwe) {
tack = null;
}
Wind wind = trackedRace.getWind(fix.getPosition(), toTimePointExcluding);
final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint());
Tack tack = wind == null? null : trackedRace.getTack(estimatedSpeed, wind, fix.getTimePoint());
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor,
fix.getTimePoint());
LegType legType = trackedLegOfCompetitor == null ? null : trackedRace.getTrackedLeg(
trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
Wind wind = trackedRace.getWind(fix.getPosition(),toTimePointExcluding);
LegType legType;
if (trackedLegOfCompetitor != null && trackedLegOfCompetitor.getLeg() != null) {
TimePoint quantifiedTimePoint = quantifyTimePointWithResolution(fix.getTimePoint(), /* resolutionInMilliseconds */
60000);
Pair<Leg, TimePoint> cacheKey = new Pair<Leg, TimePoint>(
trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
legType = legTypeCache.get(cacheKey);
if (legType == null) {
legType = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(
fix.getTimePoint());
legTypeCache.put(cacheKey, legType);
}
} else {
legType = null;
}
WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePointExcluding);
GPSFixDTO fixDTO = createGPSFixDTO(fix, track.getEstimatedSpeed(fix.getTimePoint()), windDTO, tack, legType, /* extrapolate */
GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */
false);
fixesForCompetitor.add(fixDTO);
if (fixIter.hasNext()) {
@@ -1496,22 +1522,31 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
// check if fix was at date and if extrapolation is requested
if (!fix.getTimePoint().equals(toTimePointExcluding) && extrapolate) {
Position position = track.getEstimatedPosition(toTimePointExcluding, extrapolate);
Tack tack2;
try {
tack2 = trackedRace.getTack(competitor, toTimePointExcluding);
} catch (NoWindException nwe) {
tack2 = null;
}
LegType legType2 = trackedLegOfCompetitor == null ? null : trackedRace
.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(
fix.getTimePoint());
SpeedWithBearing speedWithBearing = track.getEstimatedSpeed(toTimePointExcluding);
Wind wind2 = trackedRace.getWind(position, toTimePointExcluding);
SpeedWithBearing estimatedSpeed2 = track.getEstimatedSpeed(toTimePointExcluding);
Tack tack2 = wind2 == null ? null : trackedRace.getTack(estimatedSpeed2, wind2, toTimePointExcluding);
LegType legType2;
if (trackedLegOfCompetitor != null && trackedLegOfCompetitor.getLeg() != null) {
TimePoint quantifiedTimePoint = quantifyTimePointWithResolution(
fix.getTimePoint(), /* resolutionInMilliseconds */
60000);
Pair<Leg, TimePoint> cacheKey = new Pair<Leg, TimePoint>(
trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
legType2 = legTypeCache.get(cacheKey);
if (legType2 == null) {
legType2 = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg())
.getLegType(
fix.getTimePoint());
legTypeCache.put(cacheKey, legType2);
}
} else {
legType2 = null;
}
WindDTO windDTO2 = wind2 == null ? null : createWindDTOFromAlreadyAveraged(wind2, toTimePointExcluding);
GPSFixDTO extrapolated = new GPSFixDTO(
toPerCompetitorIdAsString.get(competitorDTO.getIdAsString()),
position==null?null:new PositionDTO(position.getLatDeg(), position.getLngDeg()),
speedWithBearing==null?null:createSpeedWithBearingDTO(speedWithBearing), windDTO2,
estimatedSpeed2==null?null:createSpeedWithBearingDTO(estimatedSpeed2), windDTO2,
tack2, legType2, /* extrapolated */ true);
fixesForCompetitor.add(extrapolated);
}
@@ -1525,6 +1560,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
return result;
}
private TimePoint quantifyTimePointWithResolution(TimePoint timePoint, long resolutionInMilliseconds) {
return new MillisecondsTimePoint(timePoint.asMillis() / resolutionInMilliseconds * resolutionInMilliseconds);
}
private SpeedWithBearingDTO createSpeedWithBearingDTO(SpeedWithBearing speedWithBearing) {
return new SpeedWithBearingDTO(speedWithBearing.getKnots(), speedWithBearing
.getBearing().getDegrees());
@@ -2521,7 +2560,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
Map<CompetitorDTO, FutureTask<CompetitorRaceDataDTO>> resultFutures = new HashMap<CompetitorDTO, FutureTask<CompetitorRaceDataDTO>>();
for (final CompetitorDTO competitorDTO : competitors) {
FutureTask<CompetitorRaceDataDTO> future = new FutureTask<CompetitorRaceDataDTO>(new Callable<CompetitorRaceDataDTO>() {
@Override
@Override
public CompetitorRaceDataDTO call() throws NoWindException {
Competitor competitor = getCompetitorByIdAsString(trackedRace.getRace().getCompetitors(),
competitorDTO.getIdAsString());
@@ -4177,7 +4216,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
Map<RegattaAndRaceIdentifier, Integer> numberOfWindFixesImportedPerRace = new HashMap<RegattaAndRaceIdentifier, Integer>();
for (Account account : igtimiConnectionFactory.getAllAccounts()) {
IgtimiConnection conn = igtimiConnectionFactory.connect(account, correctByDeclination);
IgtimiConnection conn = igtimiConnectionFactory.connect(account);
Map<TrackedRace, Integer> resultsForAccounts = conn.importWindIntoRace(trackedRaces, correctByDeclination);
for (Entry<TrackedRace, Integer> resultForAccount : resultsForAccounts.entrySet()) {
RegattaAndRaceIdentifier key = resultForAccount.getKey().getRaceIdentifier();
@@ -4658,4 +4697,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
leaderboardSearchResult.getBoatClassName(), convertToEventDTO(leaderboardSearchResult.getEvent()),
leaderboardGroups);
}
@Override
public RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived) {
if (newStartTimeReceived != null) {
RegattaNameAndRaceName regattaAndRaceIdentifier = new RegattaNameAndRaceName(
raceIdentifier.getRegattaName(), raceIdentifier.getRaceName());
DynamicTrackedRace trackedRace = getService().getTrackedRace(regattaAndRaceIdentifier);
trackedRace.setStartTimeReceived(new MillisecondsTimePoint(newStartTimeReceived));
return baseDomainFactory.createRaceDTO(getService(), false, regattaAndRaceIdentifier, trackedRace);
}
return null;
}
}
@@ -49,7 +49,8 @@ public class RaceInfoDTO implements IsSerializable {
public Date lastUpdateTime;
public Flags lastUpperFlag;
public Flags lastLowerFlag;
public boolean isLastFlagDisplayed;
public boolean lastFlagsAreDisplayed;
public boolean lastFlagsDisplayedStateChanged;
public boolean isRaceAbortedInPassBefore;
public Date abortingTimeInPassBefore;
public boolean isTracked;
@@ -23,6 +23,8 @@ import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
public class TVEntryPoint extends AbstractEntryPoint {
private static final String PARAM_LEADERBOARD_NAME = "name";
private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
private static final String PARAM_EMBEDDED = "embedded";
private static final String PARAM_SHOW_RACE_DETAILS = "showRaceDetails";
@@ -42,10 +44,8 @@ public class TVEntryPoint extends AbstractEntryPoint {
EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath);
EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath);
final boolean showRaceDetails = Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS) != null
&& Window.Location.getParameter(PARAM_SHOW_RACE_DETAILS).equalsIgnoreCase("true");
final boolean embedded = Window.Location.getParameter(PARAM_EMBEDDED) != null
&& Window.Location.getParameter(PARAM_EMBEDDED).equalsIgnoreCase("true");
final boolean showRaceDetails = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_RACE_DETAILS, true);
final boolean embedded = GwtHttpRequestUtils.getBooleanParameter(PARAM_EMBEDDED, false);
final long delayToLiveMillis = Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS) != null ? Long
.valueOf(Window.Location.getParameter(PARAM_DELAY_TO_LIVE_MILLIS)) : 5000l; // default 5s
final boolean showNavigationPanel = GwtHttpRequestUtils.getBooleanParameter(
@@ -67,7 +67,7 @@ public class TVEntryPoint extends AbstractEntryPoint {
sailingService.getLeaderboardNames(new AsyncCallback<List<String>>() {
@Override
public void onSuccess(List<String> leaderboardNames) {
leaderboardName = Window.Location.getParameter("name");
leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
leaderboardGroupName = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
if (leaderboardNames.contains(leaderboardName)) {
createUI(showRaceDetails, embedded, showNavigationPanel, delayToLiveMillis);
@@ -45,6 +45,7 @@ public class TVViewController implements RaceTimesInfoProviderListener {
private final UserAgentDetails userAgent;
private final LogoAndTitlePanel logoAndTitlePanel;
private final DockLayoutPanel dockPanel;
private final AsyncActionsExecutor asyncActionsExecutor;
/**
* We use two timers: one for the leaderboard, one for the race board. This way, we can stop one while the other continues.
@@ -93,6 +94,7 @@ public class TVViewController implements RaceTimesInfoProviderListener {
this.dockPanel = dockPanel;
this.showRaceDetails = showRaceDetails;
asyncActionsExecutor = new AsyncActionsExecutor();
showWindChart = false;
leaderboard = null;
leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false);
@@ -106,13 +108,13 @@ public class TVViewController implements RaceTimesInfoProviderListener {
raceboardTimer.setRefreshInterval(REFRESH_INTERVAL_IN_MILLIS_RACEBOARD);
raceboardTimer.play();
raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, errorReporter, new ArrayList<RegattaAndRaceIdentifier>(), 3000l);
raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, errorReporter, new ArrayList<RegattaAndRaceIdentifier>(), 3000l);
raceTimesInfoProvider.addRaceTimesInfoProviderListener(this);
}
private LeaderboardPanel createLeaderboardPanel(String leaderboardGroupName, String leaderboardName, boolean showRaceDetails) {
CompetitorSelectionModel selectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true);
LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardSettings,
LeaderboardPanel leaderboardPanel = new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderboardSettings,
/* preSelectedRace */null, selectionModel, leaderboardTimer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages,
userAgent, showRaceDetails, /* raceTimesInfoProvider */ null, /* autoExpandLastRaceColumn */ false, /* adjustTimerDelay */ true) {
@Override
@@ -146,7 +148,7 @@ public class TVViewController implements RaceTimesInfoProviderListener {
RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(raceToShow);
raceSelectionModel.setSelection(singletonList);
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, null, raceboardTimer, raceSelectionModel, leaderboardName,
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, asyncActionsExecutor,null, raceboardTimer, raceSelectionModel, leaderboardName,
null, raceboardViewConfig, errorReporter, stringMessages, userAgent, raceTimesInfoProvider, /* showMapControls */ false);
return raceBoardPanel;
}
@@ -468,6 +468,36 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest
}
}
@Test
public void testLoadStoreRegattaWithFleetsEnsuringFleetOrdering() {
final String regattaBaseName = "Kieler Woche";
BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
Regatta regatta = createRegatta(regattaBaseName, boatClass,
/* persistent */ false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null);
MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
mof.storeRegatta(regatta);
DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
assertEquals(regattaBaseName, loadedRegatta.getBaseName());
Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
Series loadedQualifyingSeries = seriesIter.next();
Iterator<? extends Fleet> qualiFleetIt = loadedQualifyingSeries.getFleets().iterator();
Fleet qualiFleet1 = qualiFleetIt.next();
assertEquals(qualiFleet1.getName(), "Yellow");
Fleet qualiFleet2 = qualiFleetIt.next();
assertEquals(qualiFleet2.getName(), "Blue");
Series loadedFinalSeries = seriesIter.next();
Iterator<? extends Fleet> finalFleetIt = loadedFinalSeries.getFleets().iterator();
Fleet finalFleet1 = finalFleetIt.next();
assertEquals(finalFleet1.getName(), "Gold");
Fleet finalFleet2 = finalFleetIt.next();
assertEquals(finalFleet2.getName(), "Silver");
}
@Test
public void testStorageOfRaceIdentifiersOnRaceColumnInSeries() {
final int numberOfQualifyingRaces = 5;
@@ -7,7 +7,7 @@ import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
@@ -16,15 +16,18 @@ import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogDenoteForTrackingEventSerializer;
public class RaceLogDenoteForTrackingEventDeserializer extends BaseRaceLogEventDeserializer {
public RaceLogDenoteForTrackingEventDeserializer(JsonDeserializer<Competitor> competitorDeserializer) {
private final SharedDomainFactory domainFactory;
public RaceLogDenoteForTrackingEventDeserializer(JsonDeserializer<Competitor> competitorDeserializer,
SharedDomainFactory factory) {
super(competitorDeserializer);
this.domainFactory = factory;
}
@Override
protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, RaceLogEventAuthor author, TimePoint timePoint, int passId, List<Competitor> competitors)
throws JsonDeserializationException {
String raceName = (String) object.get(RaceLogDenoteForTrackingEventSerializer.FIELD_RACE_NAME);
BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass(
BoatClass boatClass = domainFactory.getOrCreateBoatClass(
(String) object.get(RaceLogDenoteForTrackingEventSerializer.FIELD_BOAT_CLASS));
Serializable raceId = (Serializable) object.get(RaceLogDenoteForTrackingEventSerializer.FIELD_RACE_ID);
@@ -75,7 +75,7 @@ public class RaceLogEventDeserializer implements JsonDeserializer<RaceLogEvent>
new PositionJsonDeserializer())),
new RaceLogDeviceCompetitorMappingEventDeserializer(competitorDeserializer, deviceDeserializer),
new RaceLogDeviceMarkMappingEventDeserializer(competitorDeserializer, new MarkDeserializer(domainFactory), deviceDeserializer),
new RaceLogDenoteForTrackingEventDeserializer(competitorDeserializer),
new RaceLogDenoteForTrackingEventDeserializer(competitorDeserializer, domainFactory),
new RaceLogStartTrackingEventDeserializer(competitorDeserializer),
new RaceLogRevokeEventDeserializer(competitorDeserializer),
new RaceLogRegisterCompetitorEventDeserializer(competitorDeserializer),
@@ -137,7 +137,7 @@ public class WindStatusServlet extends SailingServerHttpServlet implements Igtim
IgtimiConnectionFactory igtimiConnectionFactory = igtimiServiceTracker.getService();
for (Account account : igtimiConnectionFactory.getAllAccounts()) {
if (account.getUser() != null) {
IgtimiConnection igtimiConnection = igtimiConnectionFactory.connect(account, /* correctByDeclination */ true);
IgtimiConnection igtimiConnection = igtimiConnectionFactory.connect(account);
try {
liveDataConnection = igtimiConnection.getOrCreateLiveConnection(igtimiConnection.getWindDevices());
igtimiWindReceiver = new IgtimiWindReceiver(igtimiConnection.getWindDevices(), DeclinationService.INSTANCE);
@@ -189,10 +189,14 @@ public class WindStatusServlet extends SailingServerHttpServlet implements Igtim
public String toString() {
if (message.getTrueWind() != null) {
return messageReceivedAt.toString() + ": [" + boatID + "] Knots: " + message.getTrueWind().getKnots() + " Bearing: " + message.getTrueWindBearing().getDegrees();
return messageReceivedAt.toString() + ": [" + boatID + "] Knots: " + message.getTrueWind().getKnots() + " From: " + getFromAsDegrees();
}
return messageReceivedAt.toString() + ": [" + boatID + "] " + message.getOriginalMessage();
}
private double getFromAsDegrees() {
return message.getTrueWindBearing().reverse().getDegrees();
}
}
private class IgtimiMessageInfo {
@@ -104,7 +104,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
private JSONObject getLeaderboardJson(Leaderboard leaderboard, Util.Triple<TimePoint, ResultStates, Integer> timePointAndResultStateAndMaxCompetitorsCount)
throws NoWindException, InterruptedException, ExecutionException {
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePointAndResultStateAndMaxCompetitorsCount.getA(),
Collections.<String> emptyList(), /* addOverallDetails */ false, getService(), getService().getBaseDomainFactory());
Collections.<String> emptyList(), /* addOverallDetails */ false, getService(), getService().getBaseDomainFactory(),
/* fillNetPointsUncorrected */ false);
TimePoint resultTimePoint = timePointAndResultStateAndMaxCompetitorsCount.getA();
ResultStates resultState = timePointAndResultStateAndMaxCompetitorsCount.getB();
@@ -567,6 +567,7 @@ public class RegattasResource extends AbstractSailingServerResource {
JSONObject jsonRaceResults = new JSONObject();
jsonRaceResults.put("name", trackedRace.getRace().getName());
jsonRaceResults.put("regatta", regatta.getName());
jsonRaceResults.put("startOfRace-ms", trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asMillis());
JSONArray jsonLegs = new JSONArray();
for (TrackedLeg leg : trackedRace.getTrackedLegs()) {
@@ -152,10 +152,12 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest {
public void testRaceTimeReplication() throws InterruptedException, Exception {
startTracking();
// now wait at least until the start of tracking time has been received on the master copy
boolean receivedStartOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null;
while (!receivedStartOfTracking) { // relying on the Timeout rule for this test
boolean receivedStartAndEndOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null &&
master.getTrackedRace(raceIdentifier).getEndOfTracking() != null;
while (!receivedStartAndEndOfTracking) { // relying on the Timeout rule for this test
Thread.sleep(10);
receivedStartOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null;
receivedStartAndEndOfTracking = master.getTrackedRace(raceIdentifier).getStartOfTracking() != null &&
master.getTrackedRace(raceIdentifier).getEndOfTracking() != null;
}
Thread.sleep(2000); // kept failing several times for a 1000ms timeout
TrackedRace replicaTrackedRace = replica.getTrackedRace(raceIdentifier);
@@ -164,6 +164,7 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
new KnotSpeedWithBearingImpl(13, new DegreeBearingImpl(234)));
WindSource webWindSource = new WindSourceImpl(WindSourceType.WEB);
trackedRace.recordWind(wind, webWindSource);
Thread.sleep(500); // wind addition and wind removal are asynchronous operations that could otherwise pass each other
trackedRace.removeWind(wind, webWindSource);
final WindTrack windTrack = trackedRace.getOrCreateWindTrack(webWindSource);
windTrack.lockForRead();
@@ -3,9 +3,15 @@ package com.sap.sailing.server.replication.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -55,6 +61,22 @@ public class Replicator implements Runnable {
private boolean stopped = false;
/**
* When many updates are triggered in a short period of time by a single thread, ensure that the single thread
* providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
* core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
*/
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()-1, 3);
/**
* Used for the parallel execution of operations that don't
* {@link RacingEventServiceOperation#requiresSynchronousExecution()}.
*/
private final static Executor executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>());
/**
* @param master
* descriptor of the master server from which this replicator receives messages
@@ -83,12 +105,31 @@ public class Replicator implements Runnable {
@Override
public void run() {
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
long messageCount = 0;
Field _queue;
try {
_queue = QueueingConsumer.class.getDeclaredField("_queue");
_queue.setAccessible(true);
} catch (Exception e) {
_queue = null;
}
while (true) {
if (isBeingStopped()) {
break;
}
try {
Delivery delivery = consumer.nextDelivery();
messageCount++;
if (_queue != null) {
if (messageCount % 10000l == 0) {
try {
logger.info("Inbound replication queue size: "+((BlockingQueue<?>) _queue.get(consumer)).size());
} catch (Exception e) {
// it didn't work; but it's a log message only...
logger.info("Received another 10000 replication messages");
}
}
}
byte[] bytesFromMessage = delivery.getBody();
checksPerformed = 0;
// Set this object's class's class loader as context for de-serialization so that all exported classes
@@ -172,8 +213,18 @@ public class Replicator implements Runnable {
}
}
private synchronized void apply(RacingEventServiceOperation<?> operation) {
racingEventServiceTracker.getRacingEventService().apply(operation);
private synchronized void apply(final RacingEventServiceOperation<?> operation) {
Runnable runnable = new Runnable() {
@Override
public void run() {
racingEventServiceTracker.getRacingEventService().apply(operation);
}
};
if (operation.requiresSynchronousExecution()) {
runnable.run();
} else {
executor.execute(runnable);
}
}
private synchronized void queue(RacingEventServiceOperation<?> operation) {
@@ -6,6 +6,7 @@
<stringAttribute key="bootstrap" value=""/>
<stringAttribute key="checked" value="[NONE]"/>
<booleanAttribute key="clearConfig" value="false"/>
<booleanAttribute key="com.sap.jvm.profiling.ui.widgets.allowDebugging" value="false"/>
<stringAttribute key="configLocation" value="${workspace_loc}/target"/>
<booleanAttribute key="default" value="true"/>
<booleanAttribute key="default_auto_start" value="true"/>
@@ -15,18 +16,18 @@
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sapjvm7"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2010 -Xmx4096m -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -Dkiwo.results=${project_loc:com.sap.sailing.kiworesultimport.test}/resources -Dpersistentcompetitors.clear=false -XX:+UseMembar -Digtimi.client.id=7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6 -Digtimi.client.secret=aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a -Digtimi.client.redirecturi=http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback -DspatialWind=true"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-IO_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-METHOD_PARAMETER_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-NETWORK_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_RESOLVE_ALL_HOSTS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-PERFORMANCE_HOTSPOT_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_IGNORE_SLEEPING_THREADS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-SYNCHRONIZATION_TRACE" value="KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-IO_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-METHOD_PARAMETER_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-NETWORK_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_RESOLVE_ALL_HOSTS%CTX_KEY%true%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-PERFORMANCE_HOTSPOT_TRACE" value="KEY_IGNORE_SLEEPING_THREADS%CTX_KEY%false%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%true%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-SYNCHRONIZATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<booleanAttribute key="show_selected_only" value="false"/>
<stringAttribute key="target_bundles" value="com.rabbitmq.client@default:default,com.sun.jersey@default:default,com.sun.mail.javax.mail@default:default,javax.servlet*3.0.0.v201112011016@default:default,javax.validation@default:default,javax.ws.rs@default:default,jcl.over.slf4j@default:default,org.apache.activemq.activeio-core@default:default,org.apache.activemq.activemq-core@default:default,org.apache.activemq.kahadb@default:default,org.apache.commons.codec@default:default,org.apache.commons.fileupload@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.gogo.shell@default:default,org.apache.geronimo.specs.geronimo-annotation_1.0_spec@default:default,org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec@default:default,org.apache.geronimo.specs.geronimo-jms_1.1_spec@default:default,org.apache.httpcomponents.httpclient@default:default,org.apache.httpcomponents.httpcore@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.console@default:default,org.eclipse.jetty.continuation@default:default,org.eclipse.jetty.deploy@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.jmx@default:default,org.eclipse.jetty.osgi.boot@3:default,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.jetty.webapp@default:default,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.websocket.server@default:default,org.eclipse.jetty.websocket.servlet@default:default,org.eclipse.jetty.xml@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.jdom@default:default,org.objectweb.asm@default:default,rome@default:default,routeconverter@default:default,slf4j.api@default:default,slf4j.jdk14@default:false"/>
<booleanAttribute key="tracing" value="false"/>
@@ -19,6 +19,7 @@ import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
@@ -40,6 +41,7 @@ import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
@@ -278,6 +280,12 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
// TODO Auto-generated method stub
return null;
}
@Override
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedRegatta getTrackedRegatta() {
@@ -391,6 +399,13 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
return null;
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalysis) throws NoWindException {
@@ -398,6 +413,13 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, WindLegTypeAndLegBearingCache cache) {
// TODO Auto-generated method stub
return null;
}
@Override
public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to,
boolean upwindOnly, boolean waitForLatestAnalysis) throws NoWindException {
@@ -15,6 +15,15 @@ public abstract class AbstractWindOperation extends AbstractRaceOperation<Void>
this.wind = wind;
}
/**
* Operations of this type can be run in parallel to other operations; subsequent operations do not have to wait
* for this operation's completion.
*/
@Override
public boolean requiresSynchronousExecution() {
return false;
}
protected WindSource getWindSource() {
return windSource;
}
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more