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

This commit is contained in:
fmittag
2016-06-28 18:34:43 +02:00
34 changed files with 316 additions and 156 deletions
@@ -15,7 +15,8 @@ public enum FleetColors {
PURPLE (212, 28, 175, 0),
NEON_GREEN (118, 255, 33, 0),
ORANGE (255, 174, 23, 0),
WHITE (250, 250, 250, 0);
WHITE (250, 250, 250, 0),
PINK(255,105,180, 0);
private Color color;
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.racelog.state.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -15,6 +16,7 @@ import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogDependentStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogPassChangeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl;
import com.sap.sailing.domain.abstractlog.race.state.RaceState;
@@ -22,6 +24,7 @@ import com.sap.sailing.domain.abstractlog.race.state.RaceStateChangedListener;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener;
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedureFactory;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.RacingProcedureFactoryImpl;
import com.sap.sailing.domain.base.configuration.ConfigurationLoader;
import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
@@ -32,13 +35,24 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class DependentRaceStateTest {
private static class RaceStateImplWithPublicRaceStateToObserve extends RaceStateImpl {
public RaceStateImplWithPublicRaceStateToObserve(RaceLogResolver raceLogResolver, RaceLog raceLog,
AbstractLogEventAuthor author, RacingProcedureFactory procedureFactory) {
super(raceLogResolver, raceLog, author, procedureFactory);
}
@Override
public ReadonlyRaceState getRaceStateToObserve() {
return super.getRaceStateToObserve();
}
}
private RaceLog raceLogA;
private RaceLog raceLogB;
private RaceLog raceLogC;
private RaceState stateA;
private RaceState stateB;
private RaceStateImplWithPublicRaceStateToObserve stateB;
private RaceState stateC;
private RaceStateChangedListener listenerA;
@@ -56,17 +70,13 @@ public class DependentRaceStateTest {
public void setUp() {
author = new LogEventAuthorImpl("Test", 1);
configuration = new EmptyRegattaConfiguration();
raceLogA = new RaceLogImpl("raceLogA");
raceLogB = new RaceLogImpl("raceLogB");
raceLogC = new RaceLogImpl("raceLogC");
listenerA = mock(RaceStateChangedListener.class);
listenerB = mock(RaceStateChangedListener.class);
listenerC = mock(RaceStateChangedListener.class);
nowMock = mock(TimePoint.class);
raceLogResolver = new RaceLogResolver() {
@Override
public RaceLog resolve(SimpleRaceLogIdentifier identifier) {
@@ -79,14 +89,9 @@ public class DependentRaceStateTest {
}
}
};
stateA = new RaceStateImpl(raceLogResolver, raceLogA, author, new RacingProcedureFactoryImpl(author,
configuration));
stateB = new RaceStateImpl(raceLogResolver, raceLogB, author, new RacingProcedureFactoryImpl(author,
configuration));
stateC = new RaceStateImpl(raceLogResolver, raceLogC, author, new RacingProcedureFactoryImpl(author,
configuration));
stateA = new RaceStateImpl(raceLogResolver, raceLogA, author, new RacingProcedureFactoryImpl(author, configuration));
stateB = new RaceStateImplWithPublicRaceStateToObserve(raceLogResolver, raceLogB, author, new RacingProcedureFactoryImpl(author, configuration));
stateC = new RaceStateImpl(raceLogResolver, raceLogC, author, new RacingProcedureFactoryImpl(author, configuration));
stateA.addChangedListener(listenerA);
stateB.addChangedListener(listenerB);
stateC.addChangedListener(listenerC);
@@ -96,13 +101,10 @@ public class DependentRaceStateTest {
public void testCorrectAmountOfStartTimeChanges() {
raceLogC.add(new RaceLogDependentStartTimeEventImpl(nowMock, nowMock, author, "12", 12,
new SimpleRaceLogIdentifierImpl("B", "", ""), new MillisecondsDurationImpl(5000), RaceLogRaceStatus.SCHEDULED));
raceLogB.add(new RaceLogDependentStartTimeEventImpl(nowMock, nowMock, author, "12", 12,
new SimpleRaceLogIdentifierImpl("A", "", ""), new MillisecondsDurationImpl(5000), RaceLogRaceStatus.SCHEDULED));
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, now, author, "12", 12, new MillisecondsTimePoint(5000), RaceLogRaceStatus.SCHEDULED));
verify(listenerC, times(3)).onStartTimeChanged(stateC);
verify(listenerB, times(2)).onStartTimeChanged(stateB);
verify(listenerA, times(1)).onStartTimeChanged(stateA);
@@ -131,17 +133,89 @@ public class DependentRaceStateTest {
public void testCorrectAmountOfStartTimeChanges2() {
raceLogC.add(new RaceLogDependentStartTimeEventImpl(nowMock, nowMock, author, "12", 12,
new SimpleRaceLogIdentifierImpl("B", "", ""), new MillisecondsDurationImpl(5000), RaceLogRaceStatus.SCHEDULED));
raceLogB.add(new RaceLogDependentStartTimeEventImpl(nowMock, nowMock, author, "12", 12,
new SimpleRaceLogIdentifierImpl("A", "", ""), new MillisecondsDurationImpl(5000), RaceLogRaceStatus.SCHEDULED));
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, now, author, "12", 12, new MillisecondsTimePoint(5000), RaceLogRaceStatus.SCHEDULED));
raceLogB.add(new RaceLogStartTimeEventImpl(now, now, author, "12", 12, new MillisecondsTimePoint(20000), RaceLogRaceStatus.SCHEDULED));
verify(listenerA, times(1)).onStartTimeChanged(stateA);
verify(listenerB, times(3)).onStartTimeChanged(stateB);
verify(listenerC, times(4)).onStartTimeChanged(stateC);
}
/**
* See bug 3740; when the current pass has an absolute start time, adding a relative start time
* to a prior pass late must not lead to the race state observing the race state of the race that
* the no longer valid dependent start time specification depended upon.
*/
@Test
public void testLateDeliveryOfDependentStartTimeForPriorPass() {
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, now, author, /* ID */ "1", /* pass */ 0, new MillisecondsTimePoint( 5000), RaceLogRaceStatus.SCHEDULED));
raceLogB.add(new RaceLogStartTimeEventImpl(now, now, author, /* ID */ "2", /* pass */ 0, new MillisecondsTimePoint(10000), RaceLogRaceStatus.SCHEDULED));
// now B enters a new pass:
raceLogB.add(new RaceLogPassChangeEventImpl(now.plus(1000), author, /* pass */ 1));
// this is expected to reset B's start time:
assertNull(stateB.getStartTime());
// now B receives an absolute start time definition for pass 1 (the second pass):
raceLogB.add(new RaceLogStartTimeEventImpl(now.plus(1000), now.plus(1000), author, /* ID */ "3", /* pass */ 1, new MillisecondsTimePoint(20000), RaceLogRaceStatus.SCHEDULED));
assertEquals(new MillisecondsTimePoint( 5000), stateA.getStartTime());
assertEquals(new MillisecondsTimePoint(20000), stateB.getStartTime());
// here comes a late event for B's pass 0, setting a relative start time; it is expected to not
// have any effect on B's current start time because B is already in pass 1; furthermore, it is
// expected to not have an effect on the observing relationship. No observer relationship is to
// be established from B to A; in particular, updating A's start time is expected to leave B's
// start time unmodified.
raceLogB.add(new RaceLogDependentStartTimeEventImpl(now.plus(500), now.plus(500), author, /* ID */ "4", /* pass */ 0,
new SimpleRaceLogIdentifierImpl("A", "", ""), new MillisecondsDurationImpl(5000), RaceLogRaceStatus.SCHEDULED));
// verify that B's race state is *not* observing A's race state
assertNull(stateB.getRaceStateToObserve());
// B's absolute start time in pass 1 is expected to remain unchanged
assertEquals(new MillisecondsTimePoint(20000), stateB.getStartTime());
// moving A's start time is expected to NOT move B's start time:
raceLogA.add(new RaceLogStartTimeEventImpl(now.plus(10000), now.plus(10000), author, /* ID */ "5", /* pass */ 0,
/* new start time */ new MillisecondsTimePoint(20000), RaceLogRaceStatus.SCHEDULED));
assertEquals(new MillisecondsTimePoint(20000), stateA.getStartTime());
assertEquals(new MillisecondsTimePoint(20000), stateB.getStartTime()); // no update to B's start time expected
}
/**
* See bug 3740; the current pass is set up such that it has a relative start time; then we assume
* that an absolute start time for a prior pass is delivered late. Now it's important that this does
* not lead to the dependent race state stopping to observe the race state it depends upon.
*/
@Test
public void testLateDeliveryOfAbsoluteStartTimeForPrioPass() {
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, now, author, /* ID */ "1", /* pass */ 0, new MillisecondsTimePoint( 5000), RaceLogRaceStatus.SCHEDULED));
raceLogB.add(new RaceLogStartTimeEventImpl(now, now, author, /* ID */ "2", /* pass */ 0, new MillisecondsTimePoint(10000), RaceLogRaceStatus.SCHEDULED));
// now B enters a new pass:
raceLogB.add(new RaceLogPassChangeEventImpl(now.plus(1000), author, /* pass */ 1));
// this is expected to reset B's start time:
assertNull(stateB.getStartTime());
// now B receives a start time definition relative to that of A:
raceLogB.add(new RaceLogDependentStartTimeEventImpl(now.plus(2000), now.plus(2000), author, /* ID */ "3", /* pass */ 1,
new SimpleRaceLogIdentifierImpl("A", "", ""), new MillisecondsDurationImpl(5000), RaceLogRaceStatus.SCHEDULED));
assertEquals(new MillisecondsTimePoint( 5000), stateA.getStartTime());
assertEquals(stateA.getStartTime().plus(5000), stateB.getStartTime()); // B is expected to have a start time 5s after that of A due to its relative start time
// moving A's start time is expected to move B's start time:
raceLogA.add(new RaceLogStartTimeEventImpl(now.plus(10000), now.plus(10000), author, /* ID */ "4", /* pass */ 0,
/* new start time */ new MillisecondsTimePoint(20000), RaceLogRaceStatus.SCHEDULED));
assertEquals(new MillisecondsTimePoint(20000), stateA.getStartTime());
assertEquals(stateA.getStartTime().plus(5000), stateB.getStartTime()); // B is expected to have a start time 5s after that of A due to its relative start time
// here comes a late event for B's pass 0, updating the absolute start time; it is expected to not
// have any effect on B's current start time because B is already in pass 1; furthermore, it is
// expected to not have an effect on the observing relationship: when updating A's start time
// later, B's relative start time is expected to still update.
raceLogB.add(new RaceLogStartTimeEventImpl(now.plus(10), now.plus(10), author, /* ID */ "5", /* pass */ 0, new MillisecondsTimePoint(11000), RaceLogRaceStatus.SCHEDULED));
assertEquals(stateA.getStartTime().plus(5000), stateB.getStartTime()); // B is expected to still have a start time 5s after that of A due to its relative start time
// move A's start time to then check that B's still relative start time has moved as well, testing the
// continued existence of the observer relationship:
raceLogA.add(new RaceLogStartTimeEventImpl(now.plus(11000), now.plus(11000), author, /* ID */ "6", /* pass */ 0,
/* new start time */ new MillisecondsTimePoint(30000), RaceLogRaceStatus.SCHEDULED));
assertEquals(new MillisecondsTimePoint(30000), stateA.getStartTime());
assertEquals(stateA.getStartTime().plus(5000), stateB.getStartTime()); // B is expected to still have a start time 5s after that of A due to its relative start time
}
}
@@ -7,6 +7,14 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* The result of a {@link StartTimeFinder} that tells about the dependencies through which the start time
* may be resolved from other races, reasons for the failure to resolve a start time and the actual start
* time as obtained immediately or transitively through a chain of dependencies.
*
* @author Axel Uhl (D043530)
*
*/
public class StartTimeFinderResult {
public static enum ResolutionFailed { RACE_LOG_UNRESOLVED, CYCLIC_DEPENDENCY, NO_START_TIME_SET };
@@ -40,6 +48,9 @@ public class StartTimeFinderResult {
this(/* racesDependingOn */ Collections.<SimpleRaceLogIdentifier>emptyList(), startTime, startTimeDiff);
}
/**
* @return {@code null} if the start time was resolved; a reason for failure to resolve otherwise
*/
public ResolutionFailed getResolutionFailed() {
return resolutionFailed;
}
@@ -239,7 +239,6 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
@Override
public void forceUpdate() {
super.update();
registerListenerOnDependentRaceIfDependentStartTime(Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap());
}
}
@@ -9,6 +9,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogChangedListener;
import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.ConfirmedFinishPositioningListFinder;
@@ -218,32 +219,24 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
} else {
dependentRaceStatesAndMe = dependentRaceStates;
}
registerListenerOnDependentRaceIfDependentStartTime(dependentRaceStatesAndMe);
}
protected void registerListenerOnDependentRaceIfDependentStartTime(Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
// Check whether the latest known StartTimeEvent is a non-dependent or dependent start time in case of a
// dependent startTime setup listeners
this.raceLog.lockForRead();
try {
for (RaceLogEvent event : this.raceLog.getFixesDescending()) {
if (event instanceof RaceLogStartTimeEvent) {
break;
} else if (event instanceof RaceLogDependentStartTimeEvent) {
setupListenersOnDependentRace(event, dependentRaceStates);
break;
}
}
} finally {
this.raceLog.unlockAfterRead();
}
adjustObserverForRelativeStartTime();
}
protected ReadonlyRaceState getRaceStateToObserve() {
return raceStateToObserve;
}
protected RacingProcedureType determineInitialProcedureType() {
// Let's ensure there is a valid RacingProcedureType set, since a RaceState cannot live without a
// RacingProcedure we need to have a fallback
RegattaConfiguration configuration = getConfiguration();
RacingProcedureType inRaceLogType = racingProcedureAnalyzer.analyze();
return determineInitialProcedureType(inRaceLogType);
}
private RacingProcedureType determineInitialProcedureType(RacingProcedureType inRaceLogType) {
RegattaConfiguration configuration = getConfiguration();
if (inRaceLogType != RacingProcedureType.UNKNOWN) {
return inRaceLogType;
} else {
@@ -366,28 +359,40 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
@Override
public void eventAdded(RaceLogEvent event) {
// FIXME the start time analyzer should be used to find out about the new start time dependency situation
if (event instanceof RaceLogDependentStartTimeEvent) {
// FIXME add the listener only if the dependent start time event belongs to the current pass
setupListenersOnDependentRace(event, Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap());
} else if (event instanceof RaceLogStartTimeEvent) {
// FIXME only remove the change listener if the RaceLogStartTimeEvent belongs to the current pass
if (raceStateToObserve != null) {
raceStateToObserve.removeChangedListener(raceStateToObserveListener);
raceStateToObserve = null;
}
// if a pass change or a start time-setting event, the observer relation to a race state of a
// race on which this race state's race's start time has depended or now depends needs to be
// re-assessed, and the observer relation needs to be established or canceled, respectively.
if (event instanceof RaceLogDependentStartTimeEvent ||
event instanceof RaceLogStartTimeEvent ||
event instanceof RaceLogPassChangeEvent) {
adjustObserverForRelativeStartTime();
}
update();
}
private void setupListenersOnDependentRace(RaceLogEvent event, Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
private void adjustObserverForRelativeStartTime() {
final StartTimeFinderResult startTimeAnalysisResult = startTimeAnalyzer.analyze();
adjustObserverForRelativeStartTime(startTimeAnalysisResult);
}
private void adjustObserverForRelativeStartTime(final StartTimeFinderResult startTimeAnalysisResult) {
if (startTimeAnalysisResult.isDependentStartTime()) {
setupListenersOnDependentRace(startTimeAnalysisResult, Collections.<SimpleRaceLogIdentifier, ReadonlyRaceState>emptyMap());
} else if (raceStateToObserve != null) {
raceStateToObserve.removeChangedListener(raceStateToObserveListener);
raceStateToObserve = null;
}
}
private void setupListenersOnDependentRace(StartTimeFinderResult startTimeAnalysisResult, Map<SimpleRaceLogIdentifier, ReadonlyRaceState> dependentRaceStates) {
assert startTimeAnalysisResult.isDependentStartTime();
assert !Util.isEmpty(startTimeAnalysisResult.getDependingOnRaces());
if (raceStateToObserve != null) {
// Remove previous listeners
raceStateToObserve.removeChangedListener(raceStateToObserveListener);
raceStateToObserve = null;
}
RaceLogDependentStartTimeEvent dependentStartTimeEvent = (RaceLogDependentStartTimeEvent) event;
final SimpleRaceLogIdentifier dependentOnRaceIdentifier = dependentStartTimeEvent.getDependentOnRaceIdentifier();
final SimpleRaceLogIdentifier dependentOnRaceIdentifier = startTimeAnalysisResult.getDependingOnRaces().iterator().next();
if (dependentRaceStates.containsKey(dependentOnRaceIdentifier)) {
raceStateToObserve = dependentRaceStates.get(dependentOnRaceIdentifier);
raceStateToObserve.addChangedListener(raceStateToObserveListener);
@@ -415,6 +420,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
RacingProcedureType type = racingProcedureAnalyzer.analyze();
if (!Util.equalsWithNull(cachedRacingProcedureType, type) && type != RacingProcedureType.UNKNOWN) {
cachedRacingProcedureType = type;
cachedRacingProcedureTypeNoFallback = determineInitialProcedureType(type);
recreateRacingProcedure();
changedListeners.onRacingProcedureChanged(this);
}
@@ -431,6 +437,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
changedListeners.onAdvancePass(this);
// reset racing procedure to force recreate on next event!
cachedRacingProcedureType = null;
cachedRacingProcedureTypeNoFallback = null;
}
StartTimeFinderResult startTimeFinderResult = startTimeAnalyzer.analyze();
@@ -438,6 +445,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
cachedStartTimeFinderResult = startTimeFinderResult;
changedListeners.onStartTimeChanged(this);
}
adjustObserverForRelativeStartTime(startTimeFinderResult);
TimePoint finishingTime = finishingTimeAnalyzer.analyze();
if (!Util.equalsWithNull(cachedFinishingTime, finishingTime)) {
@@ -6,7 +6,7 @@ import com.sap.sailing.domain.abstractlog.BaseLogAnalyzer;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sse.common.WithID;
@@ -29,8 +29,8 @@ public class OpenEndedDeviceMappingFinder extends BaseLogAnalyzer<RegattaLog, Re
@Override
protected Serializable performAnalysis() {
for (RegattaLogEvent regattaLogEvent : log.getUnrevokedEvents()) {
if (regattaLogEvent instanceof RegattaLogDeviceCompetitorMappingEvent) {
RegattaLogDeviceCompetitorMappingEvent deviceMappingEvent = (RegattaLogDeviceCompetitorMappingEvent) regattaLogEvent;
if (regattaLogEvent instanceof RegattaLogDeviceMappingEvent<?>) {
RegattaLogDeviceMappingEvent<?> deviceMappingEvent = (RegattaLogDeviceMappingEvent<?>) regattaLogEvent;
if (deviceMappingEvent.getMappedTo().equals(mappedTo) &&
deviceMappingEvent.getDevice().getStringRepresentation().equals(deviceUuid)) {
return deviceMappingEvent.getId();
@@ -6,11 +6,10 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.datamining.annotations.Connector;
public interface Competitor extends Named, WithID, IsManagedByCache<SharedDomainFactory> {
public interface Competitor extends NamedWithID, IsManagedByCache<SharedDomainFactory> {
@Connector(messageKey="Team", ordinal=9)
Team getTeam();
@@ -1,8 +1,7 @@
package com.sap.sailing.domain.base;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
public interface ControlPoint extends WithID, Named {
public interface ControlPoint extends NamedWithID {
Iterable<Mark> getMarks();
}
@@ -3,16 +3,15 @@ package com.sap.sailing.domain.base;
import java.util.UUID;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
/**
* A named area in a sailing {@link Venue} where races take place. Usually, the areas carry names according to the NATO
* alphabet, such as "Alpha", "Bravo" or "Charlie".
* alphabet, such as "Alpha", "Bravo" or "Charly".
*
* @author Axel Uhl (D043530)
*
*/
public interface CourseArea extends Named, WithID, IsManagedByCache<SharedDomainFactory> {
public interface CourseArea extends NamedWithID, IsManagedByCache<SharedDomainFactory> {
UUID getId();
}
@@ -5,9 +5,8 @@ import java.util.Locale;
import java.util.Map;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.Named;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.WithID;
import com.sap.sse.common.media.ImageSize;
import com.sap.sse.shared.media.WithMedia;
@@ -15,7 +14,7 @@ import com.sap.sse.shared.media.WithMedia;
* Base interface for an Event consisting of all static information, which might be shared
* by the server and an Android application.
*/
public interface EventBase extends Named, WithDescription, Renamable, WithID, WithMedia {
public interface EventBase extends NamedWithID, WithDescription, Renamable, WithMedia {
void setDescription(String description);
@@ -3,9 +3,9 @@ package com.sap.sailing.domain.base;
import java.util.UUID;
import com.sap.sailing.domain.common.Renamable;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
public interface LeaderboardGroupBase extends Renamable, WithID {
public interface LeaderboardGroupBase extends Renamable, NamedWithID {
UUID getId();
String getDescription();
void setDescriptiom(String description);
@@ -2,7 +2,6 @@ package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.WithID;
/**
@@ -12,7 +11,7 @@ import com.sap.sse.common.WithID;
* @author Axel Uhl (D043530)
*
*/
public interface Mark extends WithID, ControlPoint, IsManagedByCache<SharedDomainFactory> {
public interface Mark extends ControlPoint, IsManagedByCache<SharedDomainFactory> {
public String getColor();
public String getShape();
public String getPattern();
@@ -1,13 +1,13 @@
package com.sap.sailing.domain.base;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
/**
* A side line in a race course
* @author Frank (C5163874)
*
* @author Frank Mittag (C5163874)
*
*/
public interface Sideline extends Named, WithID {
public interface Sideline extends NamedWithID {
Iterable<Mark> getMarks();
}
@@ -3,8 +3,7 @@ package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
/**
* Waypoints constitute {@link Course}s and demarcate their {@link Leg}s. A waypoint's position is defined by a
@@ -14,7 +13,7 @@ import com.sap.sse.common.WithID;
* @author Axel Uhl (d043530)
*
*/
public interface Waypoint extends Named, WithID, IsManagedByCache<SharedDomainFactory> {
public interface Waypoint extends NamedWithID, IsManagedByCache<SharedDomainFactory> {
ControlPoint getControlPoint();
Iterable<Mark> getMarks();
@@ -2,7 +2,7 @@ package com.sap.sailing.domain.tractracadapter;
import java.util.UUID;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
import com.tractrac.model.lib.api.event.IEvent;
import com.tractrac.model.lib.api.route.IControlPoint;
@@ -14,14 +14,12 @@ import com.tractrac.model.lib.api.route.IControlPoint;
* @author Axel Uhl (D043530)
*
*/
public interface TracTracControlPoint extends WithID {
public interface TracTracControlPoint extends NamedWithID {
/**
* Refining the return type helps in looking up a control point by its UUID in {@link IEvent}.
*/
UUID getId();
String getName();
String getMetadata();
String getShortName();
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.tractracadapter.TracTracControlPoint;
import com.tractrac.model.lib.api.route.IControl;
public class ControlPointAdapter extends AbstractWithID implements TracTracControlPoint {
private static final long serialVersionUID = 1012632574166553433L;
private final IControl controlPoint;
public ControlPointAdapter(IControl controlPoint) {
@@ -3,8 +3,7 @@ package com.sap.sailing.domain.base;
import java.io.Serializable;
import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash;
import com.sap.sse.common.Named;
import com.sap.sse.common.WithID;
import com.sap.sse.common.NamedWithID;
/**
* Tells the {@link BoatClass boat class} and the {@link Course course} for a single race that is usually part of a
@@ -18,7 +17,7 @@ import com.sap.sse.common.WithID;
* @author Axel Uhl (D043530)
*
*/
public interface RaceDefinition extends Named, WithID {
public interface RaceDefinition extends NamedWithID {
BoatClass getBoatClass();
Course getCourse();
@@ -12,9 +12,8 @@ import com.sap.sailing.domain.regattalike.IsRegattaLike;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sse.common.Named;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.WithID;
/**
* The name shall be unique across all regattas tracked concurrently. In particular, if you want to keep apart regattas
@@ -23,7 +22,7 @@ import com.sap.sse.common.WithID;
* @author Axel Uhl (d043530)
*
*/
public interface Regatta extends Named, WithID, IsRegattaLike, HasRaceColumnsAndRegattaLike {
public interface Regatta extends NamedWithID, IsRegattaLike, HasRaceColumnsAndRegattaLike {
ScoringScheme getScoringScheme();
/**
@@ -140,6 +140,11 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
raceStateBasedStartTimeChangedListener = createRaceStateStartTimeChangeListener();
// the race states are transient, therefore not serialized; they are re-constructed here when asked for;
// the transient raceStateBasedStartTimeChangedListener is re-added here, like it was in attachRaceLog(...)
for (final RaceLog raceLog : attachedRaceLogs.values()) {
getRaceState(raceLog).addChangedListener(raceStateBasedStartTimeChangedListener);
}
listeners = new HashSet<RaceChangeListener>();
logListener = new DynamicTrackedRaceLogListener(this);
courseDesignChangedListeners = new HashSet<>();
@@ -905,7 +905,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* and not {@link Object}.
*/
private final String updateStartOfRaceCacheFieldsMonitor = "";
protected void updateStartOfRaceCacheFields() {
protected void updateStartOfRaceCacheFields() {
synchronized (updateStartOfRaceCacheFieldsMonitor) {
TimePoint newStartTime = null;
TimePoint newStartTimeWithoutInferenceFromStartMarkPassings = null;
@@ -262,6 +262,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
while (table.getColumnCount() > 0) {
table.removeColumn(0);
}
regattaOverviewDataProvider.removeDataDisplay(table);
} else {
sortInfos.add(new ColumnSortInfo(lastUpdateColumn, false));
}
@@ -544,10 +544,10 @@ gapChangeSinceLegStartInSeconds=Gap Change
gapChangeSinceLegStartInSecondsTooltip=The change of the gap to the leader since the last leg start.\nA negative number means that the gap was decreased;\na positive number means that it was increased.
sideToWhichMarkAtLegStartWasRounded=Mark rounded to
sideToWhichMarkAtLegStartWasRoundedTooltip=Side to which the mark at leg start was rounded.
raceIsLive=Race {0} is live
racesAreLive=Races {0} are live
regattaIsLive=Regatta {0} is live
regattasAreLive=Regattas {0} are live
raceIsLive=Live race: {0}
racesAreLive=Live races: {0}
regattaIsLive=Live regatta: {0}
regattasAreLive=Live regattas: {0}
knotsUnit=kts
knotsValue={0,number,#0.0} kts
knotsRange={0,number,#0.0} - {1,number,#0.0} kts
@@ -538,10 +538,10 @@ gapChangeSinceLegStartInSeconds=Änderung Abstand zum Führenden
gapChangeSinceLegStartInSecondsTooltip=Die Änderung des Abstands zum Führenden seit dem letzten Schenkelstart.\nEine negative Zahl bedeutet,\ndass sich der Abstand verringert hat; eine positive Zahl bedeutet, dass er sich vergrößert hat.
sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an
sideToWhichMarkAtLegStartWasRoundedTooltip=Seite, an der die Bahnmarke am Schenkelstart gerundet wurde
raceIsLive=Rennen {0} ist live
racesAreLive=Rennen {0} sind live
regattaIsLive=Regatta {0} ist live
regattasAreLive=Regatten {0} sind live
raceIsLive=Live-Rennen: {0}
racesAreLive=Live-Rennen: {0}
regattaIsLive=Live Regatta: {0}
regattasAreLive=Live-Regatten: {0}
scoringSchemeHighPointFirstGetsOne=High Point System, Gewinner erhält 1 Punkt
scoringSchemeHighPointFirstGetsTen=High Point System, Gewinner erhält 10 Punkte
knotsUnit=kn
@@ -59,6 +59,7 @@ import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.Mile;
import com.sap.sailing.domain.common.RaceIdentifier;
@@ -97,6 +98,7 @@ import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.filter.BinaryOperator;
import com.sap.sse.common.filter.Filter;
import com.sap.sse.common.filter.FilterSet;
@@ -3258,17 +3260,16 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
public String getLiveRacesText() {
String result = "";
List<com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis());
List<Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis());
boolean isMeta = leaderboard.type.isMetaLeaderboard();
if (!liveRaces.isEmpty()) {
if (liveRaces.size() == 1) {
com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace = liveRaces.get(0);
String text = "'" + liveRace.getA().getRaceColumnName() + "'";
String text = getLiveRaceText(liveRaces.get(0), isMeta);
result = isMeta ? stringMessages.regattaIsLive(text) : stringMessages.raceIsLive(text);
} else {
String names = "";
for (com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace : liveRaces) {
names += "'" + liveRace.getA().getRaceColumnName() + "', ";
for (Pair<RaceColumnDTO, FleetDTO> liveRace : liveRaces) {
names += getLiveRaceText(liveRace, isMeta) + ", ";
}
// remove last ", "
names = names.substring(0, names.length() - 2);
@@ -3278,6 +3279,12 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
return result;
}
private String getLiveRaceText(Pair<RaceColumnDTO, FleetDTO> liveRace, boolean isMeta) {
String raceName = liveRace.getA().getRaceColumnName(), fleetName = liveRace.getB().getName();
boolean isDefaultFleet = LeaderboardNameConstants.DEFAULT_FLEET_NAME.equals(fleetName);
return raceName + ((isDefaultFleet || isMeta) ? "" : (" (" + liveRace.getB().getName() + ")"));
}
@Override
public String getDependentCssClassName() {
return "leaderboard";
@@ -38,8 +38,10 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogCloseOpenEndedDeviceMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.impl.OpenEndedDeviceMappingFinder;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
@@ -82,6 +84,8 @@ import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.FlatGPSFixJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.MarkJsonSerializerWithPosition;
import com.sap.sse.common.Named;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
@@ -366,7 +370,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
DomainFactory domainFactory = getService().getDomainObjectFactory().getBaseDomainFactory();
AbstractLogEventAuthor author = new LogEventAuthorImpl(AbstractLogEventAuthor.NAME_COMPATIBILITY,
AbstractLogEventAuthor.PRIORITY_COMPATIBILITY);
RegattaLogDeviceCompetitorMappingEventImpl event;
RegattaLogDeviceMappingEventImpl<? extends Named> event;
JSONObject requestObject;
try {
logger.fine("Post issued to " + this.getClass().getName());
@@ -380,7 +384,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
}
final TimePoint now = MillisecondsTimePoint.now();
String competitorId = (String) requestObject.get(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING);
// TODO alternatively, check for a markId here and then produce a RegattaLogDeviceMarkMappingEventImpl
String markId = (String) requestObject.get(DeviceMappingConstants.JSON_MARK_ID_AS_STRING);
String deviceUuid = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_UUID);
Long fromMillis = (Long) requestObject.get(DeviceMappingConstants.JSON_FROM_MILLIS);
@@ -388,33 +392,48 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
// String deviceType = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_TYPE);
// String pushDeviceId = (String) requestObject.get(DeviceMappingConstants.JSON_PUSH_DEVICE_ID);
if (competitorId == null || deviceUuid == null || fromMillis == null) {
if ((competitorId == null && markId == null) || deviceUuid == null || fromMillis == null) {
// || deviceType == null
logger.warning("Invalid JSON body in request");
return Response.status(Status.BAD_REQUEST).entity("Invalid JSON body in request")
.type(MediaType.TEXT_PLAIN).build();
}
// TODO: use device type and pushDeviceId
Competitor mappedTo = domainFactory.getCompetitorStore().getExistingCompetitorByIdAsString(competitorId);
if (mappedTo == null) {
logger.warning("No competitor found for id " + competitorId);
return Response.status(Status.BAD_REQUEST).entity("No competitor found for id " + StringEscapeUtils.escapeHtml(competitorId))
.type(MediaType.TEXT_PLAIN).build();
}
Set<Competitor> registered = (Set<Competitor>) hasRegattaLike.getAllCompetitors();
if (!registered.contains(mappedTo)) {
logger.warning("Competitor found but not registered on a race of " + leaderboardName);
return Response.status(Status.BAD_REQUEST)
.entity("Competitor found but not registered on a race of " + StringEscapeUtils.escapeHtml(leaderboardName))
.type(MediaType.TEXT_PLAIN).build();
}
DeviceIdentifier device = new SmartphoneUUIDIdentifierImpl(UUID.fromString(deviceUuid));
TimePoint from = new MillisecondsTimePoint(fromMillis);
event = new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), mappedTo, device,
from, /* to */ null);
// TODO: use device type and pushDeviceId
final Named mappedTo;
if (competitorId != null) {
// map to a competitor
final Competitor mappedToCompetitor = domainFactory.getCompetitorStore().getExistingCompetitorByIdAsString(competitorId);
mappedTo = mappedToCompetitor;
if (mappedToCompetitor == null) {
logger.warning("No competitor found for id " + competitorId);
return Response.status(Status.BAD_REQUEST).entity("No competitor found for id " + StringEscapeUtils.escapeHtml(competitorId))
.type(MediaType.TEXT_PLAIN).build();
}
Set<Competitor> registered = (Set<Competitor>) hasRegattaLike.getAllCompetitors();
if (!registered.contains(mappedToCompetitor)) {
logger.warning("Competitor found but not registered on a race of " + leaderboardName);
return Response.status(Status.BAD_REQUEST)
.entity("Competitor found but not registered on a race of " + StringEscapeUtils.escapeHtml(leaderboardName))
.type(MediaType.TEXT_PLAIN).build();
}
event = new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), mappedToCompetitor, device,
from, /* to */ null);
} else {
// map to a mark
final Mark mappedToMark = domainFactory.getExistingMarkById(Helpers.tryUuidConversion(markId));
mappedTo = mappedToMark;
if (mappedToMark == null) {
logger.warning("No mark found for id " + markId);
return Response.status(Status.BAD_REQUEST).entity("No mark found for id " + StringEscapeUtils.escapeHtml(markId))
.type(MediaType.TEXT_PLAIN).build();
}
event = new RegattaLogDeviceMarkMappingEventImpl(now, now, author, UUID.randomUUID(), mappedToMark, device,
from, /* to */ null);
}
hasRegattaLike.getRegattaLike().getRegattaLog().add(event);
logger.fine("Successfully checked in competitor " + mappedTo.getName());
logger.fine("Successfully checked in "+((markId!=null)?"mark ":"competitor ") + mappedTo.getName());
return Response.status(Status.OK).build();
}
@@ -461,31 +480,48 @@ public class LeaderboardsResource extends AbstractSailingServerResource {
logger.fine("JSON requestObject is: " + requestObject.toString());
Long toMillis = (Long) requestObject.get(DeviceMappingConstants.JSON_TO_MILLIS);
String competitorId = (String) requestObject.get(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING);
String markId = (String) requestObject.get(DeviceMappingConstants.JSON_MARK_ID_AS_STRING);
String deviceUuid = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_UUID);
TimePoint closingTimePoint = new MillisecondsTimePoint(toMillis);
if (toMillis == null || deviceUuid == null || closingTimePoint == null) {
if (toMillis == null || deviceUuid == null || closingTimePoint == null ||
(competitorId == null && markId == null)) {
logger.warning("Invalid JSON body in request");
return Response.status(Status.BAD_REQUEST).entity("Invalid JSON body in request")
.type(MediaType.TEXT_PLAIN).build();
}
Competitor mappedTo = getService().getCompetitorStore().getExistingCompetitorByIdAsString(competitorId);
if (mappedTo == null) {
logger.warning("No competitor found for id " + competitorId);
return Response.status(Status.BAD_REQUEST).entity("No competitor found for id " + competitorId)
.type(MediaType.TEXT_PLAIN).build();
final NamedWithID mappedTo;
if (competitorId != null) {
final Competitor mappedToCompetitor = getService().getCompetitorStore().getExistingCompetitorByIdAsString(competitorId);
mappedTo = mappedToCompetitor;
if (mappedToCompetitor == null) {
logger.warning("No competitor found for id " + competitorId);
return Response.status(Status.BAD_REQUEST).entity("No competitor found for id " + competitorId)
.type(MediaType.TEXT_PLAIN).build();
}
} else {
// map to mark
DomainFactory domainFactory = getService().getDomainObjectFactory().getBaseDomainFactory();
final Mark mappedToMark = domainFactory.getExistingMarkById(Helpers.tryUuidConversion(markId));
mappedTo = mappedToMark;
if (mappedToMark == null) {
logger.warning("No mark found for id " + markId);
return Response.status(Status.BAD_REQUEST).entity("No mark found for id " + markId)
.type(MediaType.TEXT_PLAIN).build();
}
}
OpenEndedDeviceMappingFinder finder = new OpenEndedDeviceMappingFinder(isRegattaLike.getRegattaLog(), mappedTo, deviceUuid);
Serializable deviceMappingEventId = finder.analyze();
if (deviceMappingEventId == null) {
logger.warning("No corresponding open competitor to device mapping has been found");
logger.warning("No corresponding open mark to device mapping has been found");
return Response.status(Status.BAD_REQUEST)
.entity("No corresponding open competitor to device mapping has been found")
.entity("No corresponding open mark to device mapping has been found")
.type(MediaType.TEXT_PLAIN).build();
}
RegattaLogCloseOpenEndedDeviceMappingEventImpl event = new RegattaLogCloseOpenEndedDeviceMappingEventImpl(now,
author, deviceMappingEventId, closingTimePoint);
isRegattaLike.getRegattaLog().add(event);
logger.fine("Successfully checked out competitor " + mappedTo.getName());
logger.fine("Successfully checked out "+((markId!=null)?"mark ":"competitor ") + mappedTo.getName());
return Response.status(Status.OK).build();
}
@@ -0,0 +1,4 @@
package com.sap.sse.common;
public interface NamedWithID extends Named, WithID {
}
@@ -12,12 +12,12 @@ import java.util.Set;
import org.apache.shiro.crypto.hash.Sha256Hash;
import com.sap.sse.common.Named;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.common.WithID;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.Account.AccountType;
public class User implements Named, WithID {
public class User implements NamedWithID {
private static final long serialVersionUID = 1788215575606546042L;
/**