Merge branch 'bug4044'

This commit is contained in:
Steffen Schaefer
2017-05-03 12:48:12 +02:00
4 changed files with 1070 additions and 909 deletions
@@ -4,6 +4,7 @@ import static com.sap.sse.common.Util.size;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.io.Serializable;
@@ -26,6 +27,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.MappingEventVisitor;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
@@ -484,6 +486,12 @@ public class SensorFixStoreAndLoadTest {
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3.getMeters()));
}
private void addMoreBravoFixes() {
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP + 1, FIX_RIDE_HEIGHT.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2 + 1, FIX_RIDE_HEIGHT2.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3 + 1, FIX_RIDE_HEIGHT3.getMeters()));
}
private FixLoaderAndTracker createFixLoaderAndTracker() {
@@ -507,9 +515,23 @@ public class SensorFixStoreAndLoadTest {
}
protected void testNumberOfRawFixes(Track<?> track, long expected) {
track.lockForRead();
assertEquals(expected, size(track.getRawFixes()));
track.unlockAfterRead();
if (expected == 0) {
if (track != null) {
track.lockForRead();
try {
assertTrue(size(track.getRawFixes()) == 0);
} finally {
track.unlockAfterRead();
}
}
} else {
track.lockForRead();
try {
assertEquals(expected, size(track.getRawFixes()));
} finally {
track.unlockAfterRead();
}
}
}
private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
@@ -671,4 +693,100 @@ public class SensorFixStoreAndLoadTest {
assertNull(trackedRace.getSensorTrack(compNotPartOfRace, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true);
}
@Test
/** Test for changes of bug 4044 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4044 */
public void testThatNoSensorFixesAreLoadedAsLongAsStartOfTrackingIsNull() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
// raceLog is intentionally not attached
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
// No fixes are loaded because startOfTracking isn't set through the raceLog yet
assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
// Loading of fixes is triggered by setting startOfTracking
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(START_OF_TRACKING));
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
fixLoaderAndTracker.stop(true);
}
@Test
/** Test for changes of bug 4044 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4044 */
public void testThatNoSensorFixesAreRecordedAsWhenStartOfTrackingIsNull() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
// raceLog is intentionally not attached
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true);
}
@Test
/** Test for changes of bug 4044 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4044 */
public void testThatNoMoreSensorFixesAreLoadedWhenStartOfTrackingChangesToNull() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(START_OF_TRACKING));
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
raceLog.add(new RaceLogStartOfTrackingEventImpl(null, author, 0));
assertNull(trackedRace.getStartOfTracking());
addMoreBravoFixes();
trackedRace.waitForLoadingToFinish();
// only the initial 3 fixes are available
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
fixLoaderAndTracker.stop(true);
}
@Test
/** Test for changes of bug 4044 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4044 */
public void testThatNoMoreSensorFixesAreLoadedWhenStartOfTrackingChangesFromNullBySettingStartTime() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
raceLog.add(new RaceLogStartTimeEventImpl(new MillisecondsTimePoint(START_OF_TRACKING), author, 0,
new MillisecondsTimePoint(START_OF_TRACKING)));
assertNotNull(trackedRace.getStartOfTracking());
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
raceLog.add(new RaceLogStartOfTrackingEventImpl(null, author, 0));
addMoreBravoFixes();
trackedRace.waitForLoadingToFinish();
// only the initial 3 fixes are available
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
fixLoaderAndTracker.stop(true);
}
}
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMapping
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
@@ -32,6 +33,8 @@ import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackingDataLoader;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
@@ -47,19 +50,54 @@ import com.sap.sse.util.ThreadPoolUtil;
/**
* This class listens to RaceLog Events, changes to the race and fix loading events and properly handles mappings and
* fix loading.
*
* fix loading.<br>
* The two main responsibility are to 1. load fixes already available in the DB when a race is tracked and 2. add fixes
* newly send by trackers to {@link Track}s of a {@link TrackedRace} if the fix is relevant for the race. In order to
* save memory we try to keep the set of loaded fixes as constrained as possible. In general the following rules apply:
* <ul>
* <li>{@link RaceChangeListener},</li>
* <li>The fixes loaded to a track for a specific item (Mark, Competitor) are associated to a device that needs to be
* mapped to the specific item using a {@link RegattaLogDeviceMappingEvent} in the {@link RegattaLog}. Only fixes that
* are associated to the mapped device in the mapped {@link TimeRange} are allowed to be loaded into an item's
* {@link Track}</li>
* <li>If startOfTracking isn't available for the underlying {@link TrackedRace}, no fixes are loaded at all. This
* prevents everything to be loaded in cases where a device is mapped for a whole {@link Regatta}'s time range and a
* late race is tracked without startOfTracking being initially available.</li>
* <li>If endOfTracking isn't available yet for a race, all fixes after startOfTracking are loaded into the respective
* {@link Track}. This case is assumed to only occur in live scenarios where the endOfTracking is defined late while
* boats cross the finishing line. This isn't much of a problem because no fixes are available for the future.</li>
* <li>No loaded fixes are ever removed from {@link Track}s. Even if the tracking times change to be more restrictive,
* the fixes in the time range not covered anymore are held in the {@link Track}.</li>
* </ul>
* Marks can either be tracked or just pinged using the respective app. If a Mark is pinged early in the morning before
* the start of a race, no fix is available for this Mark in the tracked {@link TimeRange}. The semantic of pinging a
* Mark is that the position is assumed to be correct until it is pinged again. So in extreme cases this single fix
* needs to be used for all races of a day. To ensure this semantic there are additional special rules for
* {@link Mark}s:
* <ul>
* <li>Fixes in the tracking time range are loaded/tracked using the above mentioned rules.</li>
* <li>If at least one fix is available, no additional fix is loaded at all. If no fix is available for a {@link Mark}
* in the tracking time range, the following rules apply:
* <ul>
* <li>If no fix isn't available yet before/after the tracking time range, the single fix is loaded that is the best
* (nearest) before startOfTracking/after endOfTracking.</li>
* <li>If additional fixes get available (either by tracking or by a new device mapping), only the best single fixes are
* taken using the rule above. If there is already a best fix before startOfTracking/after endOfTracking, the new best
* fix is only used if it is better (nearer) as the existing one.</li>
* </ul>
* </li>
* </ul>
* There is a corner cases that is assumed to be acceptable for the specific semantic of a {@link Mark}'s fixes:<br>
* If a Marks is tracked and not pinged, any new fix transferred from the tracking device is treated as being better
* than the one before. So all fixes are being recorded starting at the time point when the operator starts tracking for
* a race and startOfTracking is in the future.<br>
* Some related classes:
* <ul>
* <li>{@link RaceChangeListener}</li>
* <li>{@link RegattaLogEventVisitor}</li>
* <li>{@link FixReceivedListener}</li>
* </ul>
*
*/
/**
* @author sschaefe
*
*/
public class FixLoaderAndTracker implements TrackingDataLoader {
private static final Logger logger = Logger.getLogger(FixLoaderAndTracker.class.getName());
protected final DynamicTrackedRace trackedRace;
@@ -72,16 +110,24 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
private final AbstractRaceChangeListener raceChangeListener = new AbstractRaceChangeListener() {
@Override
public void startOfTrackingChanged(TimePoint oldStartOfTracking, TimePoint newStartOfTracking) {
if ((newStartOfTracking == null
|| (oldStartOfTracking != null && newStartOfTracking.before(oldStartOfTracking)))) {
loadFixesForExtendedTimeRange(newStartOfTracking, oldStartOfTracking);
if (newStartOfTracking != null) {
if (oldStartOfTracking == null) {
// Fixes wheren't loaded while startOfTracking was null. So we need to load all fixes in the tracking interval now.
loadFixesForExtendedTimeRange(getTrackingTimeRange());
} else if (newStartOfTracking.before(oldStartOfTracking)) {
loadFixesForExtendedTimeRange(new TimeRangeImpl(newStartOfTracking, oldStartOfTracking));
}
}
}
@Override
public void endOfTrackingChanged(TimePoint oldEndOfTracking, TimePoint newEndOfTracking) {
if (newEndOfTracking == null || (oldEndOfTracking != null && newEndOfTracking.after(oldEndOfTracking))) {
loadFixesForExtendedTimeRange(oldEndOfTracking, newEndOfTracking);
if (trackedRace.getStartOfTracking() != null) {
if (newEndOfTracking == null && oldEndOfTracking != null) {
loadFixesForExtendedTimeRange(new TimeRangeImpl(oldEndOfTracking, TimePoint.EndOfTime));
} else if (newEndOfTracking != null && oldEndOfTracking != null && oldEndOfTracking.before(newEndOfTracking)) {
loadFixesForExtendedTimeRange(new TimeRangeImpl(oldEndOfTracking, newEndOfTracking));
}
}
}
@@ -92,107 +138,106 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
private final FixReceivedListener<Timed> listener = new FixReceivedListener<Timed>() {
@Override
public void fixReceived(DeviceIdentifier device, Timed fix) {
if (preemptiveStopRequested.get()) {
return;
}
final TimePoint timePoint = fix.getTimePoint();
deviceMappings.forEachMappingOfDeviceIncludingTimePoint(device, fix.getTimePoint(), (mapping) -> {
mapping.getRegattaLogEvent().accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
if (preemptiveStopRequested.get()) {
return;
if (!preemptiveStopRequested.get() && trackedRace.getStartOfTracking() != null) {
final TimePoint timePoint = fix.getTimePoint();
deviceMappings.forEachMappingOfDeviceIncludingTimePoint(device, fix.getTimePoint(), (mapping) -> {
mapping.getRegattaLogEvent().accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
if (preemptiveStopRequested.get()) {
return;
}
SensorFixMapper<SensorFix, DynamicSensorFixTrack<Competitor, SensorFix>, Competitor> mapper = sensorFixMapperFactory
.createCompetitorMapper(event.getClass());
DynamicSensorFixTrack<Competitor, SensorFix> track = mapper.getTrack(trackedRace,
event.getMappedTo());
if (track != null && trackedRace.isWithinStartAndEndOfTracking(fix.getTimePoint())) {
mapper.addFix(track, (DoubleVectorFix) fix);
}
}
SensorFixMapper<SensorFix, DynamicSensorFixTrack<Competitor, SensorFix>, Competitor> mapper = sensorFixMapperFactory
.createCompetitorMapper(event.getClass());
DynamicSensorFixTrack<Competitor, SensorFix> track = mapper.getTrack(trackedRace,
event.getMappedTo());
if (track != null && trackedRace.isWithinStartAndEndOfTracking(fix.getTimePoint())) {
mapper.addFix(track, (DoubleVectorFix) fix);
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
if (preemptiveStopRequested.get()) {
return;
}
Competitor comp = event.getMappedTo();
if (fix instanceof GPSFixMoving) {
trackedRace.recordFix(comp, (GPSFixMoving) fix);
} else {
logger.log(Level.WARNING,
String.format(
"Could not add fix for competitor (%s) in race (%s), as it"
+ " is no GPSFixMoving, meaning it is missing COG/SOG values",
comp, trackedRace.getRace().getName()));
}
}
}
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
if (preemptiveStopRequested.get()) {
return;
}
Competitor comp = event.getMappedTo();
if (fix instanceof GPSFixMoving) {
trackedRace.recordFix(comp, (GPSFixMoving) fix);
} else {
logger.log(Level.WARNING,
String.format(
"Could not add fix for competitor (%s) in race (%s), as it"
+ " is no GPSFixMoving, meaning it is missing COG/SOG values",
comp, trackedRace.getRace().getName()));
}
}
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
if (preemptiveStopRequested.get()) {
return;
}
Mark mark = event.getMappedTo();
final DynamicGPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getOrCreateTrack(mark);
final GPSFix firstFixAtOrAfter;
final boolean forceFix;
if (trackedRace.isWithinStartAndEndOfTracking(fix.getTimePoint())) {
forceFix = false;
} else {
markTrack.lockForRead();
try {
if (Util.isEmpty(markTrack.getRawFixes())
|| (firstFixAtOrAfter = markTrack.getFirstFixAtOrAfter(timePoint)) != null
&& firstFixAtOrAfter.getTimePoint().equals(timePoint)) {
// either the first fix or overwriting an existing one
forceFix = true;
} else {
// checking if the given fix is "better" than an existing one
TimePoint startOfTracking = trackedRace.getStartOfTracking();
TimePoint endOfTracking = trackedRace.getStartOfTracking();
if (startOfTracking != null) {
GPSFix fixAfterStartOfTracking = markTrack
.getFirstFixAtOrAfter(startOfTracking);
if (fixAfterStartOfTracking == null
|| !trackedRace.isWithinStartAndEndOfTracking(
fixAfterStartOfTracking.getTimePoint())) {
// There is no fix in the tracking interval, so this fix could be "better"
// than ones already available in the track
// Better means closer before/after the beginning/end of the tracking
// interval
if (timePoint.before(startOfTracking)) {
// check if it is closer to the beginning of the tracking interval
GPSFix fixBeforeStartOfTracking = markTrack
.getLastFixAtOrBefore(startOfTracking);
forceFix = (fixBeforeStartOfTracking == null
|| fixBeforeStartOfTracking.getTimePoint().before(timePoint));
} else if (endOfTracking != null && timePoint.after(endOfTracking)) {
// check if it is closer to the end of the tracking interval
GPSFix fixAfterEndOfTracking = markTrack
.getFirstFixAtOrAfter(endOfTracking);
forceFix = (fixAfterEndOfTracking == null
|| fixAfterEndOfTracking.getTimePoint().after(timePoint));
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
if (preemptiveStopRequested.get()) {
return;
}
Mark mark = event.getMappedTo();
final DynamicGPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getOrCreateTrack(mark);
final GPSFix firstFixAtOrAfter;
final boolean forceFix;
if (trackedRace.isWithinStartAndEndOfTracking(fix.getTimePoint())) {
forceFix = false;
} else {
markTrack.lockForRead();
try {
if (Util.isEmpty(markTrack.getRawFixes())
|| (firstFixAtOrAfter = markTrack.getFirstFixAtOrAfter(timePoint)) != null
&& firstFixAtOrAfter.getTimePoint().equals(timePoint)) {
// either the first fix or overwriting an existing one
forceFix = true;
} else {
// checking if the given fix is "better" than an existing one
TimePoint startOfTracking = trackedRace.getStartOfTracking();
TimePoint endOfTracking = trackedRace.getStartOfTracking();
if (startOfTracking != null) {
GPSFix fixAfterStartOfTracking = markTrack
.getFirstFixAtOrAfter(startOfTracking);
if (fixAfterStartOfTracking == null
|| !trackedRace.isWithinStartAndEndOfTracking(
fixAfterStartOfTracking.getTimePoint())) {
// There is no fix in the tracking interval, so this fix could be "better"
// than ones already available in the track
// Better means closer before/after the beginning/end of the tracking
// interval
if (timePoint.before(startOfTracking)) {
// check if it is closer to the beginning of the tracking interval
GPSFix fixBeforeStartOfTracking = markTrack
.getLastFixAtOrBefore(startOfTracking);
forceFix = (fixBeforeStartOfTracking == null
|| fixBeforeStartOfTracking.getTimePoint().before(timePoint));
} else if (endOfTracking != null && timePoint.after(endOfTracking)) {
// check if it is closer to the end of the tracking interval
GPSFix fixAfterEndOfTracking = markTrack
.getFirstFixAtOrAfter(endOfTracking);
forceFix = (fixAfterEndOfTracking == null
|| fixAfterEndOfTracking.getTimePoint().after(timePoint));
} else {
forceFix = false;
}
} else {
// there is already a fix in the tracking interval
forceFix = false;
}
} else {
// there is already a fix in the tracking interval
forceFix = false;
}
} else {
forceFix = false;
}
} finally {
markTrack.unlockAfterRead();
}
} finally {
markTrack.unlockAfterRead();
}
trackedRace.recordFix(mark, (GPSFix) fix, /* only when in tracking interval */ !forceFix);
}
trackedRace.recordFix(mark, (GPSFix) fix, /* only when in tracking interval */ !forceFix);
}
});
});
});
}
}
};
@@ -215,20 +260,22 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
*/
private void loadFixesForNewlyCoveredTimeRanges(WithID item,
Map<RegattaLogDeviceMappingEvent<WithID>, MultiTimeRange> newlyCoveredTimeRanges) {
TimeRange trackingTimeRange = getTrackingTimeRange();
loadFixesInTrackingTimeRange(newlyCoveredTimeRanges, trackingTimeRange);
if (item instanceof Mark) {
Mark mark = (Mark) item;
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
// load all mapped fixes if there was no fix in the tracking TimeRange
GPSFix firstFixAfterStartOfTracking = track.getFirstFixAfter(trackingTimeRange.from());
if (firstFixAfterStartOfTracking == null
|| firstFixAfterStartOfTracking.getTimePoint().after(trackingTimeRange.to())) {
// There is no fix in the tracking interval -> looking for better fixes before start of tracking and
// after end of tracking
newlyCoveredTimeRanges.forEach((event, timeRange) -> {
loadBetterFixesIfAvailable(trackingTimeRange, timeRange, event);
});
if (trackedRace.getStartOfTracking() != null) {
TimeRange trackingTimeRange = getTrackingTimeRange();
loadFixesInTrackingTimeRange(newlyCoveredTimeRanges, trackingTimeRange);
if (item instanceof Mark) {
Mark mark = (Mark) item;
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(mark);
// load all mapped fixes if there was no fix in the tracking TimeRange
GPSFix firstFixAfterStartOfTracking = track.getFirstFixAfter(trackingTimeRange.from());
if (firstFixAfterStartOfTracking == null
|| firstFixAfterStartOfTracking.getTimePoint().after(trackingTimeRange.to())) {
// There is no fix in the tracking interval -> looking for better fixes before start of tracking and
// after end of tracking
newlyCoveredTimeRanges.forEach((event, timeRange) -> {
loadBetterFixesIfAvailable(trackingTimeRange, timeRange, event);
});
}
}
}
}
@@ -262,7 +309,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
*/
public boolean containsMappingThatIntersectsTimeRange(Iterable<DeviceMappingWithRegattaLogEvent<WithID>> mappings, TimeRange timeRange) {
for (DeviceMappingWithRegattaLogEvent<WithID> mapping : mappings) {
if(timeRange.intersects(mapping.getTimeRange())) {
if (timeRange.intersects(mapping.getTimeRange())) {
return true;
}
}
@@ -273,60 +320,56 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
* Loads the fixes in the specified {@link TimeRange} using a visitor of the given mapping event.
*/
private void loadFixes(TimeRange timeRangeToLoad, RegattaLogDeviceMappingEvent<? extends WithID> mappingEvent) {
if (timeRangeToLoad == null) {
return;
}
if (preemptiveStopRequested.get()) {
return;
}
mappingEvent.accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
SensorFixMapper<Timed, DynamicTrack<Timed>, Competitor> mapper = sensorFixMapperFactory
.createCompetitorMapper(event.getClass());
DynamicTrack<Timed> track = mapper.getTrack(trackedRace, event.getMappedTo());
if (track != null) {
// for split-fleet racing, device mappings coming from the regatta log may not be relevant
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
sensorFixStore.<DoubleVectorFix> loadFixes(fix -> mapper.addFix(track, fix), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (NoCorrespondingServiceRegisteredException | TransformationException e) {
logger.log(Level.WARNING, "Could not load track for competitor: " + event.getMappedTo()
+ "; device: " + event.getDevice());
if (timeRangeToLoad != null && !preemptiveStopRequested.get()) {
mappingEvent.accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
SensorFixMapper<Timed, DynamicTrack<Timed>, Competitor> mapper = sensorFixMapperFactory
.createCompetitorMapper(event.getClass());
DynamicTrack<Timed> track = mapper.getTrack(trackedRace, event.getMappedTo());
if (track != null) {
// for split-fleet racing, device mappings coming from the regatta log may not be relevant
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
sensorFixStore.<DoubleVectorFix> loadFixes(fix -> mapper.addFix(track, fix), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (NoCorrespondingServiceRegisteredException | TransformationException e) {
logger.log(Level.WARNING, "Could not load track for competitor: " + event.getMappedTo()
+ "; device: " + event.getDevice());
}
}
}
}
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(event.getMappedTo());
if (track != null) {
// for split-fleet racing, device mappings coming from the regatta log may not be relevant
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(event.getMappedTo());
if (track != null) {
// for split-fleet racing, device mappings coming from the regatta log may not be relevant
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
sensorFixStore.<GPSFixMoving> loadFixes(fix -> track.add(fix, true), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + event.getMappedTo() + "; device "
+ event.getDevice());
}
}
}
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
try {
sensorFixStore.<GPSFixMoving> loadFixes(fix -> track.add(fix, true), event.getDevice(),
sensorFixStore.<GPSFix> loadFixes(fix -> track.add(fix, true), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + event.getMappedTo() + "; device "
+ event.getDevice());
logger.log(Level.WARNING, "Could not load mark track " + event.getMappedTo());
}
}
}
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
try {
sensorFixStore.<GPSFix> loadFixes(fix -> track.add(fix, true), event.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), /* toIsInclusive */ false);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load mark track " + event.getMappedTo());
}
}
});
});
}
}
/**
@@ -334,66 +377,65 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
*/
private void loadBetterFixesIfAvailable(TimeRange trackingTimeRange, MultiTimeRange coveredTimeRanges,
RegattaLogDeviceMappingEvent<? extends WithID> mappingEvent) {
if (preemptiveStopRequested.get()) {
return;
if (!preemptiveStopRequested.get()) {
mappingEvent.accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
throw new UnsupportedOperationException();
}
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
throw new UnsupportedOperationException();
}
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
final GPSFix lastFixAtOrBeforeStartOfTracking = track.getLastFixAtOrBefore(trackingTimeRange.from());
// A better fix before start of tracking must be after the current best fix
final MultiTimeRange beforeRange = coveredTimeRanges
.intersection(new TimeRangeImpl(
lastFixAtOrBeforeStartOfTracking != null
? lastFixAtOrBeforeStartOfTracking.getTimePoint() : TimePoint.BeginningOfTime,
trackingTimeRange.from()));
// starting to load newer ranges to make the first found fix the best available fix
Collection<TimeRange> inverseTimeRanges = Util.addAll(beforeRange,
new TreeSet<>((timeRange1, timeRange2) -> -timeRange1.from().compareTo(timeRange2.from())));
for (TimeRange timeRange : inverseTimeRanges) {
try {
if (sensorFixStore.<GPSFix> loadYoungestFix(fix -> track.add(fix, true),
event.getDevice(), timeRange)) {
// new best fix before start of tracking found
break;
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load better fix for mark track " + event.getMappedTo());
}
}
final GPSFix firstFixAtOrAfterEndOfTracking = track.getFirstFixAtOrAfter(trackingTimeRange.to());
// A better fix after end of tracking must be before the current best fix
MultiTimeRange afterRange = coveredTimeRanges.intersection(new TimeRangeImpl(trackingTimeRange.to(),
firstFixAtOrAfterEndOfTracking != null ? firstFixAtOrAfterEndOfTracking.getTimePoint()
: TimePoint.EndOfTime));
for (TimeRange timeRange : afterRange) {
try {
if (sensorFixStore.<GPSFix> loadOldestFix(fix -> track.add(fix, true), event.getDevice(),
timeRange)) {
// new best fix after end of tracking found
break;
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load better fix for mark track " + event.getMappedTo());
}
}
}
});
}
mappingEvent.accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
throw new UnsupportedOperationException();
}
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
throw new UnsupportedOperationException();
}
@Override
public void visit(RegattaLogDeviceMarkMappingEvent event) {
DynamicGPSFixTrack<Mark, GPSFix> track = trackedRace.getOrCreateTrack(event.getMappedTo());
final GPSFix lastFixAtOrBeforeStartOfTracking = track.getLastFixAtOrBefore(trackingTimeRange.from());
// A better fix before start of tracking must be after the current best fix
final MultiTimeRange beforeRange = coveredTimeRanges
.intersection(new TimeRangeImpl(
lastFixAtOrBeforeStartOfTracking != null
? lastFixAtOrBeforeStartOfTracking.getTimePoint() : TimePoint.BeginningOfTime,
trackingTimeRange.from()));
// starting to load newer ranges to make the first found fix the best available fix
Collection<TimeRange> inverseTimeRanges = Util.addAll(beforeRange,
new TreeSet<>((timeRange1, timeRange2) -> -timeRange1.from().compareTo(timeRange2.from())));
for (TimeRange timeRange : inverseTimeRanges) {
try {
if (sensorFixStore.<GPSFix> loadYoungestFix(fix -> track.add(fix, true),
event.getDevice(), timeRange)) {
// new best fix before start of tracking found
break;
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load better fix for mark track " + event.getMappedTo());
}
}
final GPSFix firstFixAtOrAfterEndOfTracking = track.getFirstFixAtOrAfter(trackingTimeRange.to());
// A better fix after end of tracking must be before the current best fix
MultiTimeRange afterRange = coveredTimeRanges.intersection(new TimeRangeImpl(trackingTimeRange.to(),
firstFixAtOrAfterEndOfTracking != null ? firstFixAtOrAfterEndOfTracking.getTimePoint()
: TimePoint.EndOfTime));
for (TimeRange timeRange : afterRange) {
try {
if (sensorFixStore.<GPSFix> loadOldestFix(fix -> track.add(fix, true), event.getDevice(),
timeRange)) {
// new best fix after end of tracking found
break;
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load better fix for mark track " + event.getMappedTo());
}
}
}
});
}
private TimeRange getTrackingTimeRange() {
@@ -435,8 +477,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
}
private void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) {
final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo);
private void loadFixesForExtendedTimeRange(final TimeRange extendedTimeRange) {
deviceMappings.forEachItemAndCoveredTimeRanges((item, mappingsAndCoveredTimeRanges) -> loadFixesInTrackingTimeRange(mappingsAndCoveredTimeRanges, extendedTimeRange));
}
@@ -1,170 +1,170 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAndFleetsDialog {
protected static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
protected static class RegattaParameterValidator implements Validator<RegattaDTO> {
private StringMessages stringMessages;
private ArrayList<RegattaDTO> existingRegattas;
public RegattaParameterValidator(StringMessages stringMessages, Collection<RegattaDTO> existingRegattas) {
this.stringMessages = stringMessages;
this.existingRegattas = new ArrayList<RegattaDTO>(existingRegattas);
}
@Override
public String getErrorMessage(RegattaDTO regattaToValidate) {
String errorMessage = null;
boolean nameNotEmpty = regattaToValidate.getName() != null && regattaToValidate.getName().length() > 0;
boolean boatClassNotEmpty = regattaToValidate.boatClass != null
&& regattaToValidate.boatClass.getName().length() > 0;
boolean unique = true;
for (RegattaDTO regatta : existingRegattas) {
if (regatta.getName().equals(regattaToValidate.getName())) {
unique = false;
break;
}
}
Date startDate = regattaToValidate.startDate;
Date endDate = regattaToValidate.endDate;
String datesErrorMessage = null;
// remark: startDate == null and endDate == null is valid
if (startDate != null && endDate != null) {
if (startDate.after(endDate)) {
datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate();
}
} else if ((startDate != null && endDate == null) || (startDate == null && endDate != null)) {
datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate();
}
if (datesErrorMessage != null) {
errorMessage = datesErrorMessage;
} else if (!nameNotEmpty) {
errorMessage = stringMessages.pleaseEnterAName();
} else if (regattaToValidate.getName().contains("/")) {
errorMessage = stringMessages.regattaNameMustNotContainSlashes();
} else if (!boatClassNotEmpty) {
errorMessage = stringMessages.pleaseEnterABoatClass();
} else if (!unique) {
errorMessage = stringMessages.regattaWithThisNameAlreadyExists();
}
if (regattaToValidate.buoyZoneRadiusInHullLengths == null) {
errorMessage = stringMessages.incorrectValueForRegattaBuoyZoneRadiusInHullLengths();
}
if (errorMessage == null) {
List<SeriesDTO> seriesToValidate = regattaToValidate.series;
int index = 0;
boolean seriesNameNotEmpty = true;
for (SeriesDTO series : seriesToValidate) {
seriesNameNotEmpty = series.getName() != null && series.getName().length() > 0;
if (!seriesNameNotEmpty) {
break;
}
index++;
}
int index2 = 0;
boolean seriesUnique = true;
HashSet<String> setToFindDuplicates = new HashSet<String>();
for (SeriesDTO series : seriesToValidate) {
if (!setToFindDuplicates.add(series.getName())) {
seriesUnique = false;
break;
}
index2++;
}
if (!seriesNameNotEmpty) {
errorMessage = stringMessages.series() + " " + (index + 1) + ": "
+ stringMessages.pleaseEnterAName();
} else if (!seriesUnique) {
errorMessage = stringMessages.series() + " " + (index2 + 1) + ": "
+ stringMessages.seriesWithThisNameAlreadyExists();
}
}
return errorMessage;
}
}
public RegattaWithSeriesAndFleetsCreateDialog(Collection<RegattaDTO> existingRegattas,
List<EventDTO> existingEvents, EventDTO correspondingEvent, StringMessages stringMessages, DialogCallback<RegattaDTO> callback) {
super(new RegattaDTO(), Collections.<SeriesDTO>emptySet(), existingEvents, correspondingEvent, stringMessages.addRegatta(), stringMessages.ok(),
stringMessages, new RegattaParameterValidator(stringMessages, existingRegattas), callback);
buoyZoneRadiusInHullLengthsDoubleBox.setValue(Regatta.DEFAULT_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS);
SeriesDTO series = new SeriesDTO();
series.setName(Series.DEFAULT_NAME);
series.setMedal(false);
series.setStartsWithZeroScore(false);
series.setSplitFleetContiguousScoring(false);
series.setFirstColumnIsNonDiscardableCarryForward(false);
series.setFleets(Collections.singletonList(new FleetDTO(LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)));
seriesEditor.setValue(Collections.singleton(series));
}
@Override
protected boolean isEnableFleetRemoval() {
return true;
}
@Override
protected void setupAdditionalWidgetsOnPanel(final VerticalPanel panel, Grid formGrid) {
super.setupAdditionalWidgetsOnPanel(panel, formGrid);
insertRankingMetricTabPanel(formGrid);
final TabPanel tabPanel = new TabPanel();
tabPanel.setWidth("100%");
tabPanel.add(getSeriesEditor(), stringMessages.series());
tabPanel.selectTab(0);
panel.add(tabPanel);
}
@Override
protected FocusWidget getInitialFocusWidget() {
return nameEntryField;
}
@Override
protected RegattaDTO getResult() {
RegattaDTO dto = super.getResult();
List<SeriesDTO> seriesList = getSeriesEditor().getValue();
for (SeriesDTO series : seriesList) {
// generate 3 Default Races if default series is still present
if (series.getName().equals(Series.DEFAULT_NAME)) {
List<RaceColumnDTO> races = new ArrayList<RaceColumnDTO>();
for (int i = 1; i <= 3; i++) {
RaceColumnDTO raceColumnDTO = new RaceColumnInSeriesDTO(series.getName(), dto.getName());
raceColumnDTO.setName("R"+i);
races.add(raceColumnDTO);
}
series.setRaceColumns(races);
}
}
dto.series = seriesList;
setRankingMetrics(dto);
return dto;
}
}
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAndFleetsDialog {
protected static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
protected static class RegattaParameterValidator implements Validator<RegattaDTO> {
private StringMessages stringMessages;
private ArrayList<RegattaDTO> existingRegattas;
public RegattaParameterValidator(StringMessages stringMessages, Collection<RegattaDTO> existingRegattas) {
this.stringMessages = stringMessages;
this.existingRegattas = new ArrayList<RegattaDTO>(existingRegattas);
}
@Override
public String getErrorMessage(RegattaDTO regattaToValidate) {
String errorMessage = null;
boolean nameNotEmpty = regattaToValidate.getName() != null && regattaToValidate.getName().length() > 0;
boolean boatClassNotEmpty = regattaToValidate.boatClass != null
&& regattaToValidate.boatClass.getName().length() > 0;
boolean unique = true;
for (RegattaDTO regatta : existingRegattas) {
if (regatta.getName().equals(regattaToValidate.getName())) {
unique = false;
break;
}
}
Date startDate = regattaToValidate.startDate;
Date endDate = regattaToValidate.endDate;
String datesErrorMessage = null;
// remark: startDate == null and endDate == null is valid
if (startDate != null && endDate != null) {
if (startDate.after(endDate)) {
datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate();
}
} else if ((startDate != null && endDate == null) || (startDate == null && endDate != null)) {
datesErrorMessage = stringMessages.pleaseEnterStartAndEndDate();
}
if (datesErrorMessage != null) {
errorMessage = datesErrorMessage;
} else if (!nameNotEmpty) {
errorMessage = stringMessages.pleaseEnterAName();
} else if (regattaToValidate.getName().contains("/")) {
errorMessage = stringMessages.regattaNameMustNotContainSlashes();
} else if (!boatClassNotEmpty) {
errorMessage = stringMessages.pleaseEnterABoatClass();
} else if (!unique) {
errorMessage = stringMessages.regattaWithThisNameAlreadyExists();
}
if (regattaToValidate.buoyZoneRadiusInHullLengths == null) {
errorMessage = stringMessages.incorrectValueForRegattaBuoyZoneRadiusInHullLengths();
}
if (errorMessage == null) {
List<SeriesDTO> seriesToValidate = regattaToValidate.series;
int index = 0;
boolean seriesNameNotEmpty = true;
for (SeriesDTO series : seriesToValidate) {
seriesNameNotEmpty = series.getName() != null && series.getName().length() > 0;
if (!seriesNameNotEmpty) {
break;
}
index++;
}
int index2 = 0;
boolean seriesUnique = true;
HashSet<String> setToFindDuplicates = new HashSet<String>();
for (SeriesDTO series : seriesToValidate) {
if (!setToFindDuplicates.add(series.getName())) {
seriesUnique = false;
break;
}
index2++;
}
if (!seriesNameNotEmpty) {
errorMessage = stringMessages.series() + " " + (index + 1) + ": "
+ stringMessages.pleaseEnterAName();
} else if (!seriesUnique) {
errorMessage = stringMessages.series() + " " + (index2 + 1) + ": "
+ stringMessages.seriesWithThisNameAlreadyExists();
}
}
return errorMessage;
}
}
public RegattaWithSeriesAndFleetsCreateDialog(Collection<RegattaDTO> existingRegattas,
List<EventDTO> existingEvents, EventDTO correspondingEvent, StringMessages stringMessages, DialogCallback<RegattaDTO> callback) {
super(new RegattaDTO(), Collections.<SeriesDTO>emptySet(), existingEvents, correspondingEvent, stringMessages.addRegatta(), stringMessages.ok(),
stringMessages, new RegattaParameterValidator(stringMessages, existingRegattas), callback);
buoyZoneRadiusInHullLengthsDoubleBox.setValue(Regatta.DEFAULT_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS);
SeriesDTO series = new SeriesDTO();
series.setName(Series.DEFAULT_NAME);
series.setMedal(false);
series.setStartsWithZeroScore(false);
series.setSplitFleetContiguousScoring(false);
series.setFirstColumnIsNonDiscardableCarryForward(false);
series.setFleets(Collections.singletonList(new FleetDTO(LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)));
seriesEditor.setValue(Collections.singleton(series));
}
@Override
protected boolean isEnableFleetRemoval() {
return true;
}
@Override
protected void setupAdditionalWidgetsOnPanel(final VerticalPanel panel, Grid formGrid) {
super.setupAdditionalWidgetsOnPanel(panel, formGrid);
insertRankingMetricTabPanel(formGrid);
final TabPanel tabPanel = new TabPanel();
tabPanel.setWidth("100%");
tabPanel.add(getSeriesEditor(), stringMessages.series());
tabPanel.selectTab(0);
panel.add(tabPanel);
}
@Override
protected FocusWidget getInitialFocusWidget() {
return nameEntryField;
}
@Override
protected RegattaDTO getResult() {
RegattaDTO dto = super.getResult();
List<SeriesDTO> seriesList = getSeriesEditor().getValue();
for (SeriesDTO series : seriesList) {
// generate 3 Default Races if default series is still present
if (series.getName().equals(Series.DEFAULT_NAME)) {
List<RaceColumnDTO> races = new ArrayList<RaceColumnDTO>();
for (int i = 1; i <= 3; i++) {
RaceColumnDTO raceColumnDTO = new RaceColumnInSeriesDTO(series.getName(), dto.getName());
raceColumnDTO.setName("R"+i);
races.add(raceColumnDTO);
}
series.setRaceColumns(races);
}
}
dto.series = seriesList;
setRankingMetrics(dto);
return dto;
}
}