getPreviousRacesInExecutionOrder(TrackedRace race);
+
+ /**
+ * When the owner of this cache-like provider has initialized all its structures that this cache requires
+ * it shall call this method. This applies in particular after de-serializing the owner because during de-serialization,
+ * even during the readObject method, the owner's fields may not all be initialized. The
+ * readResolve() method is usually a good place to do so.
+ */
+ void triggerUpdate();
+}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
index c98280a45e8..2a8c9168b16 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
@@ -275,6 +275,27 @@ public interface TrackedRace extends Serializable {
* is returned. Otherwise, the center of gravity between the mark positions is computed and returned.
*/
Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint);
+
+ /**
+ * Checks whether the {@link Wind#getTimePoint()} is in range of start and end {@link TimePoint}s plus extra time
+ * for wind recording. If, based on a {@link RaceExecutionOrderProvider}, there is no previous race that takes the
+ * wind fix, an extended time range lead (see
+ * {@link TrackedRaceImpl#EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS}) is used to record wind even a long
+ * time before the race start.
+ *
+ *
+ * A race does not record wind when both, {@link #getStartOfTracking()} and {@link #getStartOfRace()} are
+ * null. Wind is not recorded when it is after the later of {@link #getEndOfRace()} and
+ * {@link #getEndOfTracking()} and one of the two is not null.
+ *
+ *
+ * This default implementation returns true which may be useful for tests and mocked implementations; however, real
+ * implementations shall override this and provide a meaningful implementation according to the specification given
+ * above.
+ */
+ default boolean takesWindFix(Wind wind) {
+ return true;
+ }
/**
* Same as {@link #getWind(Position, TimePoint, Set) getWind(p, at, Collections.emptyList())}
@@ -652,9 +673,9 @@ public interface TrackedRace extends Serializable {
void detachRaceLog(Serializable identifier);
/**
- * Detaches all {@link RaceLog} instances from this race
+ * Detaches the link {@link RaceExecutionOrderProvider}
*/
- void detachAllRaceLogs();
+ void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider);
/**
* Attaches the passed race log with this {@link TrackedRace}.
@@ -674,6 +695,12 @@ public interface TrackedRace extends Serializable {
*/
void attachRegattaLog(RegattaLog regattaLog);
+ /**
+ * Attaches a {@link RaceExecutionOrderProvider} to make a {@link TrackedRace} aware
+ * which races are scheduled around it in the execution order of a {@link Regatta}.
+ * */
+ void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider);
+
/**
* Returns the attached race log event track for this race if any.
* Otherwise null.
@@ -823,4 +850,5 @@ public interface TrackedRace extends Serializable {
Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, NoWindException;
void setPolarDataService(PolarDataService polarDataService);
+
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
index 59827408828..e1463636149 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
@@ -24,7 +24,6 @@ import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
-import com.sap.sailing.domain.common.TimingConstants;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
@@ -658,17 +657,7 @@ DynamicTrackedRace, GPSTrackListener {
@Override
public boolean recordWind(Wind wind, WindSource windSource) {
final boolean result;
- // TODO check what a good filter is; remember that start/end of tracking may change over time; what if we have discarded valuable wind fixes?
- TimePoint startOfRace = getStartOfRace();
- TimePoint startOfTracking = getStartOfTracking();
- TimePoint endOfRace = getEndOfRace();
- TimePoint endOfTracking = getEndOfTracking();
- if ((startOfTracking == null || !startOfTracking.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()) ||
- (startOfRace != null && !startOfRace.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint())))
- &&
- // Caution: don't add to endOfTracking; it may be the end of time, leading to a wrap-around / overflow
- (endOfTracking == null || endOfTracking.after(wind.getTimePoint().minus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS)) ||
- (endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) {
+ if (takesWindFix(wind)) {
result = getOrCreateWindTrack(windSource).add(wind);
updated(/* time point */null); // wind events shouldn't advance race time
triggerManeuverCacheRecalculationForAllCompetitors();
@@ -759,7 +748,7 @@ DynamicTrackedRace, GPSTrackListener {
}
super.detachRaceLog(identifier);
}
-
+
@Override
public void addCourseDesignChangedListener(CourseDesignChangedListener listener) {
this.courseDesignChangedListeners.add(listener);
@@ -818,4 +807,5 @@ DynamicTrackedRace, GPSTrackListener {
public DynamicGPSFixTrack getTrack(Mark mark) {
return (DynamicGPSFixTrack) super.getTrack(mark);
}
+
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
index a43c6f91836..399d4a7625d 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
@@ -116,6 +116,7 @@ import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
+import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -156,7 +157,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// TODO make this variable
private static final long DELAY_FOR_CACHE_CLEARING_IN_MILLISECONDS = 7500;
- public static final long TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = 4 * 60 * 1000l; // let wind start four minutes before race
+ public static final Duration TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = Duration.ONE_MINUTE.times(4); // let wind start four minutes before race
+
+ public static final Duration EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS = Duration.ONE_HOUR;
private TrackedRaceStatus status;
@@ -283,6 +286,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* Keys are the {@link RegattaLog#getId() IDs} of the regatta logs that are stored as values.
*/
protected transient ConcurrentHashMap attachedRegattaLogs;
+
+ private transient ConcurrentHashMap attachedRaceExecutionOrderProviders;
/**
* The time delay to the current point in time in milliseconds.
@@ -376,6 +381,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
locksForMarkPassings = new IdentityHashMap<>();
attachedRaceLogs = new ConcurrentHashMap<>();
attachedRegattaLogs = new ConcurrentHashMap<>();
+ attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>();
this.status = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0);
this.statusNotifier = new Object[0];
this.loadingFromWindStoreLock = new NamedReentrantReadWriteLock("Loading from wind store lock for tracked race "
@@ -573,6 +579,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
ois.defaultReadObject();
raceStates = new WeakHashMap<>();
attachedRaceLogs = new ConcurrentHashMap<>();
+ attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>();
markPassingsTimes = new ArrayList>>();
// The short time wind cache needs to be there before operations such as maneuver recalculation try to access it
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
@@ -1491,6 +1498,62 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
return result;
}
+
+ /**
+ * Checks whether the {@link Wind#getTimePoint()} is in range of start and end {@link TimePoint}s plus extra time
+ * for wind recording. If, based on a {@link RaceExecutionOrderProvider}, there is no previous race that takes the
+ * wind fix, an extended time range lead (see {@link TrackedRaceImpl#EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS})
+ * is used to record wind even a long time before the race start.
+ *
+ * A race does not record wind when both, {@link #getStartOfTracking()} and {@link #getStartOfRace()} are null.
+ * Wind is not recorded when it is after the later of {@link #getEndOfRace()} and {@link #getEndOfTracking()} and one of the
+ * two is not null.
+ */
+ @Override
+ public boolean takesWindFix(Wind wind) {
+ final boolean result;
+ final TimePoint earliestStartTimePoint = Util.getEarliestOfTimePoints(getStartOfRace(), getStartOfTracking());
+ final TimePoint latestEndTimePoint = Util.getLatestOfTimePoints(getEndOfRace(), getEndOfTracking());
+ if (earliestStartTimePoint != null) {
+ // first check if the fix meets the criteria set by the latestEndTimePoint: either the latestEndTimePoint is null, meaning an
+ // open interval which will continue to accept late wind fixes, or the fix time point is before the latestEndTimePoint plus a grace
+ // interval:
+ if (latestEndTimePoint == null || wind.getTimePoint().minus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).before(latestEndTimePoint)) {
+ // then check, if fix is accepted anyway because it's after earliestStartTimePoint.minus(TIME_BEFORE_START_TO_TRACK_WIND_MILLIS)
+ // and before latestEndTimePoint.plus(IS_LIVE_GRACE_PERIOD_IN_MILLIS) or latestEndTimePoint is null. In this case, no expensive
+ // recursive check whether previous races take the fix are required.
+ if (wind.getTimePoint().plus(TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(earliestStartTimePoint)) {
+ result = true;
+ } else {
+ // if the fix is older than even the extended lead interval would accept, don't accept the fix:
+ if (wind.getTimePoint().plus(EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).before(earliestStartTimePoint)) {
+ result = false;
+ } else {
+ // the fix is in the critical interval between EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS and
+ // TIME_BEFORE_START_TO_TRACK_WIND_MILLIS before the earliestStartTimePoint; the fix shall only be accepted
+ // if no previous race exists that accepts it
+ result = noPreviousRaceTakesWind(wind);
+ }
+ }
+ } else {
+ result = false; // don't accept the fix if it's after the latest end time point plus some grace interval
+ }
+ } else {
+ result = false; // don't accept a fix if we don't have any start time information about the race
+ }
+ return result;
+ }
+
+ private boolean noPreviousRaceTakesWind(Wind wind) {
+ final boolean result;
+ Set previousRacesInExecutionOrder = getPreviousRacesFromAttachedRaceExecutionOrderProviders();
+ if (previousRacesInExecutionOrder == null || !previousRacesInExecutionOrder.stream().filter(tr -> tr.takesWindFix(wind) == true).findAny().isPresent()) {
+ result = true;
+ } else {
+ result = false;
+ }
+ return result;
+ }
@Override
public Wind getWind(Position p, TimePoint at) {
@@ -3041,7 +3104,35 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public void attachRaceLog(final RaceLog raceLog) {
loadFixesForLog(raceLog, attachedRaceLogs);
}
+
+ @Override
+ public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
+ if (raceExecutionOrderProvider != null && !attachedRaceExecutionOrderProviders.containsKey(raceExecutionOrderProvider)) {
+ attachedRaceExecutionOrderProviders.put(raceExecutionOrderProvider, raceExecutionOrderProvider);
+ }
+ }
+
+ protected Set getPreviousRacesFromAttachedRaceExecutionOrderProviders() {
+ final Set result;
+ if (attachedRaceExecutionOrderProviders != null) {
+ result = attachedRaceExecutionOrderProviders.values().stream().map(reop->reop.getPreviousRacesInExecutionOrder(this)).collect(HashSet::new, (r, e)->r.addAll(e), (r, e)->r.addAll(e));
+ } else {
+ result = Collections.emptySet();
+ }
+ return result;
+ }
+ @Override
+ public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
+ if (raceExecutionOrderProvider != null) {
+ attachedRaceExecutionOrderProviders.remove(raceExecutionOrderProvider);
+ }
+ }
+
+ public boolean hasRaceExecutionOrderProvidersAttached(){
+ return !attachedRaceExecutionOrderProviders.isEmpty();
+ }
+
private ReadonlyRaceState getRaceState(RaceLog raceLog) {
ReadonlyRaceState result;
synchronized (raceStates) {
@@ -3064,11 +3155,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public void detachRaceLog(Serializable identifier) {
this.attachedRaceLogs.remove(identifier);
}
-
- @Override
- public void detachAllRaceLogs() {
- this.attachedRaceLogs.clear();
- }
@Override
public RaceLog getRaceLog(Serializable identifier) {
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java
index f34596a7855..442ca68ede2 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -94,11 +95,12 @@ public class TrackedRegattaImpl implements TrackedRegatta {
unlockTrackedRacesAfterRead();
}
}
+
/**
* Resolving replaces this de-serialized object (which has a null {@link #raceListeners} collection) by
* a new one into which all other collection contents are copied.
*/
- private Object readResolve() {
+ private Object readResolve() throws ObjectStreamException {
TrackedRegattaImpl result = new TrackedRegattaImpl(this.regatta);
result.trackedRaces.putAll(this.trackedRaces);
result.trackedRacesByBoatClass.putAll(this.trackedRacesByBoatClass);
diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java
index 128b68ed781..9eca28dba48 100755
--- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java
+++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
+import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -590,10 +591,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
- @Override
- public void detachAllRaceLogs() {
- }
-
@Override
public void invalidateStartTime() {
}
@@ -779,4 +776,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public RankingMetric getRankingMetric() {
return null;
}
+
+ @Override
+ public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
+ }
+
+ @Override
+ public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
+ }
}
diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
index 328b5babf96..a00d6c0ff91 100755
--- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
+++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
@@ -5,12 +5,9 @@
-
-
-
@@ -18,8 +15,12 @@
+
+
+
+
@@ -72,7 +73,7 @@
-
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java
index e5c0ed29a8a..0933c567da3 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java
@@ -18,6 +18,11 @@ public class BoatClassImageResolver {
static {
boatClassIconsMap = new HashMap();
+
+ // handicap sailing icons
+ boatClassIconsMap.put(BoatClassMasterdata.ORC.getDisplayName(), imageResources.ORCIcon());
+ boatClassIconsMap.put(BoatClassMasterdata.ORC_CLUB.getDisplayName(), imageResources.ORCIcon());
+ boatClassIconsMap.put(BoatClassMasterdata.ORC_INTERNATIONAL.getDisplayName(), imageResources.ORCIcon());
boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon());
boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon());
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java
index 2a8e2a1102e..cb36d3fe29c 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java
@@ -153,4 +153,8 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS800.png")
ImageResource RS800Icon();
+
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png")
+ ImageResource ORCIcon();
+
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png
new file mode 100644
index 00000000000..9fb9b24bed7
Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png differ
diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java
index dbef3735dcc..89587544b76 100644
--- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java
+++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java
@@ -106,10 +106,10 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
JSONArray jsonLeaderboards = new JSONArray();
Map leaderboards = getService().getLeaderboards();
for (String leaderboardName : leaderboards.keySet()) {
- if (!leaderboardName.equals(LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME))
+ if (!leaderboardName.equals(LeaderboardNameConstants.DEFAULT_LEADERBOARD_NAME)) {
jsonLeaderboards.add(leaderboardName);
+ }
}
-
String json = jsonLeaderboards.toJSONString();
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
index 6db76168f62..a8efcff988e 100644
--- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
+++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
@@ -889,8 +889,8 @@ public class RegattasResource extends AbstractSailingServerResource {
throw re;
}
}
- jsonCompetitorInLeg.put("gapToLeader-s",
- trackedLegOfCompetitor.getGapToLeader(timePoint, rankingInfo, WindPositionMode.LEG_MIDDLE));
+ Duration gapToLeaderDuration = trackedLegOfCompetitor.getGapToLeader(timePoint, rankingInfo, WindPositionMode.LEG_MIDDLE);
+ jsonCompetitorInLeg.put("gapToLeader-s", gapToLeaderDuration != null ? gapToLeaderDuration.asSeconds() : 0.0);
jsonCompetitorInLeg.put("started", trackedLegOfCompetitor.hasStartedLeg(timePoint));
jsonCompetitorInLeg.put("finished", trackedLegOfCompetitor.hasFinishedLeg(timePoint));
jsonCompetitors.add(jsonCompetitorInLeg);
diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java
index 48a8ddd7da4..0aff92a2f88 100755
--- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java
+++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java
@@ -135,7 +135,7 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl
DynamicTrackedRace masterTrackedRace = master.createTrackedRace(new RegattaNameAndRaceName(masterRegatta.getName(), masterRace.getName()),
master.getWindStore(), master.getGPSFixStore(), /* delayToLiveInMillis */ 3000,
/* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 10000, /*ignoreTracTracMarkPassings*/ false);
-
+ masterTrackedRace.setStartOfTrackingReceived(MillisecondsTimePoint.now());
/* Leaderboard */
final String leaderboardName = "Great Leaderboard";
final int[] discardThresholds = new int[] { 17, 23 };
diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java
index 8467de586e4..1c3728ba8cb 100755
--- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java
+++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java
@@ -55,6 +55,7 @@ import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
import com.sap.sailing.server.operationaltransformation.CreateTrackedRace;
import com.sap.sailing.server.operationaltransformation.TrackRegatta;
import com.sap.sse.common.Color;
+import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -142,7 +143,9 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
@Test
public void testWindAdditionReplication() throws InterruptedException {
- final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(3456),
+ TimePoint now = MillisecondsTimePoint.now();
+ trackedRace.setStartOfTrackingReceived(now);
+ final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(now.asMillis()+1234),
new KnotSpeedWithBearingImpl(13, new DegreeBearingImpl(234)));
WindSource webWindSource = new WindSourceImpl(WindSourceType.WEB);
trackedRace.recordWind(wind, webWindSource);
@@ -190,12 +193,14 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio
@Test
public void testReplicationOfLoadingOfStoredWindTrack() throws UnknownHostException, MongoException, InterruptedException {
+ TimePoint now = MillisecondsTimePoint.now();
+ trackedRace.setStartOfTrackingReceived(now);
MongoWindStore windStore = MongoWindStoreFactory.INSTANCE.getMongoWindStore(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(),
PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory());
WindSource webWindSource = new WindSourceImpl(WindSourceType.WEB);
WindTrack windTrack = windStore.getWindTrack(trackedRegatta.getRegatta().getName(), trackedRace, webWindSource, /* millisecondsOverWhichToAverage */ 10000,
/* delayForWindEstimationCacheInvalidation */ 10000);
- final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(3456),
+ final Wind wind = new WindImpl(new DegreePosition(2, 3), new MillisecondsTimePoint(now.asMillis()+1234),
new KnotSpeedWithBearingImpl(13, new DegreeBearingImpl(234)));
windTrack.add(wind);
Thread.sleep(1000); // give MongoDB time to read its own writes in a separate session
diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java
index eafaf3b1e0a..e9f8e91e88c 100644
--- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java
+++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java
@@ -101,6 +101,7 @@ public class WindByRaceLogTest {
service.apply(new TrackRegatta(raceIdentifier));
trackedRace = (DynamicTrackedRace) service.apply(new CreateTrackedRace(raceIdentifier, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,/* delayToLiveInMillis */ 5000,
/* millisecondsOverWhichToAverageWind */ 10000, /* millisecondsOverWhichToAverageSpeed */10000));
+ trackedRace.setStartOfTrackingReceived(MillisecondsTimePoint.now());
defaultFleet = Util.get(raceColumn.getFleets(), 0);
}
diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java
index 6ccd2bcc396..9aa44d096da 100644
--- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java
+++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java
@@ -90,11 +90,6 @@ public class RaceLogReplicator implements RaceColumnListener {
public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
}
- @Override
- public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
- return true;
- }
-
@Override
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
}
diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java
index eaa614b551e..8fb76bc3920 100644
--- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java
+++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java
@@ -62,11 +62,6 @@ public class RaceLogScoringReplicator implements RaceColumnListener {
public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) {
}
- @Override
- public boolean canAddRaceColumnToContainer(RaceColumn raceColumn) {
- return true;
- }
-
@Override
public void raceColumnAddedToContainer(RaceColumn raceColumn) {
}
diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java
index e0218afdb82..370826f1ecd 100755
--- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java
+++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java
@@ -42,6 +42,7 @@ import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
+import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
@@ -438,10 +439,6 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public void detachRaceLog(Serializable identifier) {
}
- @Override
- public void detachAllRaceLogs() {
- }
-
@Override
public void attachRaceLog(RaceLog raceLog) {
}
@@ -616,4 +613,12 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public RankingMetric getRankingMetric() {
return null;
}
+
+ @Override
+ public void attachRaceExecutionProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
+ }
+
+ @Override
+ public void detachRaceExecutionOrderProvider(RaceExecutionOrderProvider raceExecutionOrderProvider) {
+ }
}
diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java
index 6a56acbdfbe..3c1d8d300fc 100755
--- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java
+++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java
@@ -362,4 +362,46 @@ public class Util {
}
return null;
}
+
+ /**
+ * Returns the earlier {@link TimePoint} of a and b. If one of them is null and the other
+ * !null, the TimePoint that is not !null gets returned. If both are null,
+ * null is the result.
+ */
+ public static TimePoint getEarliestOfTimePoints(TimePoint a, TimePoint b) {
+ TimePoint result = null;
+ if (a != null && b != null) {
+ result = a.before(b) ? a : b;
+ } else {
+ result = (a != null && b == null) ? a : (a == null && b != null) ? b : null;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the latest {@link TimePoint} of a and b. If one of them is null and the other
+ * !null, the TimePoint that is not !null gets returned. If both are null,
+ * null is the result.
+ */
+ public static TimePoint getLatestOfTimePoints(TimePoint a, TimePoint b) {
+ TimePoint result = null;
+ if (a != null && b != null) {
+ result = a.after(b) ? a : b;
+ } else {
+ result = (a != null && b == null) ? a : (a == null && b != null) ? b : null;
+ }
+ return result;
+ }
+
+ /**
+ * Returns true if timePoint is after a an before b.
+ * If one of the parameters is null the method returns false.
+ */
+ public static boolean isTimePointInRangeOfTimePointsAandB(TimePoint timePoint, TimePoint a, TimePoint b) {
+ boolean result = false;
+ if (timePoint != null && a != null && b != null) {
+ result = timePoint.after(a) && timePoint.before(b);
+ }
+ return result;
+ }
}
\ No newline at end of file
diff --git a/mobile/com.sap.sailing.racecommittee.app/.classpath b/mobile/com.sap.sailing.racecommittee.app/.classpath
index 361be950b40..b32ef90cb7e 100644
--- a/mobile/com.sap.sailing.racecommittee.app/.classpath
+++ b/mobile/com.sap.sailing.racecommittee.app/.classpath
@@ -1,11 +1,13 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/com.sap.sailing.racecommittee.app/SimpleRaceIdentifierEscapingTest.launch b/mobile/com.sap.sailing.racecommittee.app/SimpleRaceIdentifierEscapingTest.launch
new file mode 100644
index 00000000000..5dcdc3de012
--- /dev/null
+++ b/mobile/com.sap.sailing.racecommittee.app/SimpleRaceIdentifierEscapingTest.launch
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/com.sap.sailing.racecommittee.app/build.gradle b/mobile/com.sap.sailing.racecommittee.app/build.gradle
index 644b342601b..84e5765a0bf 100644
--- a/mobile/com.sap.sailing.racecommittee.app/build.gradle
+++ b/mobile/com.sap.sailing.racecommittee.app/build.gradle
@@ -29,9 +29,13 @@ android {
}
androidTest {
- java.srcDirs = ["tests/src"]
- res.srcDirs = ["tests/res"]
- assets.srcDirs = ["tests/assets"]
+ java.srcDirs = ["tests-ui/src"]
+ res.srcDirs = ["tests-ui/res"]
+ assets.srcDirs = ["tests-ui/assets"]
+ }
+
+ test {
+ java.srcDirs = ["tests-jvm/src"]
}
}
@@ -83,14 +87,19 @@ dependencies {
/* all local jar-libs */
compile fileTree(dir: "libs", include: ["*.jar"])
+ /* JVM tests */
+ testCompile 'junit:junit:4.12'
+ testCompile "org.mockito:mockito-core:1.9.5"
+
/* UI testing with espresso */
+ androidTestCompile "com.android.support:support-annotations:22.1.1"
androidTestCompile "com.android.support.test:runner:0.2"
androidTestCompile "com.android.support.test:rules:0.2"
androidTestCompile "com.android.support.test.espresso:espresso-core:2.1"
- androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.1') {
- exclude group: 'com.android.support', module: 'appcompat'
- exclude group: 'com.android.support', module: 'support-v4'
- exclude module: 'recyclerview-v7'
+ androidTestCompile ("com.android.support.test.espresso:espresso-contrib:2.1") {
+ exclude group: "com.android.support", module: "appcompat"
+ exclude group: "com.android.support", module: "support-v4"
+ exclude module: "recyclerview-v7"
}
}
diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java
index 152965cad8c..0ab155c64b0 100644
--- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java
+++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java
@@ -41,9 +41,13 @@ public class FleetIdentifierImpl implements FleetIdentifier {
return fragment.replace("\\", "\\\\").replace(".", "\\.");
}
+ /**
+ * @return a triple consisting of regattaLikeName/raceColumnName/fleetName; note that the series name which is
+ * encoded in the escapedId in second place (zero-based index 1) is silently ignored
+ */
public static Triple unescape(String escapedId) {
int arrayIndex = 0;
- StringBuilder[] split = new StringBuilder[3];
+ StringBuilder[] split = new StringBuilder[4];
for (int i=0; i(leaderboardName, raceColumnName, fleetName);
}
diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java
index 128ade4f94e..55940938aee 100644
--- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java
+++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdater.java
@@ -1,10 +1,5 @@
package com.sap.sailing.racecommittee.app.utils.autoupdate;
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
@@ -12,14 +7,18 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.support.v7.app.AlertDialog;
-
import com.sap.sailing.android.shared.logging.ExLog;
import com.sap.sailing.android.shared.util.FileHandlerUtils;
import com.sap.sailing.racecommittee.app.AppPreferences;
import com.sap.sailing.racecommittee.app.R;
-import com.sap.sailing.racecommittee.app.ui.activities.SettingsActivity;
+import com.sap.sailing.racecommittee.app.ui.activities.PreferenceActivity;
import com.sap.sailing.racecommittee.app.ui.fragments.preference.GeneralPreferenceFragment;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
public class AutoUpdater {
private static final String TAG = AutoUpdater.class.getName();
@@ -70,8 +69,8 @@ public class AutoUpdater {
OnClickListener okListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- Intent intent = new Intent(context, SettingsActivity.class);
- intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, GeneralPreferenceFragment.class.getName());
+ Intent intent = new Intent(context, PreferenceActivity.class);
+ intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeneralPreferenceFragment.class.getName());
context.startActivity(intent);
dialog.dismiss();
}
diff --git a/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java
new file mode 100755
index 00000000000..fbb81c2f423
--- /dev/null
+++ b/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java
@@ -0,0 +1,71 @@
+package com.sap.sailing.racecommittee.app.domain.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.sap.sailing.domain.base.Fleet;
+import com.sap.sailing.domain.base.SeriesBase;
+import com.sap.sailing.domain.base.impl.FleetImpl;
+import com.sap.sailing.domain.base.racegroup.RaceGroup;
+import com.sap.sailing.domain.base.racegroup.impl.RaceGroupImpl;
+import com.sap.sailing.racecommittee.app.domain.ManagedRaceIdentifier;
+import com.sap.sse.common.Util.Triple;
+
+/**
+ * This test verifies the algorithm used to escape and unescape SimpleRaceIdentifier components as used in
+ * FleetIfentifierImpl.
+ *
+ * @author Axel Uhl (D043530)
+ *
+ */
+public class SimpleRaceIdentifierEscapingTest {
+ @Test
+ public void testUnescapingSimpleRaceIdentifier() {
+ test("Leaderboard with trailing backslash and dot\\.", "empty", "fleet", "raceColumn");
+ test("Leaderboard\\.with\\.two dots", "empty", "fleet", "raceColumn");
+ test("Leaderboard with double backslash\\\\", "empty", "fleet", "raceColumn");
+ test("Leaderboard with double backslash and dot\\\\.", "empty", "fleet", "raceColumn");
+ test("\\Leaderboard with leading backslash", "empty", "fleet", "raceColumn");
+ test("\\.Leaderboard with leading backslash and dot", "empty", "fleet", "raceColumn");
+ test("\\\\.Leaderboard with leading double backslash and dot", "empty", "fleet", "raceColumn");
+ test("\\\\.Leaderboard with leading double backslash and two dots", "empty", "fleet", "raceColumn");
+ }
+
+ private void test(String leaderboardName, String empty, String fleetName, String raceColumnName) {
+ final String marshalled = build(leaderboardName, empty, fleetName, raceColumnName);
+ final Triple parsed = FleetIdentifierImpl.unescape(marshalled);
+ assertEquals(leaderboardName, parsed.getA());
+ assertEquals(raceColumnName, parsed.getB());
+ assertEquals(fleetName, parsed.getC());
+ }
+
+ private String build(final String leaderboardName, final String seriesName, final String fleetName, final String raceColumnName) {
+ final Fleet fleet = new FleetImpl(fleetName);
+ final SeriesBase series = new SeriesBase() {
+ private static final long serialVersionUID = 1139740910228343375L;
+
+ @Override
+ public String getName() {
+ return seriesName;
+ }
+
+ @Override
+ public void setName(String newName) {
+ }
+
+ @Override
+ public boolean isMedal() {
+ return false;
+ }
+
+ @Override
+ public Iterable extends Fleet> getFleets() {
+ return null;
+ }
+ };
+ RaceGroup raceGroup = new RaceGroupImpl(leaderboardName, /* displayName */ null, /* boatClass */ null, /* courseArea */ null, /* series */ null, /* regattaConfiguration */ null);
+ ManagedRaceIdentifier identifier = new ManagedRaceIdentifierImpl(raceColumnName, fleet, series, raceGroup);
+ return identifier.getId();
+ }
+}
diff --git a/mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ApplicationTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ApplicationTest.java
similarity index 100%
rename from mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ApplicationTest.java
rename to mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ApplicationTest.java
diff --git a/mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java
similarity index 100%
rename from mobile/com.sap.sailing.racecommittee.app/tests/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java
rename to mobile/com.sap.sailing.racecommittee.app/tests-ui/src/com/sap/sailing/racecommittee/app/ui/activities/LoginActivityTest.java