Merge branch 'master' into bug1564

This commit is contained in:
Axel Uhl committed 2016-09-30 11:29:31 +02:00
commit b40407dfda
140 files changed
+5244 -1386

No files matched your search

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en">
<file datatype="javapropertyresourcebundle" original="/usr/sap/ljs/app/Converter_6482944262098570487/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en">
<header>
<skl>
<internal-file crc="2476e2fa472ba5cb7a43d465af58c5c7" form="base64">H4sIAAAAAAAAAIVVTW8aMRC9+1fMpVJyiATptypXAiIIFfkQoEY9mt0BLBYb2SaU/vrO2Muuk0b04p1582zPjO23sx1iKWc8ijjO0cywsKb0fVxahykG3Y6HRQTEjfZBmQLnTj1jRbNPAJwQca+2KHkQE1zNjzuU9AU2xEzpanwj+QP3++0CnfiFykkemJ2wyK/D9ypoa1Slw1FmtugVzprjVtZfMcWVCkHJ+isGdu889hwqmUxgW/StCoNKeS/ZgmiKYYUYZBzFVBUoeaAVtjsMOlgnWzMuEOeKOaqt5IESVyW6hVWu5Mq0Wd3YrdJmisFpfEY3WJMnM5qP+5wjc9xzS86vuPJi9Dgb6t/naMS4Igqtd6fc5pFKJua5CUyDmpdSnaLfV+F/CUOipSkPy7Zt/60U7BJaus9bOnJ2v8ubBxHJKXmUe8aNyWt9URAT8tzibaPti/aQRztP/Tr1TTxpU86CQ7MK67Hpo9rTUwiSYTjhoA2cIqK/DB05UNWWra6c6NU6gNKO3evaXTjEP8jIezlCEyrMoA/yzlJBKuTgRzl06NcZ8knS9tasMuizvKXl4UC5sftFjlQVA19P3FUNdDuEWJdy7Mqf2laUBvgGu5a3e+d0oQzS86ro8T3jrCARGJv0SmoMPIPcAMd34KJDWuHDN+jCgY4yXKb+sZC8bp6P6sKdW4ZGWXphFhQxGmEJlpIiBFRIRtKqpzWJFbt8wEmnDgQlCp/zm4qW1n4ta/W6U2U2vTDUlDXfGMk+7xoRYEgkWXpY3lFX9nSHvUwI36AGa1hzVWxyRvSb6A9Njcqi0W+ij0hSF44D7YrqBa0OQB2JWY/oJfkHN7HeEzfmHSGwDhLYKrflHiS10uWb7W4IwAy4eMfbTuI9v2zzs6Xeb4faaL/Os4swJDwrhw48L4LdfzPvYzggmuYEeqasN3irJLjo1scCV9/rHS/F2NOfqNjQr2nsoTabLNpnHwUxS6iNQNLK9qTd5jU57plofwGIogiCRAcAAA==</internal-file>
</skl>
<phase-group>
<phase company-name="SAP AG" date="2016-05-11T12:28:06Z" phase-name="file-creation" process-name="file-creation" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter"/>
<phase company-name="SAP AG" date="2016-09-23T09:24:36Z" phase-name="file-creation" process-name="file-creation" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter"/>
</phase-group>
<tool tool-company="SAP AG" tool-id="urn:x-sap:sls-mlt:java:properties-flexible-converter" tool-name="TEW: Javascript .properties Flexible File Converter" tool-version="1.1.0"/>
</header>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:45:09Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:25Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="2476e2fa472ba5cb7a43d465af58c5c7" form="base64">H4sIAAAAAAAAAIVVTW8aMRC9+1fMpVJyiATptypXAiIIFfkQoEY9mt0BLBYb2SaU/vrO2Muuk0b04p1582zPjO23sx1iKWc8ijjO0cywsKb0fVxahykG3Y6HRQTEjfZBmQLnTj1jRbNPAJwQca+2KHkQE1zNjzuU9AU2xEzpanwj+QP3++0CnfiFykkemJ2wyK/D9ypoa1Slw1FmtugVzprjVtZfMcWVCkHJ+isGdu889hwqmUxgW/StCoNKeS/ZgmiKYYUYZBzFVBUoeaAVtjsMOlgnWzMuEOeKOaqt5IESVyW6hVWu5Mq0Wd3YrdJmisFpfEY3WJMnM5qP+5wjc9xzS86vuPJi9Dgb6t/naMS4Igqtd6fc5pFKJua5CUyDmpdSnaLfV+F/CUOipSkPy7Zt/60U7BJaus9bOnJ2v8ubBxHJKXmUe8aNyWt9URAT8tzibaPti/aQRztP/Tr1TTxpU86CQ7MK67Hpo9rTUwiSYTjhoA2cIqK/DB05UNWWra6c6NU6gNKO3evaXTjEP8jIezlCEyrMoA/yzlJBKuTgRzl06NcZ8knS9tasMuizvKXl4UC5sftFjlQVA19P3FUNdDuEWJdy7Mqf2laUBvgGu5a3e+d0oQzS86ro8T3jrCARGJv0SmoMPIPcAMd34KJDWuHDN+jCgY4yXKb+sZC8bp6P6sKdW4ZGWXphFhQxGmEJlpIiBFRIRtKqpzWJFbt8wEmnDgQlCp/zm4qW1n4ta/W6U2U2vTDUlDXfGMk+7xoRYEgkWXpY3lFX9nSHvUwI36AGa1hzVWxyRvSb6A9Njcqi0W+ij0hSF44D7YrqBa0OQB2JWY/oJfkHN7HeEzfmHSGwDhLYKrflHiS10uWb7W4IwAy4eMfbTuI9v2zzs6Xeb4faaL/Os4swJDwrhw48L4LdfzPvYzggmuYEeqasN3irJLjo1scCV9/rHS/F2NOfqNjQr2nsoTabLNpnHwUxS6iNQNLK9qTd5jU57plofwGIogiCRAcAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:06:51Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:25Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="2476e2fa472ba5cb7a43d465af58c5c7" form="base64">H4sIAAAAAAAAAIVVTW8aMRC9+1fMpVJyiATptypXAiIIFfkQoEY9mt0BLBYb2SaU/vrO2Muuk0b04p1582zPjO23sx1iKWc8ijjO0cywsKb0fVxahykG3Y6HRQTEjfZBmQLnTj1jRbNPAJwQca+2KHkQE1zNjzuU9AU2xEzpanwj+QP3++0CnfiFykkemJ2wyK/D9ypoa1Slw1FmtugVzprjVtZfMcWVCkHJ+isGdu889hwqmUxgW/StCoNKeS/ZgmiKYYUYZBzFVBUoeaAVtjsMOlgnWzMuEOeKOaqt5IESVyW6hVWu5Mq0Wd3YrdJmisFpfEY3WJMnM5qP+5wjc9xzS86vuPJi9Dgb6t/naMS4Igqtd6fc5pFKJua5CUyDmpdSnaLfV+F/CUOipSkPy7Zt/60U7BJaus9bOnJ2v8ubBxHJKXmUe8aNyWt9URAT8tzibaPti/aQRztP/Tr1TTxpU86CQ7MK67Hpo9rTUwiSYTjhoA2cIqK/DB05UNWWra6c6NU6gNKO3evaXTjEP8jIezlCEyrMoA/yzlJBKuTgRzl06NcZ8knS9tasMuizvKXl4UC5sftFjlQVA19P3FUNdDuEWJdy7Mqf2laUBvgGu5a3e+d0oQzS86ro8T3jrCARGJv0SmoMPIPcAMd34KJDWuHDN+jCgY4yXKb+sZC8bp6P6sKdW4ZGWXphFhQxGmEJlpIiBFRIRtKqpzWJFbt8wEmnDgQlCp/zm4qW1n4ta/W6U2U2vTDUlDXfGMk+7xoRYEgkWXpY3lFX9nSHvUwI36AGa1hzVWxyRvSb6A9Njcqi0W+ij0hSF44D7YrqBa0OQB2JWY/oJfkHN7HeEzfmHSGwDhLYKrflHiS10uWb7W4IwAy4eMfbTuI9v2zzs6Xeb4faaL/Os4swJDwrhw48L4LdfzPvYzggmuYEeqasN3irJLjo1scCV9/rHS/F2NOfqNjQr2nsoTabLNpnHwUxS6iNQNLK9qTd5jU57plofwGIogiCRAcAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:06:52Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:25Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="2476e2fa472ba5cb7a43d465af58c5c7" form="base64">H4sIAAAAAAAAAIVVTW8aMRC9+1fMpVJyiATptypXAiIIFfkQoEY9mt0BLBYb2SaU/vrO2Muuk0b04p1582zPjO23sx1iKWc8ijjO0cywsKb0fVxahykG3Y6HRQTEjfZBmQLnTj1jRbNPAJwQca+2KHkQE1zNjzuU9AU2xEzpanwj+QP3++0CnfiFykkemJ2wyK/D9ypoa1Slw1FmtugVzprjVtZfMcWVCkHJ+isGdu889hwqmUxgW/StCoNKeS/ZgmiKYYUYZBzFVBUoeaAVtjsMOlgnWzMuEOeKOaqt5IESVyW6hVWu5Mq0Wd3YrdJmisFpfEY3WJMnM5qP+5wjc9xzS86vuPJi9Dgb6t/naMS4Igqtd6fc5pFKJua5CUyDmpdSnaLfV+F/CUOipSkPy7Zt/60U7BJaus9bOnJ2v8ubBxHJKXmUe8aNyWt9URAT8tzibaPti/aQRztP/Tr1TTxpU86CQ7MK67Hpo9rTUwiSYTjhoA2cIqK/DB05UNWWra6c6NU6gNKO3evaXTjEP8jIezlCEyrMoA/yzlJBKuTgRzl06NcZ8knS9tasMuizvKXl4UC5sftFjlQVA19P3FUNdDuEWJdy7Mqf2laUBvgGu5a3e+d0oQzS86ro8T3jrCARGJv0SmoMPIPcAMd34KJDWuHDN+jCgY4yXKb+sZC8bp6P6sKdW4ZGWXphFhQxGmEJlpIiBFRIRtKqpzWJFbt8wEmnDgQlCp/zm4qW1n4ta/W6U2U2vTDUlDXfGMk+7xoRYEgkWXpY3lFX9nSHvUwI36AGa1hzVWxyRvSb6A9Njcqi0W+ij0hSF44D7YrqBa0OQB2JWY/oJfkHN7HeEzfmHSGwDhLYKrflHiS10uWb7W4IwAy4eMfbTuI9v2zzs6Xeb4faaL/Os4swJDwrhw48L4LdfzPvYzggmuYEeqasN3irJLjo1scCV9/rHS/F2NOfqNjQr2nsoTabLNpnHwUxS6iNQNLK9qTd5jU57plofwGIogiCRAcAAA==</internal-file>
@@ -24,6 +24,7 @@ public enum BravoSensorDataMetadata {
"BravoNet_Node0x0A0_Voltage", "BravoNet_Node0x0A0_Temperature", "BravoNet_Node0x0A0_Current"));
public final int rideHeightColumn = getColumnIndex(RIDE_HEIGHT);
public final int columnCount = columns.size();
public List<String> getColumns() {
return columns;
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking.impl;
import java.util.Arrays;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sse.common.TimePoint;
@@ -34,4 +36,31 @@ public class DoubleVectorFixImpl implements DoubleVectorFix {
return fixData;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(fixData);
result = prime * result + ((timePoint == null) ? 0 : timePoint.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DoubleVectorFixImpl other = (DoubleVectorFixImpl) obj;
if (!Arrays.equals(fixData, other.fixData))
return false;
if (timePoint == null) {
if (other.timePoint != null)
return false;
} else if (!timePoint.equals(other.timePoint))
return false;
return true;
}
}
@@ -71,6 +71,8 @@ public class AbstractGPSFixStoreTest {
MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory();
mongoOF.getGPSFixCollection().drop();
mongoOF.getGPSFixMetadataCollection().drop();
mongoOF.getRaceLogCollection().drop();
mongoOF.getRegattaLogCollection().drop();
}
protected void map(RegattaLog regattaLog, Competitor comp, DeviceIdentifier device, long from, long to) {
@@ -0,0 +1,657 @@
package com.sap.sailing.domain.racelogtracking.test.impl;
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 org.mockito.Mockito.mock;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.mongodb.DB;
import com.mongodb.MongoException;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.impl.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.regatta.MappingEventVisitor;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
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.RegattaLogDefineMarkEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRevokeEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
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.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.SensorFix;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.persistence.impl.CollectionNames;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.impl.BravoDataFixMapper;
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.BravoFixTrack;
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.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.SensorFixTrackImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.WithID;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class SensorFixStoreAndLoadTest {
private static final long START_OF_TRACKING = 100;
private static final long MID_OF_TRACKING = 200;
private static final long END_OF_TRACKING = 300;
private static final long FIX_TIMESTAMP = 110;
private static final long FIX_TIMESTAMP2 = 120;
private static final long FIX_TIMESTAMP3 = 210;
private static final long AFTER_LAST_FIX = FIX_TIMESTAMP3 + 1;
private static final double FIX_RIDE_HEIGHT = 1337.0;
private static final double FIX_RIDE_HEIGHT2 = 1338.0;
private static final double FIX_RIDE_HEIGHT3 = 1336.0;
private static final double FIX_TEST_VALUE = 12.0;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
protected final DeviceIdentifier deviceTest = new SmartphoneImeiIdentifier("b");
protected RaceLog raceLog;
protected RegattaLog regattaLog;
protected SensorFixStore store;
protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null,
null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null);
protected final Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null,
null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null);
protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark");
protected final Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
protected final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
private DynamicTrackedRace trackedRace;
protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
return new GPSFixMovingImpl(new DegreePosition(lat, lng), new MillisecondsTimePoint(millis),
new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
}
@Before
public void setUp() throws UnknownHostException, MongoException {
dropPersistedData();
raceLog = new RaceLogImpl("racelog");
raceLog.add(new RaceLogStartOfTrackingEventImpl(new MillisecondsTimePoint(START_OF_TRACKING), author, 0));
raceLog.add(new RaceLogEndOfTrackingEventImpl(new MillisecondsTimePoint(END_OF_TRACKING), author, 0));
regattaLog = new RegattaLogImpl("regattalog");
store = new MongoSensorFixStoreImpl(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(),
PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(), serviceFinderFactory);
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author,
new MillisecondsTimePoint(1), 0, mark));
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author,
new MillisecondsTimePoint(1), 0, mark2));
Course course = new CourseImpl("course",
Arrays.asList(new Waypoint[] { new WaypointImpl(mark), new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(comp, comp2));
DynamicTrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass,
/* startDate */ null, /* endDate */null, null, null, "a", null));
trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections.<Sideline> emptyList(),
EmptyWindStore.INSTANCE, 0, 0, 0, /* useMarkPassingCalculator */ false, OneDesignRankingMetric::new,
mock(RaceLogResolver.class));
}
private void dropPersistedData() {
DB db = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase();
db.getCollection(CollectionNames.GPS_FIXES.name()).drop();
db.getCollection(CollectionNames.GPS_FIXES_METADATA.name()).drop();
db.getCollection(CollectionNames.REGATTA_LOGS.name()).drop();
db.getCollection(CollectionNames.RACE_LOGS.name()).drop();
}
@After
public void after() {
dropPersistedData();
}
@Test
public void testLoadAlreadyAddedFixes() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true);
}
@Test
public void testAddFixesWhileTracking() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true);
}
@Test
public void testNoFixesAreLoadedIfNoStoredFixIsInTimeRange() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
fixLoaderAndTracker.stop(true);
}
@Test
public void testFixesNotInMappedTimeRangeAreIgnoredWhileTracking() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
fixLoaderAndTracker.stop(true);
}
@Test
public void testCompetitorWithoutMappingHasNoTrack() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
assertNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true);
}
@Test
public void testDeviceIsMappedToDifferentCompetitorsInDifferentTimeRanges() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME), 1);
fixLoaderAndTracker.stop(true);
}
@Test
public void testMultipleMappingsForOneDeviceAndCompetitor() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
fixLoaderAndTracker.stop(true);
}
@Test
public void testNothingLoadedForRevokedMapping() throws InterruptedException {
RegattaLogDeviceCompetitorBravoMappingEventImpl mappingEvent = new RegattaLogDeviceCompetitorBravoMappingEventImpl(
new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING),
new MillisecondsTimePoint(MID_OF_TRACKING));
regattaLog.add(mappingEvent);
regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes"));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true);
}
@Test
public void testFixesAreLoadedIfThereIsOneRevokedAndOneNonRevokedMapping() throws InterruptedException {
// revoked mapping timestamp 100-300
RegattaLogDeviceCompetitorBravoMappingEventImpl mappingEvent = new RegattaLogDeviceCompetitorBravoMappingEventImpl(
new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING),
new MillisecondsTimePoint(END_OF_TRACKING));
regattaLog.add(mappingEvent);
regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes"));
// non-revoked mapping timestamp 100-200
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
// Only Fixes from 100 to 200 may be included
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true);
}
@Test
public void testLoadFixesWhenClosingEventIsRevoked() throws InterruptedException {
UUID mappingEventId = UUID.randomUUID();
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(MillisecondsTimePoint.now(),
new MillisecondsTimePoint(3), author, mappingEventId, comp, device, new MillisecondsTimePoint(START_OF_TRACKING),
null));
RegattaLogCloseOpenEndedDeviceMappingEventImpl closeEvent = new RegattaLogCloseOpenEndedDeviceMappingEventImpl(
new MillisecondsTimePoint(4), author, mappingEventId, new MillisecondsTimePoint(MID_OF_TRACKING));
regattaLog.add(closeEvent);
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
testNumberOfRawFixes(bravoFixTrack, 2);
regattaLog.add(new RegattaLogRevokeEventImpl(author, closeEvent, "Test purposes"));
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(bravoFixTrack, 3);
fixLoaderAndTracker.stop(true);
}
@Test
public void testBravoFixIsCorrectlyWrapped() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
assertEquals(FIX_RIDE_HEIGHT,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
fixLoaderAndTracker.stop(true);
}
@Test
public void testMultipleFixTypesAreLoadedInSeparateTracks() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorTestMappingEventImpl(new MillisecondsTimePoint(1), author, comp,
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addTestFixes();
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1);
fixLoaderAndTracker.stop(true);
}
@Test
public void testMultipleFixTypesAreLoadedInSeparateTracksWhileTracking() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorTestMappingEventImpl(new MillisecondsTimePoint(1), author, comp,
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addTestFixes();
addBravoFixes();
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true);
}
@Test
public void testMultipleFixTypesAreMappedCorrectly() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorTestMappingEventImpl(new MillisecondsTimePoint(1), author, comp,
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addTestFixes();
addBravoFixes();
trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
assertEquals(FIX_RIDE_HEIGHT,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
assertEquals(FIX_RIDE_HEIGHT2,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP2)).getRideHeight());
assertEquals(FIX_RIDE_HEIGHT3,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP3)).getRideHeight());
TestFixTrackImpl<Competitor> testFixTrack = trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME);
assertEquals(FIX_TEST_VALUE,
testFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getTestValue());
fixLoaderAndTracker.stop(true);
}
private void addBravoFixes() {
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3));
}
private FixLoaderAndTracker createFixLoaderAndTracker() {
return new FixLoaderAndTracker(trackedRace, store, new SensorFixMapperFactory() {
private TestDataFixMapper testDataFixMapper = new TestDataFixMapper();
private BravoDataFixMapper bravoDataFixMapper = new BravoDataFixMapper();
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <FixT extends Timed, TrackT extends DynamicTrack<FixT>> SensorFixMapper<FixT, TrackT, Competitor> createCompetitorMapper(
Class<? extends RegattaLogDeviceMappingEvent<?>> eventType) {
if(bravoDataFixMapper.isResponsibleFor(eventType)) {
return (SensorFixMapper) bravoDataFixMapper;
}
if(testDataFixMapper.isResponsibleFor(eventType)) {
return (SensorFixMapper) testDataFixMapper;
}
throw new IllegalArgumentException("Unknown event type");
}
});
}
protected void testNumberOfRawFixes(Track<?> track, long expected) {
track.lockForRead();
assertEquals(expected, size(track.getRawFixes()));
track.unlockAfterRead();
}
private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
}
private void addTestFixes() {
store.storeFix(deviceTest, createTestDoubleVectorFixWithTestValue(FIX_TIMESTAMP, FIX_TEST_VALUE));
}
private DoubleVectorFix createTestDoubleVectorFixWithTestValue(long timestamp, double testValue) {
double[] fixData = new double[TestFixImpl.COLUMNS.size()];
fixData[TestFixImpl.TEST_COLUMN_INDEX] = testValue;
return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
}
private static class TestFixImpl implements SensorFix {
private static final long serialVersionUID = 2033254212013220L;
public static final String TEST_COLUMN = "testColumn";
public static final int TEST_COLUMN_INDEX = 1;
public static final List<String> COLUMNS = Collections.unmodifiableList(Arrays.asList("blub", TEST_COLUMN));
private final DoubleVectorFix fix;
public TestFixImpl(DoubleVectorFix fix) {
this.fix = fix;
}
@Override
public double get(String valueName) {
return fix.get(COLUMNS.indexOf(valueName));
}
@Override
public TimePoint getTimePoint() {
return fix.getTimePoint();
}
public double getTestValue() {
return fix.get(TEST_COLUMN_INDEX);
}
}
public class TestFixTrackImpl<ItemType extends WithID & Serializable> extends SensorFixTrackImpl<ItemType, TestFixImpl> {
private static final long serialVersionUID = 5517848726454386L;
public static final String TRACK_NAME = "TestFixTrack";
public TestFixTrackImpl(ItemType trackedItem, String trackName) {
super(trackedItem, trackName, TestFixImpl.COLUMNS,
TRACK_NAME + " for " + trackedItem);
}
public Double getTextValue(TimePoint timePoint) {
TestFixImpl fixAfter = getFirstFixAtOrAfter(timePoint);
if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixAfter.getTestValue();
}
TestFixImpl fixBefore = getLastFixAtOrBefore(timePoint);
if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
return fixBefore.getTestValue();
}
if (fixAfter == null || fixBefore == null) {
// the fix is out of the TimeRange where we have fixes
return null;
}
return fixBefore.getTestValue();
}
}
public class TestDataFixMapper implements SensorFixMapper<TestFixImpl, DynamicSensorFixTrack<Competitor, TestFixImpl>, Competitor> {
@Override
public DynamicSensorFixTrack<Competitor, TestFixImpl> getTrack(DynamicTrackedRace race, Competitor key) {
return race.getOrCreateSensorTrack(key, TestFixTrackImpl.TRACK_NAME,
() -> new TestFixTrackImpl<Competitor>(key, TestFixTrackImpl.TRACK_NAME));
}
@Override
public TestFixImpl map(DoubleVectorFix fix) {
return new TestFixImpl(fix);
}
@Override
public boolean isResponsibleFor(Class<? extends RegattaLogDeviceMappingEvent<?>> eventType) {
return RegattaLogDeviceCompetitorTestMappingEventImpl.class.isAssignableFrom(eventType);
}
}
public class RegattaLogDeviceCompetitorTestMappingEventImpl extends RegattaLogDeviceMappingEventImpl<Competitor>
implements RegattaLogDeviceCompetitorSensorDataMappingEvent {
private static final long serialVersionUID = -14940305448048753L;
public RegattaLogDeviceCompetitorTestMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint,
AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device,
TimePoint from, TimePoint to) {
super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to);
}
public RegattaLogDeviceCompetitorTestMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) {
super(logicalTimePoint, author, mappedTo, device, from, to);
}
@Override
public void accept(RegattaLogEventVisitor visitor) {
visitor.visit(this);
}
@Override
public void accept(MappingEventVisitor visitor) {
visitor.visit(this);
}
}
@Test
public void testThatMappingsOutsideOfTheTrackedIntervalDontCauseLoadingToFail() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(10), new MillisecondsTimePoint(20)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
deviceTest, new MillisecondsTimePoint(10), new MillisecondsTimePoint(20)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
assertNotNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
assertNotNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true);
}
}
@@ -0,0 +1,301 @@
package com.sap.sailing.domain.racelogtracking.test.impl;
import static org.junit.Assert.assertEquals;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.mongodb.DB;
import com.mongodb.MongoException;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.persistence.impl.CollectionNames;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl;
import com.sap.sailing.domain.racelog.tracking.FixReceivedListener;
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sse.common.Timed;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class SensorFixStoreTest {
private static final long FIX_TIMESTAMP = 110;
private static final long FIX_TIMESTAMP2 = 120;
private static final double FIX_RIDE_HEIGHT = 1337.0;
private static final double FIX_RIDE_HEIGHT2 = 1338.0;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
protected final DeviceIdentifier device2 = new SmartphoneImeiIdentifier("b");
protected SensorFixStore store;
protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
return new GPSFixMovingImpl(new DegreePosition(lat, lng), new MillisecondsTimePoint(millis),
new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
}
@Before
public void setUp() throws UnknownHostException, MongoException {
dropPersistedData();
newStore();
}
private void newStore() {
store = new MongoSensorFixStoreImpl(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(),
PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(), serviceFinderFactory);
}
@After
public void after() {
dropPersistedData();
}
private void dropPersistedData() {
DB db = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase();
db.getCollection(CollectionNames.GPS_FIXES.name()).drop();
db.getCollection(CollectionNames.GPS_FIXES_METADATA.name()).drop();
}
@Test
public void testFixIsPersisted() throws Exception {
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
assertEquals(1, store.getNumberOfFixes(device));
}
/**
* Ensures that no local caching in the store makes in seem to work but data will be lost on restart of the system.
*/
@Test
public void testFixIsFoundByOtherStoreInstance() throws Exception {
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
newStore();
assertEquals(1, store.getNumberOfFixes(device));
}
@Test
public void testFixDataIsPreservedOnStore() throws Exception {
DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
verifySingleFix(fix, 100, 200, device, true);
}
@Test
public void testFixWithinExclusiveBoundsIsLoaded() throws Exception {
DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
verifySingleFix(fix, FIX_TIMESTAMP - 1, FIX_TIMESTAMP + 1, device, false);
}
@Test
public void testFixOnInclusiveLowerBoundIsLoaded() throws Exception {
DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
verifySingleFix(fix, FIX_TIMESTAMP, FIX_TIMESTAMP + 1, device, true);
}
@Test
public void testFixOnInclusiveUpperBoundIsLoaded() throws Exception {
DoubleVectorFix fix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
verifySingleFix(fix, FIX_TIMESTAMP-1, FIX_TIMESTAMP, device, true);
}
@Test
public void testFixesOnExclusiveBoundsArentLoaded() throws Exception {
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
addBravoFix(device, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
verifyNoFix(FIX_TIMESTAMP, FIX_TIMESTAMP2, device, false);
}
@Test
public void testSingleListenerIsNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix);
}
@Test
public void testMultipleListenersForSameDeviceAreNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> listener2 = mockFixReceivedListener();
store.addListener(listener1, device);
store.addListener(listener2, device);
DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verify(listener1, Mockito.times(1)).fixReceived(device, doubleVectorFix);
Mockito.verify(listener2, Mockito.times(1)).fixReceived(device, doubleVectorFix);
}
@Test
public void testListenerForNonMatchingDeviceIsNotNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
addBravoFix(device2, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verifyZeroInteractions(listener);
}
@Test
public void testOnlyListenerForMatchingDeviceIsNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> listener2 = mockFixReceivedListener();
store.addListener(listener1, device);
store.addListener(listener2, device2);
DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verify(listener1, Mockito.times(1)).fixReceived(device, doubleVectorFix);
Mockito.verifyZeroInteractions(listener2);
}
@Test
public void testListenerForMatchingDeviceIsNotifiedMultipleTimes() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
DoubleVectorFix doubleVectorFix1 = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
DoubleVectorFix doubleVectorFix2 = addBravoFix(device, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix1);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix2);
Mockito.verifyNoMoreInteractions(listener);
}
@Test
public void testAfterRemoveListenerIsNotNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
store.removeListener(listener);
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verifyZeroInteractions(listener);
}
@Test
public void testAfterRemoveForDeviceListenerIsNotNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
store.removeListener(listener, device);
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verifyZeroInteractions(listener);
}
@Test
public void testAfterRemoveForNonMatchingDeviceListenerIsNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
store.removeListener(listener, device2);
DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix);
}
@Test
public void testListenerForMultipleDevicesIsNotifiedMultipleTimes() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
store.addListener(listener, device2);
DoubleVectorFix doubleVectorFix1 = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
DoubleVectorFix doubleVectorFix2 = addBravoFix(device2, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device, doubleVectorFix1);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device2, doubleVectorFix2);
Mockito.verifyNoMoreInteractions(listener);
}
@Test
public void testAfterRemoveListenerForMultipleDevicesIsNotNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
store.addListener(listener, device2);
store.removeListener(listener);
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
addBravoFix(device2, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
Mockito.verifyZeroInteractions(listener);
}
@Test
public void testAfterRemoveListenerForOneDevicesIsNotifiedForOtherDevice() throws Exception {
FixReceivedListener<DoubleVectorFix> listener = mockFixReceivedListener();
store.addListener(listener, device);
store.addListener(listener, device2);
store.removeListener(listener, device);
addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
DoubleVectorFix doubleVectorFix2 = addBravoFix(device2, FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2);
Mockito.verify(listener, Mockito.times(1)).fixReceived(device2, doubleVectorFix2);
Mockito.verifyNoMoreInteractions(listener);
}
@Test
public void testAfterRemoveListenerOtherListenerIsNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> listener2 = mockFixReceivedListener();
store.addListener(listener1, device);
store.addListener(listener2, device);
store.removeListener(listener1);
DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verify(listener2, Mockito.times(1)).fixReceived(device, doubleVectorFix);
Mockito.verifyNoMoreInteractions(listener2);
Mockito.verifyZeroInteractions(listener1);
}
@Test
public void testAfterRemoveForDeviceListenerOtherListenerIsNotified() throws Exception {
FixReceivedListener<DoubleVectorFix> listener1 = mockFixReceivedListener();
FixReceivedListener<DoubleVectorFix> listener2 = mockFixReceivedListener();
store.addListener(listener1, device);
store.addListener(listener2, device);
store.removeListener(listener1, device);
DoubleVectorFix doubleVectorFix = addBravoFix(device, FIX_TIMESTAMP, FIX_RIDE_HEIGHT);
Mockito.verify(listener2, Mockito.times(1)).fixReceived(device, doubleVectorFix);
Mockito.verifyNoMoreInteractions(listener2);
Mockito.verifyZeroInteractions(listener1);
}
@SuppressWarnings("unchecked")
private FixReceivedListener<DoubleVectorFix> mockFixReceivedListener() {
FixReceivedListener<DoubleVectorFix> listener = Mockito.mock(FixReceivedListener.class);
return listener;
}
private void verifySingleFix(Timed expectedFix, long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
List<Timed> loadedFixes = loadFixes(start, end, device, inclusive);
assertEquals(1, loadedFixes.size());
assertEquals(expectedFix, loadedFixes.get(0));
}
private void verifyNoFix(long start, long end, DeviceIdentifier device, boolean inclusive) throws Exception {
List<Timed> loadedFixes = loadFixes(start, end, device, inclusive);
assertEquals(0, loadedFixes.size());
}
private List<Timed> loadFixes(long start, long end, DeviceIdentifier device, boolean inclusive)
throws TransformationException {
List<Timed> loadedFixes = new ArrayList<>();
store.loadFixes(loadedFixes::add, device, new MillisecondsTimePoint(start), new MillisecondsTimePoint(end), inclusive);
return loadedFixes;
}
private DoubleVectorFix addBravoFix(DeviceIdentifier device, long timestamp, double rideHeight) {
DoubleVectorFix fix = createBravoDoubleVectorFixWithRideHeight(timestamp, rideHeight);
store.storeFix(device, fix);
return fix;
}
private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
}
}
@@ -6,11 +6,13 @@ import java.util.Set;
import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.DoubleVectorFixMongoHandlerImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMongoHandlerImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.GPSFixMovingMongoHandlerImpl;
import com.sap.sse.common.TypeBasedServiceFinder;
@@ -20,6 +22,7 @@ public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder<FixM
private final DomainObjectFactoryImpl dof = new DomainObjectFactoryImpl(null, null);
private final FixMongoHandler<?> movingHandler = new GPSFixMovingMongoHandlerImpl(mof, dof);
private final FixMongoHandler<?> handler = new GPSFixMongoHandlerImpl(mof, dof);
private final FixMongoHandler<?> doubleVectorFixHandler = new DoubleVectorFixMongoHandlerImpl(mof, dof);
@Override
public FixMongoHandler<?> findService(String fixType) {
@@ -27,6 +30,7 @@ public class MockGPSFixMongoServiceFinder implements TypeBasedServiceFinder<FixM
fixType.equals(CompactGPSFixMovingImpl.class.getName())) return movingHandler;
if (fixType.equals(GPSFixImpl.class.getName()) ||
fixType.equals(CompactGPSFixImpl.class.getName())) return handler;
if (fixType.equals(DoubleVectorFixImpl.class.getName())) return doubleVectorFixHandler;
return null;
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:46:08Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:18:02Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="cdbdcb19e10086d34f715e2a7da26eef" form="base64">H4sIAAAAAAAAAF2RTU/DMAyG7/0VvnGZEGekHgYSUiVgY+3gnLXeajWNo9jbqBD/naQtbOLm+PXH+zgaTN2ROxTuRGqU2D1xyKs5C5c07DlkUhv3tnnkBlfhnYR0G2zFGzyQKIal5C+mQ5BjQBj4CK05IWiL8HX3DUvvgZyosRabW6hadJDmjQV7tpbPaeM0HjhAbanuRtWS6+AoSY5jA0hvgvqWXZzOEOb1YCQ7o625x4rzjymKBRmtyq1gkLzU2AfxmcxkxjWBqbmWllNqkm2c6SL7Ce0wwlYtyXO0khf7Ec9EzICmSb40atAbssATUYMnqhHOpO34TvjGpvLhcoYFCPXeDjPrDuMREpIkN/fZ7shDha7BkKcQdIwz432pHLCYxlT4qclSw+5GLzdPCwfUBXiLRvB36X1qL1jyuftyDu9n/D/p+hzJwDqSxm/2/tX0mJfLNZSROOE/JHuTnEnMFa6kQ6v/S2HOZz95rIJYdwIAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:07:55Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:18:02Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="cdbdcb19e10086d34f715e2a7da26eef" form="base64">H4sIAAAAAAAAAF2RTU/DMAyG7/0VvnGZEGekHgYSUiVgY+3gnLXeajWNo9jbqBD/naQtbOLm+PXH+zgaTN2ROxTuRGqU2D1xyKs5C5c07DlkUhv3tnnkBlfhnYR0G2zFGzyQKIal5C+mQ5BjQBj4CK05IWiL8HX3DUvvgZyosRabW6hadJDmjQV7tpbPaeM0HjhAbanuRtWS6+AoSY5jA0hvgvqWXZzOEOb1YCQ7o625x4rzjymKBRmtyq1gkLzU2AfxmcxkxjWBqbmWllNqkm2c6SL7Ce0wwlYtyXO0khf7Ec9EzICmSb40atAbssATUYMnqhHOpO34TvjGpvLhcoYFCPXeDjPrDuMREpIkN/fZ7shDha7BkKcQdIwz432pHLCYxlT4qclSw+5GLzdPCwfUBXiLRvB36X1qL1jyuftyDu9n/D/p+hzJwDqSxm/2/tX0mJfLNZSROOE/JHuTnEnMFa6kQ6v/S2HOZz95rIJYdwIAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:07:55Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:18:02Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="cdbdcb19e10086d34f715e2a7da26eef" form="base64">H4sIAAAAAAAAAF2RTU/DMAyG7/0VvnGZEGekHgYSUiVgY+3gnLXeajWNo9jbqBD/naQtbOLm+PXH+zgaTN2ROxTuRGqU2D1xyKs5C5c07DlkUhv3tnnkBlfhnYR0G2zFGzyQKIal5C+mQ5BjQBj4CK05IWiL8HX3DUvvgZyosRabW6hadJDmjQV7tpbPaeM0HjhAbanuRtWS6+AoSY5jA0hvgvqWXZzOEOb1YCQ7o625x4rzjymKBRmtyq1gkLzU2AfxmcxkxjWBqbmWllNqkm2c6SL7Ce0wwlYtyXO0khf7Ec9EzICmSb40atAbssATUYMnqhHOpO34TvjGpvLhcoYFCPXeDjPrDuMREpIkN/fZ7shDha7BkKcQdIwz432pHLCYxlT4qclSw+5GLzdPCwfUBXiLRvB36X1qL1jyuftyDu9n/D/p+hzJwDqSxm/2/tX0mJfLNZSROOE/JHuTnEnMFa6kQ6v/S2HOZz95rIJYdwIAAA==</internal-file>
@@ -48,7 +48,6 @@ public class EndOfTrackingController extends AbstractRaceChangeListener {
if (!Util.equalsWithNull(trackedRace.getEndOfTracking(), newEndOfTracking)) {
raceLog.add(new RaceLogEndOfTrackingEventImpl(newEndOfTracking,
raceLogEventAuthor, raceLog.getCurrentPassId()));
trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false);
}
}
}
@@ -46,7 +46,6 @@ public class StartOfTrackingController implements StartTimeChangedListener {
if (!Util.equalsWithNull(trackedRace.getStartOfTracking(), newStartOfTracking)) {
raceLog.add(new RaceLogStartOfTrackingEventImpl(newStartOfTracking,
raceLogEventAuthor, raceLog.getCurrentPassId()));
trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false);
}
}
}
@@ -161,6 +161,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
private void loadFixes(TimeRange timeRangeToLoad, DeviceMappingWithRegattaLogEvent<? extends WithID> mapping) {
if(timeRangeToLoad == null) {
return;
}
if (preemptiveStopRequested.get()) {
return;
}
@@ -194,8 +197,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
try {
@SuppressWarnings({ "unchecked" })
DeviceMapping<Competitor> competitorMapping = (DeviceMapping<Competitor>) mapping;
gpsFixStore.loadCompetitorTrack(track, competitorMapping, getStartOfTracking(),
getEndOfTracking());
gpsFixStore.loadCompetitorTrack(track, competitorMapping, timeRangeToLoad.from(),
timeRangeToLoad.to());
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo());
}
@@ -208,12 +211,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
try {
@SuppressWarnings("unchecked")
DeviceMapping<Mark> markMapping = (DeviceMapping<Mark>) mapping;
TimePoint from = getStartOfTracking();
TimePoint to = getEndOfTracking();
gpsFixStore.loadMarkTrack(track, markMapping, from, to);
gpsFixStore.loadMarkTrack(track, markMapping, timeRangeToLoad.from(), timeRangeToLoad.to());
if (track.getFirstRawFix() == null) {
logger.fine("Loading mark positions from outside of start/end of tracking interval (" + from
+ ".." + to + ") because no fixes were found in that interval");
logger.fine("Loading mark positions from outside of start/end of tracking interval (" + timeRangeToLoad.from()
+ ".." + timeRangeToLoad.to() + ") because no fixes were found in that interval");
// got an empty track for the mark; try again without constraining the mapping interval
// by start/end of tracking to at least attempt to get fixes at all in case there were any
// within the device mapping interval specified
@@ -264,12 +265,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
}
// TEMP: kommt von RaceLogSensorFixTracker
protected TimePoint getStartOfTracking() {
return trackedRace.getStartOfTracking();
}
// TEMP: kommt von RaceLogSensorFixTracker
protected TimePoint getEndOfTracking() {
return trackedRace.getEndOfTracking();
}
@@ -277,10 +276,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
protected void loadFixesForExtendedTimeRange(TimePoint loadFixesFrom, TimePoint loadFixesTo) {
final TimeRangeImpl extendedTimeRange = new TimeRangeImpl(loadFixesFrom, loadFixesTo);
deviceMappings.forEachMapping((mapping) -> {
TimeRange timeRangeToLoad = extendedTimeRange.intersection(mapping.getTimeRange());
if (timeRangeToLoad != null && !preemptiveStopRequested.get()) {
loadFixes(timeRangeToLoad, mapping);
}
loadFixes(extendedTimeRange.intersection(mapping.getTimeRange()), mapping);
});
}
@@ -292,7 +288,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
* @param updateCallback
* the {@link Runnable} callback used to run the update
*/
// TODO Consider using a thread pool here, after merging bug3864 into master
private void updateAsyncInternal(final Runnable updateCallback) {
synchronized (FixLoaderAndTracker.this) {
activeLoaders.incrementAndGet();
@@ -301,9 +296,9 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().execute(new Runnable() {
@Override
public void run() {
trackedRace.lockForSerializationRead();
try {
if (!preemptiveStopRequested.get()) {
trackedRace.lockForSerializationRead();
setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5);
synchronized (FixLoaderAndTracker.this) {
FixLoaderAndTracker.this.notifyAll();
@@ -311,15 +306,18 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
updateCallback.run();
}
} finally {
synchronized (FixLoaderAndTracker.this) {
int currentActiveLoaders = activeLoaders.decrementAndGet();
FixLoaderAndTracker.this.notifyAll();
if (currentActiveLoaders == 0) {
setStatusAndProgress(stopRequested.get() ? TrackedRaceStatusEnum.FINISHED
: TrackedRaceStatusEnum.TRACKING, 1.0);
try {
synchronized (FixLoaderAndTracker.this) {
int currentActiveLoaders = activeLoaders.decrementAndGet();
FixLoaderAndTracker.this.notifyAll();
if (currentActiveLoaders == 0) {
setStatusAndProgress(stopRequested.get() ? TrackedRaceStatusEnum.FINISHED
: TrackedRaceStatusEnum.TRACKING, 1.0);
}
}
} finally {
trackedRace.unlockAfterSerializationRead();
}
trackedRace.unlockAfterSerializationRead();
}
}
});
@@ -350,7 +348,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
@Override
protected void mappingRemoved(DeviceMappingWithRegattaLogEvent<WithID> mapping) {
final DeviceIdentifier device = mapping.getDevice();
if(!hasMappingForDevice(device)) {
if (!hasMappingForDevice(device)) {
sensorFixStore.removeListener(listener, device);
}
// TODO if tracks are always associated to only one device mapping, we could remove tracks here
@@ -366,8 +364,22 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
@Override
protected void mappingChanged(DeviceMappingWithRegattaLogEvent<WithID> oldMapping,
DeviceMappingWithRegattaLogEvent<WithID> newMapping) {
// TODO can the new time range be bigger than the old one? In this case we would need to load the
// additional time range.
final TimeRange newTimeRange = newMapping.getTimeRange();
final TimeRange oldTimeRange = oldMapping.getTimeRange();
if(newTimeRange.endsAfter(oldTimeRange)) {
final TimePoint oldTo= oldTimeRange.to();
final TimePoint newFrom = newTimeRange.from();
final TimePoint from = oldTo.after(newFrom) ? oldTo : newFrom;
final TimeRange rangeToLoad = new TimeRangeImpl(from, newTimeRange.to());
loadFixes(rangeToLoad, newMapping);
}
if(newTimeRange.startsBefore(oldTimeRange)) {
final TimePoint oldFrom= oldTimeRange.from();
final TimePoint newTo = newTimeRange.to();
final TimePoint to = oldFrom.after(newTo) ? oldFrom : newTo;
final TimeRange rangeToLoad = new TimeRangeImpl(newTimeRange.from(), to);
loadFixes(rangeToLoad, newMapping);
}
}
}
}
@@ -120,7 +120,7 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader {
stopTrackerIfStillRunning(preemptive);
trackedRace.removeListener(raceChangeListener);
synchronized (knownRaceLogs) {
for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) {
for (RaceLog raceLog : knownRaceLogs) {
removeRaceLogUnlocked(raceLog);
}
}
@@ -142,10 +142,12 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader {
}
boolean isForTracking() {
for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) {
RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
if (raceLogTrackingState.isForTracking()) {
return true;
synchronized (knownRaceLogs) {
for (RaceLog raceLog : knownRaceLogs) {
RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
if (raceLogTrackingState.isForTracking()) {
return true;
}
}
}
return false;
@@ -48,7 +48,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* sure to not produce a memory leak.
*/
private final Set<RegattaLog> knownRegattaLogs = new HashSet<>();
/**
* Lock object to be used when accessing {@link #knownRegattaLogs}.
*/
@@ -127,16 +127,16 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor));
knownRegattaLogs.clear();
});
}
}
protected void updateMappings() {
try {
updateMappings(true);
updateMappingsInternal();
} catch (Exception e) {
logger.log(Level.WARNING, "Could not update device mappings", e);
}
}
/**
* Calls the given callback for every known mapping that's currently known.
*
@@ -163,11 +163,11 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
public void forEachMapping(BiConsumer<ItemT, DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> {
for (Map.Entry<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> entry : mappings.entrySet()) {
ItemT item = entry.getKey();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) {
callback.accept(item, mapping);
}
ItemT item = entry.getKey();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) {
callback.accept(item, mapping);
}
}
});
}
@@ -187,13 +187,13 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
Consumer<DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
LockUtil.executeWithReadLock(mappingsLock, () -> {
List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDevice = mappingsByDevice.get(device);
if (mappingsForDevice != null) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappingsForDevice) {
if (mapping.getTimeRange().includes(timePoint)) {
callback.accept(mapping);
}
if (mappingsForDevice != null) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappingsForDevice) {
if (mapping.getTimeRange().includes(timePoint)) {
callback.accept(mapping);
}
}
}
});
}
@@ -236,9 +236,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
*
* @throws DoesNotHaveRegattaLogException
*/
private final <FixT extends Timed, TrackT extends DynamicTrack<FixT>> void updateMappings(boolean loadIfNotCovered) {
// TODO remove fixes, if mappings have been removed
// check if there are new time ranges not covered so far
private final <FixT extends Timed, TrackT extends DynamicTrack<FixT>> void updateMappingsInternal() {
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings = calculateMappings();
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> oldMappings = new HashMap<>();
LockUtil.lockForWrite(mappingsLock);
@@ -261,18 +259,20 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
} finally {
LockUtil.unlockAfterWrite(mappingsLock);
}
if (loadIfNotCovered) {
calculateDiff(oldMappings, newMappings);
}
calculateDiff(oldMappings, newMappings);
}
/**
* Calculates <em>and applies</em> the mapping changes by removing listeners no longer needed for the mappings removed,
* and by loading and adding the fixes for extended or added mappings.
*/
private void calculateDiff(Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> previousMappings,
Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings) {
Set<ItemT> itemsToProcess = new HashSet<ItemT>(previousMappings.keySet());
itemsToProcess.addAll(newMappings.keySet());
for (ItemT item : itemsToProcess) {
if (!newMappings.containsKey(item)) {
previousMappings.get(item).forEach(this::mappingRemoved);
previousMappings.get(item).forEach(this::mappingRemovedInternal);
} else {
final List<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings = previousMappings.containsKey(item)
? previousMappings.get(item) : Collections.emptyList();
@@ -281,22 +281,30 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
DeviceMappingWithRegattaLogEvent<ItemT> oldMapping = findAndRemoveMapping(newMapping,
oldMappings);
if (oldMapping == null) {
mappingAdded(newMapping);
} else if (newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
mappingChanged(oldMapping, newMapping);
mappingAddedInternal(newMapping);
} else if (!newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
mappingChangedInternal(oldMapping, newMapping);
}
}
oldMappings.forEach(this::mappingRemoved);
oldMappings.forEach(this::mappingRemovedInternal);
}
}
}
/**
* Called when a {@link DeviceMapping} was removed.
*
* @param mapping the removed mapping
*/
protected abstract void mappingRemoved(DeviceMappingWithRegattaLogEvent<ItemT> mapping);
private void mappingRemovedInternal(DeviceMappingWithRegattaLogEvent<ItemT> mapping) {
try {
mappingRemoved(mapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while removing mapping " + mapping, e);
}
}
/**
* Called when a {@link DeviceMapping} was added.
@@ -304,6 +312,14 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* @param mapping the new mapping
*/
protected abstract void mappingAdded(DeviceMappingWithRegattaLogEvent<ItemT> mapping);
private void mappingAddedInternal(DeviceMappingWithRegattaLogEvent<ItemT> mapping) {
try {
mappingAdded(mapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while adding mapping " + mapping, e);
}
}
/**
* Called when a {@link DeviceMapping} was changed regarding its mapped time range.
@@ -315,6 +331,15 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
protected abstract void mappingChanged(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping,
DeviceMappingWithRegattaLogEvent<ItemT> newMapping);
private void mappingChangedInternal(DeviceMappingWithRegattaLogEvent<ItemT> oldMapping,
DeviceMappingWithRegattaLogEvent<ItemT> newMapping) {
try {
mappingChanged(oldMapping, newMapping);
} catch(Exception e) {
logger.log(Level.SEVERE, "error while changing mapping old: " + oldMapping + "; new: " + newMapping, e);
}
}
private DeviceMappingWithRegattaLogEvent<ItemT> findAndRemoveMapping(
DeviceMappingWithRegattaLogEvent<ItemT> mappingToFind,
List<DeviceMappingWithRegattaLogEvent<ItemT>> newItemsToProcess) {
@@ -65,7 +65,7 @@ public abstract class BaseRegattaLogDeviceMappingFinder<ItemT extends WithID>
if (!endOfFirstHalf.before(from)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice()));
}
if (!to.before(startOfSecondHalf)) {
if (to == null || !to.before(startOfSecondHalf)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to,
event.getDevice()));
}
@@ -46,7 +46,7 @@ public class WaypointImpl implements Waypoint {
@Override
public String toString() {
return getName();
return (getName()==null?"":getName()) + " ("+getPassingInstructions()+")";
}
@Override
@@ -10,7 +10,6 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import org.junit.Before;
@@ -30,6 +29,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.ThreadPoolUtil;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -77,7 +77,7 @@ public class ConcurrencyTest extends OnlineTracTracBasedTest {
logger.info("1 thread: "+duration+"ns");
logger.info("number of approximation points: "+approximation1.size());
dp = new DouglasPeucker<Competitor, GPSFixMoving>(teamAquaTrack, Executors.newFixedThreadPool(numberOfCPUs));
dp = new DouglasPeucker<Competitor, GPSFixMoving>(teamAquaTrack, ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor());
start = System.nanoTime();
List<GPSFixMoving> approximation2 = dp.approximate(new MeterDistance(3), firstMarkPassing.getTimePoint(), lastMarkPassing.getTimePoint());
duration = System.nanoTime()-start;
@@ -0,0 +1,80 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.regattalog.RegattaLogStore;
import com.sap.sailing.domain.test.mock.MockedTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Util;
public class RegattaLogAttachOnTrackedRaceTest {
private class MyMockedRegattaLogStore implements RegattaLogStore {
private Map<RegattaLikeIdentifier, RegattaLog> regattaLogs = new HashMap<>();
@Override
public RegattaLog getRegattaLog(RegattaLikeIdentifier regattaLikeId, boolean ignoreCache) {
if(!regattaLogs.containsKey(regattaLikeId)) {
regattaLogs.put(regattaLikeId, new RegattaLogImpl(regattaLikeId));
}
return regattaLogs.get(regattaLikeId);
}
@Override
public void removeRegattaLog(RegattaLikeIdentifier regattaLikeId) {
}
@Override
public void addImportedRegattaLog(RegattaLog regattaLog, RegattaLikeIdentifier identifier) {
}
}
private class MyMockedTrackedRace extends MockedTrackedRace {
private static final long serialVersionUID = -5450144964435682919L;
private RegattaLog regattaLog;
@Override
public void attachRegattaLog(RegattaLog regattaLog) {
this.regattaLog = regattaLog;
}
@Override
public Iterable<RegattaLog> getAttachedRegattaLogs() {
return Collections.singletonList(regattaLog);
}
}
private FlexibleLeaderboard leaderboard;
@Before
public void setUp() {
leaderboard = new FlexibleLeaderboardImpl(EmptyRaceLogStore.INSTANCE, new MyMockedRegattaLogStore(), "", null, new LowPoint(), null);
}
@Test
public void testAttachRegattaLogIsCalledWhenTrackedRaceIsSetToARaceColumn() {
RaceColumn column = leaderboard.addRaceColumn("R1", false);
TrackedRace trackedRace = new MyMockedTrackedRace();
Fleet defaultFleet = Util.get(column.getFleets(), 0);
column.setTrackedRace(defaultFleet, trackedRace);
assertEquals(1, Util.size(trackedRace.getAttachedRegattaLogs()));
assertSame(leaderboard.getRegattaLog(), Util.get(trackedRace.getAttachedRegattaLogs(), 0));
}
}
@@ -11,6 +11,7 @@ import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.base.Competitor;
@@ -27,6 +28,7 @@ import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.TrackedLegImpl;
@@ -137,7 +139,6 @@ public class StarbordSideOfStartLineRecognitionTest {
Position startStarboardPosition = new DegreePosition(0, 1);
Position windwardPosition = new DegreePosition(10, 0.5);
MockedTrackedRaceImpl trackedRace = createTrackedRaceWithMarkPositions(startPortPosition, startStarboardPosition, windwardPosition);
Position p = trackedRace.getOrCreateTrack(trackedRace.getStartLine(now).getStarboardMarkWhileApproachingLine()).
getEstimatedPosition(now, /* extrapolate */ false);
assertEquals(startStarboardPosition, p);
@@ -184,6 +185,8 @@ public class StarbordSideOfStartLineRecognitionTest {
when(trackedRace.getTrackedLeg(course.getFirstLeg())).thenReturn(trackedLeg);
when(trackedRace.getApproximatePosition(startWaypoint, now)).thenCallRealMethod();
when(trackedRace.getApproximatePosition(windwardWaypoint, now)).thenCallRealMethod();
when(trackedRace.getApproximatePosition(Matchers.eq(startWaypoint), Matchers.eq(now), Matchers.any(MarkPositionAtTimePointCache.class))).thenCallRealMethod();
when(trackedRace.getApproximatePosition(Matchers.eq(windwardWaypoint), Matchers.eq(now), Matchers.any(MarkPositionAtTimePointCache.class))).thenCallRealMethod();
return trackedRace;
}
@@ -37,8 +37,10 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl;
import com.sap.sailing.domain.tracking.impl.TrackedLegImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -87,9 +89,8 @@ public class TargetTimeEstimationTest {
TrackedLeg trackedLeg = new TrackedLegImpl(trackedRace, leg, competitors);
//Actual test of functionality
Duration duration = trackedLeg.getEstimatedTimeToComplete(mockedPolars, timepoint);
Duration duration = trackedLeg.getEstimatedTimeToComplete(mockedPolars, timepoint, new MarkPositionAtTimePointCacheImpl(trackedRace, timepoint));
assertEquals(75494, duration.asMillis(), 100);
}
@Test
@@ -123,9 +124,10 @@ public class TargetTimeEstimationTest {
when(leg.getFrom()).thenReturn(from);
when(leg.getTo()).thenReturn(to);
when(trackedRace.getApproximatePosition(from, timepoint)).thenReturn(startOfLeg);
when(trackedRace.getApproximatePosition(to, timepoint)).thenReturn(endOfLeg);
when(trackedRace.getApproximatePosition(eq(from), eq(timepoint))).thenReturn(startOfLeg);
when(trackedRace.getApproximatePosition(eq(to), eq(timepoint))).thenReturn(endOfLeg);
when(trackedRace.getApproximatePosition(eq(from), eq(timepoint), any(MarkPositionAtTimePointCache.class))).thenReturn(startOfLeg);
when(trackedRace.getApproximatePosition(eq(to), eq(timepoint), any(MarkPositionAtTimePointCache.class))).thenReturn(endOfLeg);
SpeedWithBearing speedWithBearingPort = new KnotSpeedWithBearingImpl(6, new DegreeBearingImpl(-45));
SpeedWithBearingWithConfidence<Void> boatSpeedWithBearingWithConfidencePort = new SpeedWithBearingWithConfidenceImpl<Void>(speedWithBearingPort, 1, null);
@@ -139,9 +141,8 @@ public class TargetTimeEstimationTest {
TrackedLeg trackedLeg = new TrackedLegImpl(trackedRace, leg, competitors);
//Actual test of functionality
Duration duration = trackedLeg.getEstimatedTimeToComplete(mockedPolars, timepoint);
Duration duration = trackedLeg.getEstimatedTimeToComplete(mockedPolars, timepoint, new MarkPositionAtTimePointCacheImpl(trackedRace, timepoint));
assertEquals(213513, duration.asMillis(), 100);
}
@Test
@@ -175,9 +176,10 @@ public class TargetTimeEstimationTest {
when(leg.getFrom()).thenReturn(from);
when(leg.getTo()).thenReturn(to);
when(trackedRace.getApproximatePosition(from, timepoint)).thenReturn(startOfLeg);
when(trackedRace.getApproximatePosition(to, timepoint)).thenReturn(endOfLeg);
when(trackedRace.getApproximatePosition(eq(from), eq(timepoint))).thenReturn(startOfLeg);
when(trackedRace.getApproximatePosition(eq(to), eq(timepoint))).thenReturn(endOfLeg);
when(trackedRace.getApproximatePosition(eq(from), eq(timepoint), any(MarkPositionAtTimePointCache.class))).thenReturn(startOfLeg);
when(trackedRace.getApproximatePosition(eq(to), eq(timepoint), any(MarkPositionAtTimePointCache.class))).thenReturn(endOfLeg);
SpeedWithBearing speedWithBearingPort = new KnotSpeedWithBearingImpl(11, new DegreeBearingImpl(150));
SpeedWithBearingWithConfidence<Void> boatSpeedWithBearingWithConfidencePort = new SpeedWithBearingWithConfidenceImpl<Void>(speedWithBearingPort, 1, null);
@@ -191,7 +193,7 @@ public class TargetTimeEstimationTest {
TrackedLeg trackedLeg = new TrackedLegImpl(trackedRace, leg, competitors);
//Actual test of functionality
Duration duration = trackedLeg.getEstimatedTimeToComplete(mockedPolars, timepoint);
Duration duration = trackedLeg.getEstimatedTimeToComplete(mockedPolars, timepoint, new MarkPositionAtTimePointCacheImpl(trackedRace, timepoint));
assertEquals(95090, duration.asMillis(), 100);
}
@@ -22,6 +22,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
@@ -50,6 +51,7 @@ public class TrackedRaceCenterTest {
trackedRace = mock(DynamicTrackedRaceImpl.class);
when(trackedRace.getCenterOfCourse(Matchers.any(TimePoint.class))).thenCallRealMethod();
when(trackedRace.getApproximatePosition(Matchers.any(Waypoint.class), Matchers.any(TimePoint.class))).thenCallRealMethod();
when(trackedRace.getApproximatePosition(Matchers.any(Waypoint.class), Matchers.any(TimePoint.class), Matchers.any(MarkPositionAtTimePointCache.class))).thenCallRealMethod();
RaceDefinition race = mock(RaceDefinition.class);
when(trackedRace.getRace()).thenReturn(race);
course = mock(Course.class);
@@ -63,6 +63,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
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.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
@@ -621,7 +622,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
return null;
}
@@ -42,6 +42,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
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.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
@@ -218,7 +219,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
return null;
}
@@ -116,7 +116,7 @@ public class BravoFixTrackSerializationTest {
}
private void addOrReplaceBravoFixToTrack(boolean replace) {
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.getColumns().size()];
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
track.add(new BravoFixImpl(new DoubleVectorFixImpl(timePoint, fixData)), replace);
}
@@ -43,6 +43,10 @@ public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutio
protected abstract Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries();
/**
* For all tracked races found on any of the {@link #getRaceColumnsOfSeries() race columns} produces a valid and
* potentially empty set.
*/
private Map<TrackedRace, Set<TrackedRace>> reloadAndGetPreviousRacesByRace() {
final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = new HashMap<>();
for (Entry<Fleet, Iterable<? extends RaceColumn>> raceColumnsInSeries : getRaceColumnsOfSeries().entrySet()) {
@@ -96,11 +100,13 @@ public abstract class AbstractRaceExecutionOrderProvider implements RaceExecutio
@Override
public Set<TrackedRace> getPreviousRacesInExecutionOrder(TrackedRace race) {
Set<TrackedRace> result = Collections.emptySet();
final Set<TrackedRace> result;
final Map<TrackedRace, Set<TrackedRace>> previousRacesByRace = getPreviousRacesByRace();
if (previousRacesByRace != null) {
result = previousRacesByRace.get(race);
}
} else {
result = Collections.emptySet();
}
return result;
}
}
@@ -6,12 +6,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ScheduledExecutorService;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
@@ -31,7 +28,7 @@ import com.sap.sse.common.Util;
public class DouglasPeucker<ItemType, FixType extends GPSFix> {
private final GPSFixTrack<ItemType, FixType> track;
private final Executor executor;
private final ScheduledExecutorService executor;
public DouglasPeucker(GPSFixTrack<ItemType, FixType> track) {
this.track = track;
@@ -42,7 +39,7 @@ public class DouglasPeucker<ItemType, FixType extends GPSFix> {
* Allows clients to specify a non-default, optionally multi-threaded executor that will be used to perform the
* high-effort computations.
*/
public DouglasPeucker(GPSFixTrack<ItemType, FixType> track, Executor executor) {
public DouglasPeucker(GPSFixTrack<ItemType, FixType> track, ScheduledExecutorService executor) {
this.track = track;
this.executor = executor;
}
@@ -96,15 +93,8 @@ public class DouglasPeucker<ItemType, FixType extends GPSFix> {
if (fix.getTimePoint().compareTo(to) > 0) {
break;
}
RunnableFuture<Util.Pair<FixType, Distance>> crossTrackErrorFuture = new FutureTask<Util.Pair<FixType, Distance>>(
new Callable<Util.Pair<FixType, Distance>>() {
@Override
public Util.Pair<FixType, Distance> call() throws Exception {
return new Util.Pair<FixType, Distance>(fix, fix.getPosition().absoluteCrossTrackError(
fromPosition, bearing));
}
});
executor.execute(crossTrackErrorFuture);
Future<Util.Pair<FixType, Distance>> crossTrackErrorFuture = executor.submit(
()->new Util.Pair<FixType, Distance>(fix, fix.getPosition().absoluteCrossTrackError(fromPosition, bearing)));
crossTrackErrorFutures.add(crossTrackErrorFuture);
}
for (Future<Util.Pair<FixType, Distance>> crossTrackErrorFuture : crossTrackErrorFutures) {
@@ -23,6 +23,7 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.impl.FutureTaskWithTracingGet;
/**
* Caches the expensive to compute {@link LeaderboardDTO} results of a
@@ -140,15 +141,16 @@ public class LeaderboardDTOCache implements LeaderboardCache {
LockUtil.unlockAfterRead(leaderboardCacheLock);
}
if (future == null) {
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
@Override
public LeaderboardDTO call() throws Exception {
LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false);
return result;
}
});
future = new FutureTaskWithTracingGet<LeaderboardDTO>(LeaderboardDTOCache.class.getName()+" for leaderboard "+leaderboard.getName(),
new Callable<LeaderboardDTO>() {
@Override
public LeaderboardDTO call() throws Exception {
LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails,
waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false);
return result;
}
});
// The add(Leaderboard) method that the cache manager calls back on this class does nothing, so no synchronization required
this.leaderboardCacheManager.add(leaderboard); // ensure the leaderboard is tracked for changes to invalidate
LockUtil.lockForWrite(leaderboardCacheLock);
@@ -21,7 +21,6 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -97,6 +96,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.ThreadPoolUtil;
import com.sap.sse.util.impl.FutureTaskWithTracingGet;
/**
* Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events
@@ -311,7 +311,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
this.suppressedCompetitors = new HashSet<Competitor>();
this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
this.raceColumnListeners = new RaceColumnListeners();
this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>();
this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
initTransientFields();
}
@@ -330,7 +330,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
private void initTransientFields() {
this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>();
this.raceDetailsAtEndOfTrackingCache = new HashMap<>();
this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
// When many updates are triggered in a short period of time by a single thread, ensure that the single thread
// providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
@@ -1225,7 +1225,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
// Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
// futures to finish:
Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
Map<RaceColumn, Future<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
for (final RaceColumn raceColumn : this.getRaceColumns()) {
boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn;
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
@@ -1253,13 +1253,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn);
}
FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(
Future<List<CompetitorDTO>> task = executor.submit(
() -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
executor.execute(task);
competitorsFromBestToWorstTasks.put(raceColumn, task);
}
// wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data
for (Map.Entry<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
for (Map.Entry<RaceColumn, Future<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
try {
result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
} catch (InterruptedException e) {
@@ -1323,7 +1322,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
for (final RaceColumn raceColumn : this.getRaceColumns()) {
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
Future<LeaderboardEntryDTO> future = executor.submit(() -> {
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails != null
@@ -1331,7 +1330,6 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
fillTotalPointsUncorrected, cache);
});
executor.execute(future);
futuresForColumnName.put(raceColumn.getName(), future);
}
for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
@@ -1611,7 +1609,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
if (raceDetails == null) {
needToRunRaceDetails = true;
raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() {
raceDetails = new FutureTaskWithTracingGet<RaceDetails>("RaceDetails for "+trackedRace, new Callable<RaceDetails>() {
@Override
public RaceDetails call() throws Exception {
TimePoint end = trackedRace.getEndOfRace();
@@ -9,7 +9,6 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -173,7 +172,7 @@ public class MarkPassingCalculator {
+ " while waiting for new GPSFixes");
}
listener.getQueue().drainTo(allNewFixInsertions);
logger.finer("MPC for "+raceName+" recieved "+ allNewFixInsertions.size()+" new updates.");
logger.fine("MPC for "+raceName+" received "+ allNewFixInsertions.size()+" new updates.");
for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) {
if (listener.isEndMarker(fixInsertion)) {
logger.info("Stopping "+MarkPassingCalculator.this+"'s listener for race "+raceName);
@@ -212,8 +211,7 @@ public class MarkPassingCalculator {
}
executor.invokeAll(tasks);
}
updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings,
unsuppressedMarkPassings);
updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, unsuppressedMarkPassings);
computeMarkPasses(competitorFixes, markFixes);
competitorFixes.clear();
markFixes.clear();
@@ -284,9 +282,24 @@ public class MarkPassingCalculator {
fixes.addAll(fixesAffectedByNewMarkFixes.getValue());
}
}
List<Callable<Object>> tasks = new ArrayList<>();
for (Entry<Competitor, Set<GPSFix>> c : combinedCompetitorFixes.entrySet()) {
tasks.add(Executors.callable(new ComputeMarkPassings(c.getKey(), c.getValue())));
List<Callable<Void>> tasks = new ArrayList<>();
for (final Entry<Competitor, Set<GPSFix>> c : combinedCompetitorFixes.entrySet()) {
final Runnable runnable = new ComputeMarkPassings(c.getKey(), c.getValue());
tasks.add(new Callable<Void>() {
@Override
public Void call() throws Exception {
runnable.run();
return null;
}
/**
* A reasonable toString implementation helps identifying these tasks in log messages for long-running tasks
*/
@Override
public String toString() {
return "Mark passing calculation for competitor "+c.getKey()+" with "+c.getValue().size()+" fixes";
}
});
}
try {
executor.invokeAll(tasks);
@@ -296,8 +309,8 @@ public class MarkPassingCalculator {
}
private class ComputeMarkPassings implements Runnable {
Competitor c;
Iterable<GPSFix> fixes;
final Competitor c;
final Iterable<GPSFix> fixes;
public ComputeMarkPassings(Competitor c, Iterable<GPSFix> fixes) {
this.c = c;
@@ -306,12 +319,22 @@ public class MarkPassingCalculator {
@Override
public void run() {
logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)");
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes);
logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates for race "+raceName+
" and competitor "+c+" and will remove "
+ Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
try {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)");
}
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes);
if (logger.isLoggable(Level.FINER)) {
logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates for race "+raceName+
" and competitor "+c+" and will remove "
+ Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
}
chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
} catch (Exception e) {
// make sure the exception is logged and not only "swallowed" and suppressed by an invokeAll(...) statement
logger.log(Level.SEVERE, "Exception trying to compute mark passings for competitor "+c, e);
throw e;
}
}
}
@@ -39,6 +39,8 @@ import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl;
import com.sap.sailing.domain.tracking.impl.TimedComparator;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -47,6 +49,7 @@ import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.util.ThreadPoolUtil;
/**
@@ -383,24 +386,30 @@ public class CandidateFinderImpl implements CandidateFinder {
changedWaypoints.add(w);
}
}
final Set<Callable<Void>> tasks = new HashSet<>();;
final Set<Callable<Void>> tasks = new HashSet<>();
final Thread executingThread = Thread.currentThread(); // most likely the MarkPassingCalculator.Listen thread
for (Competitor c : race.getRace().getCompetitors()) {
tasks.add(()->{
List<Candidate> badCans = new ArrayList<>();
List<Candidate> newCans = new ArrayList<>();
for (Waypoint w : changedWaypoints) {
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
badCans.addAll(xteCans.values());
xteCans.clear();
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
badCans.addAll(distanceCans.values());
distanceCans.clear();
LockUtil.propagateLockSetFrom(executingThread); // "inherit" the course read lock from the "calling" thread into the thread pool executor thread
try {
List<Candidate> badCans = new ArrayList<>();
List<Candidate> newCans = new ArrayList<>();
for (Waypoint w : changedWaypoints) {
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
badCans.addAll(xteCans.values());
xteCans.clear();
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
badCans.addAll(distanceCans.values());
distanceCans.clear();
}
Set<GPSFix> allFixes = getAllFixes(c);
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
return null;
} finally {
LockUtil.unpropagateLockSetFrom(executingThread); // "return" the course read lock from the thread pool executor thread to the "calling" thread
}
Set<GPSFix> allFixes = getAllFixes(c);
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
return null;
});
}
executor.invokeAll(tasks);
@@ -513,8 +522,8 @@ public class CandidateFinderImpl implements CandidateFinder {
private Set<GPSFix> getAllFixes(Competitor c) {
Set<GPSFix> fixes = new TreeSet<GPSFix>(comp);
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(c);
track.lockForRead();
try {
track.lockForRead();
for (GPSFix fix : track.getFixes(
timeRangeForValidCandidates.from(), /* fromInclusive */ true,
timeRangeForValidCandidates.to(), /* toInclusive */ true)) {
@@ -542,8 +551,8 @@ public class CandidateFinderImpl implements CandidateFinder {
affectedFixes.add(fix);
GPSFix fixBefore;
GPSFix fixAfter;
track.lockForRead();
try {
track.lockForRead();
TimePoint timePoint = fix.getTimePoint();
fixBefore = track.getLastFixBefore(timePoint);
fixAfter = track.getFirstFixAfter(timePoint);
@@ -604,15 +613,16 @@ public class CandidateFinderImpl implements CandidateFinder {
if (Math.abs(dis.getMeters()) < Math.abs(disBefore.getMeters())
&& Math.abs(dis.getMeters()) < Math.abs(disAfter.getMeters())) {
t = fix.getTimePoint();
final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t);
p = fix.getPosition();
Double newProbability = getDistanceBasedProbability(w, t, dis);
Double newProbability = getDistanceBasedProbability(w, t, dis, markPositionCache);
if (newProbability != null) {
// FIXME why not generate the candidate here where we have all information at hand?
final double newOnCorrectSideOfWaypointPenalty = getSidePenalty(w, p, t, portMark);
final double newOnCorrectSideOfWaypointPenalty = getSidePenalty(w, p, t, portMark, markPositionCache);
newProbability *= newOnCorrectSideOfWaypointPenalty * PENALTY_FOR_DISTANCE_CANDIDATES;
final Double newStartProbabilityBasedOnOtherCompetitors;
if (isStartWaypoint(w)) {
newStartProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t);
newStartProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t, markPositionCache);
if (newStartProbabilityBasedOnOtherCompetitors != null) {
newProbability *= newStartProbabilityBasedOnOtherCompetitors;
}
@@ -663,6 +673,7 @@ public class CandidateFinderImpl implements CandidateFinder {
private Map<Waypoint, List<Distance>> getDistances(Competitor c, GPSFix fix) {
// TODO Possibly for specific waypoints
Map<Waypoint, List<Distance>> result = distanceCache.get(c).get(fix);
final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, fix.getTimePoint());
if (result == null) {
// Else calculate distances and put them into the cache
result = new LinkedHashMap<>();
@@ -670,7 +681,7 @@ public class CandidateFinderImpl implements CandidateFinder {
course.lockForRead();
try {
for (Waypoint w : course.getWaypoints()) {
List<Distance> distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint());
List<Distance> distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint(), markPositionCache);
result.put(w, distances);
}
} finally {
@@ -811,13 +822,14 @@ public class CandidateFinderImpl implements CandidateFinder {
result = new HashMap<>();
Position p = fix.getPosition();
TimePoint t = fix.getTimePoint();
final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t);
Course course = race.getRace().getCourse();
course.lockForRead();
try {
for (Waypoint w : course.getWaypoints()) {
List<Distance> distances = new ArrayList<>();
result.put(w, distances);
for (Util.Pair<Position, Bearing> crossingInfo : getCrossingInformation(w, t)) {
for (Util.Pair<Position, Bearing> crossingInfo : getCrossingInformation(w, t, markPositionCache)) {
if (crossingInfo.getA() != null && crossingInfo.getB() != null) {
distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB()));
}
@@ -831,20 +843,6 @@ public class CandidateFinderImpl implements CandidateFinder {
return result;
}
/**
* Calculates the cross-track error of each fix to the position and crossing bearing of each waypoint. Gates have
* two of these and lines always go from the port mark to the starboard mark.
*
*
* /* private void calculateCrossTrackErrors(Competitor c, Iterable<GPSFix> fixes, Iterable<Waypoint>
* waypointsToCalculate) { for (GPSFix fix : fixes) { Position fixPos = fix.getPosition(); TimePoint t =
* fix.getTimePoint(); Map<Waypoint, List<Double>> waypointXTE = new HashMap<Waypoint, List<Double>>();
* crossTrackErrors.get(c).put(fix, waypointXTE); for (Waypoint w : race.getRace().getCourse().getWaypoints()) {
* List<Double> xte = new ArrayList<>(); waypointXTE.put(w, xte); for (Util.Pair<Position, Bearing> crossingInfo :
* getCrossingInformation(w, t)) { xte.add(fixPos.crossTrackError(crossingInfo.getA(),
* crossingInfo.getB()).getMeters()); } } } }
*/
/**
* {@code xte1} and {@code xte2} are expected to have different signums or both be 0. The method
* interpolates between the time points {@code t1} and {@code t2} according to where the XTE is
@@ -856,10 +854,11 @@ public class CandidateFinderImpl implements CandidateFinder {
final double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2)));
final TimePoint t = t1.plus((long) (differenceInMillis * ratio));
final Position p = race.getTrack(c).getEstimatedPosition(t, false);
final List<Distance> distances = calculateDistance(p, w, t);
final List<Distance> distances = calculateDistance(p, w, t, new MarkPositionAtTimePointCacheImpl(race, t));
final Distance d = portMark ? distances.get(0) : distances.get(1);
final double sidePenalty = getSidePenalty(w, p, t, portMark);
double probability = getDistanceBasedProbability(w, t, d) * sidePenalty;
final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t);
final double sidePenalty = getSidePenalty(w, p, t, portMark, markPositionCache);
double probability = getDistanceBasedProbability(w, t, d, markPositionCache) * sidePenalty;
final Double passesInTheRightDirectionProbability = passesInTheRightDirection(w, xte1, xte2, portMark);
// null would mean "unknown"; no penalty for those cases
probability = passesInTheRightDirectionProbability == null ? probability : probability * passesInTheRightDirectionProbability;
@@ -869,7 +868,7 @@ public class CandidateFinderImpl implements CandidateFinder {
// at time point t the other competitors are largely not even close to the start waypoint;
// this will make start candidates much more likely if many other competitors are also very close to the
// start, and it will help ruling out those candidates where someone is practicing a start just for themselves
startProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t);
startProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t, markPositionCache);
if (startProbabilityBasedOnOtherCompetitors != null) {
probability *= startProbabilityBasedOnOtherCompetitors;
}
@@ -932,9 +931,9 @@ public class CandidateFinderImpl implements CandidateFinder {
* A low variance with a positive average (meaning on course side) indicates that everyone else may have started around the
* same time, making this a probable late starter's candidate. Therefore, low variance with positive average shall increase
* a probability that was low because of a large weighted average distance.
*
* @param t
* the time point at which to consider the other competitors behavior relative to the start waypoint
*
* @return {@code null} if the {@link #race} has a gate start or the start time is known; a probability between 0..1
* (inclusive) where 0 means that based on all but {@code c}'s relation to the start line it seems
* completely unlikely that a candidate for {@code c} at time {@code t} could have been a start candidate; 1
@@ -942,11 +941,13 @@ public class CandidateFinderImpl implements CandidateFinder {
* a fact that this must have been the start.
*
*/
private Double getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(Competitor c, TimePoint t) {
private Double getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(Competitor c, TimePoint t, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
final Double result;
if (race.getStartOfRace(/* inferred */ false) == null && race.isGateStart() != Boolean.TRUE) {
final Waypoint start = race.getRace().getCourse().getFirstWaypoint();
final Iterable<Pair<Position, Bearing>> crossingInformationForStart = getCrossingInformation(start, t);
final Iterable<Pair<Position, Bearing>> crossingInformationForStart = getCrossingInformation(start, t, markPositionCache);
if (start == null) {
result = 1.0;
} else {
@@ -960,7 +961,7 @@ public class CandidateFinderImpl implements CandidateFinder {
final Position estimatedPositionAtT = track.getEstimatedPosition(t, /* extrapolate */ true);
// consider that a position may not be available for that other competitor, although a track exists
if (estimatedPositionAtT != null) {
final Distance otherCompetitorsDistanceToStartAtT = getMinDistanceOrNull(calculateDistance(estimatedPositionAtT, start, t));
final Distance otherCompetitorsDistanceToStartAtT = getMinDistanceOrNull(calculateDistance(estimatedPositionAtT, start, t, markPositionCache));
if (otherCompetitorsDistanceToStartAtT != null) {
final Distance crossTrackError;
if (startIsLine) {
@@ -1095,14 +1096,16 @@ public class CandidateFinderImpl implements CandidateFinder {
* to be negative. If the passing instructions are {@link PassingInstruction#Line}, it checks whether the boat
* passed between the two marks.
*/
private double getSidePenalty(Waypoint w, Position p, TimePoint t, boolean portMark) {
private double getSidePenalty(Waypoint w, Position p, TimePoint t, boolean portMark, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
boolean isOnRightSide = true;
Distance onWrongSide = new MeterDistance(0);
PassingInstruction instruction = getPassingInstructions(w);
if (instruction == PassingInstruction.Line) {
List<Position> pos = new ArrayList<>();
for (Mark m : w.getMarks()) {
Position po = race.getOrCreateTrack(m).getEstimatedPosition(t, false);
Position po = markPositionCache.getEstimatedPosition(m);
if (po == null) {
isOnRightSide = true;
break;
@@ -1130,11 +1133,11 @@ public class CandidateFinderImpl implements CandidateFinder {
|| instruction == PassingInstruction.FixedBearing || instruction == PassingInstruction.Offset) {
m = w.getMarks().iterator().next();
} else if (instruction == PassingInstruction.Gate) {
Util.Pair<Mark, Mark> pair = getPortAndStarboardMarks(t, w);
Util.Pair<Mark, Mark> pair = getPortAndStarboardMarks(t, w, markPositionCache);
m = portMark ? pair.getA() : pair.getB();
}
if (m != null) {
Util.Pair<Position, Bearing> crossingInfo = Util.get(getCrossingInformation(w, t), portMark ? 0 : 1);
Util.Pair<Position, Bearing> crossingInfo = Util.get(getCrossingInformation(w, t, markPositionCache), portMark ? 0 : 1);
isOnRightSide = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90))).getMeters() < 0;
if (isOnRightSide == false) {
onWrongSide = p.getDistance(crossingInfo.getA());
@@ -1188,9 +1191,11 @@ public class CandidateFinderImpl implements CandidateFinder {
/**
* @return a probability based on the distance to <code>w</code> and the average leg lengths before and after.
*/
private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance) {
private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
final Double result;
Distance legLength = getAverageLengthOfAdjacentLegs(t, w);
Distance legLength = getAverageLengthOfAdjacentLegs(t, w, markPositionCache);
if (legLength != null) {
result = 1 / (STRICTNESS_OF_DISTANCE_BASED_PROBABILITY/* Raising this will make it stricter */
// reduce distance by 2x the typical HDOP, accounting for the possibility that some distance from the mark
@@ -1205,26 +1210,38 @@ public class CandidateFinderImpl implements CandidateFinder {
/**
* @return an average of the estimated legs before and after <code>w</code>.
*/
private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) {
Course course = race.getRace().getCourse();
if (w == course.getFirstWaypoint()) {
return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
final Distance result;
final Course course = race.getRace().getCourse();
if (course.getNumberOfWaypoints() < 2) {
result = null;
} else if (w == course.getFirstWaypoint()) {
result = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t, markPositionCache);
} else if (w == course.getLastWaypoint()) {
return race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
result = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t, markPositionCache);
} else {
Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t);
Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t, markPositionCache);
Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t, markPositionCache);
if (after != null && before != null) {
return new MeterDistance(before.add(after).getMeters() / 2);
result = new MeterDistance(before.add(after).getMeters() / 2);
} else {
result = null;
}
return null;
}
return result;
}
/**
* Calculates the distance from p to each mark of w at the timepoint t.
*
* @param markPositionCache
* a mark position cache for time point {@code t} for this finder's {@link #race}
*/
private List<Distance> calculateDistance(Position p, Waypoint w, TimePoint t) {
private List<Distance> calculateDistance(Position p, Waypoint w, TimePoint t, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
final List<Distance> distances = new ArrayList<>();
PassingInstruction instruction = getPassingInstructions(w);
boolean singleMark = false;
@@ -1236,25 +1253,25 @@ public class CandidateFinderImpl implements CandidateFinder {
singleMark = true;
break;
case Gate:
Util.Pair<Mark, Mark> posGate = getPortAndStarboardMarks(t, w);
Util.Pair<Mark, Mark> posGate = getPortAndStarboardMarks(t, w, markPositionCache);
if (posGate.getA() != null) {
Position portGatePosition = race.getOrCreateTrack(posGate.getA()).getEstimatedPosition(t, false);
Position portGatePosition = markPositionCache.getEstimatedPosition(posGate.getA());
distances.add(portGatePosition != null ? p.getDistance(portGatePosition) : null);
} else {
distances.add(null);
}
if (posGate.getB() != null) {
Position starboardGatePosition = race.getOrCreateTrack(posGate.getB()).getEstimatedPosition(t, false);
Position starboardGatePosition = markPositionCache.getEstimatedPosition(posGate.getB());
distances.add(starboardGatePosition != null ? p.getDistance(starboardGatePosition) : null);
} else {
distances.add(null);
}
break;
case Line:
Util.Pair<Mark, Mark> posLine = getPortAndStarboardMarks(t, w);
Util.Pair<Mark, Mark> posLine = getPortAndStarboardMarks(t, w, markPositionCache);
if (posLine.getA() != null && posLine.getB() != null) {
Position portLinePosition = race.getOrCreateTrack(posLine.getA()).getEstimatedPosition(t, false);
Position starboardLinePosition = race.getOrCreateTrack(posLine.getB()).getEstimatedPosition(t, false);
Position portLinePosition = markPositionCache.getEstimatedPosition(posLine.getA());
Position starboardLinePosition = markPositionCache.getEstimatedPosition(posLine.getB());
distances.add((portLinePosition != null && starboardLinePosition != null) ? p.getDistanceToLine(
portLinePosition, starboardLinePosition).abs() : null);
}
@@ -1270,8 +1287,7 @@ public class CandidateFinderImpl implements CandidateFinder {
break;
}
if (singleMark) {
Position markPosition = race.getOrCreateTrack(w.getMarks().iterator().next())
.getEstimatedPosition(t, false);
Position markPosition = markPositionCache.getEstimatedPosition(w.getMarks().iterator().next());
distances.add(markPosition != null ? p.getDistance(markPosition) : null);
}
return distances;
@@ -1292,44 +1308,46 @@ public class CandidateFinderImpl implements CandidateFinder {
* @return all possible ways to pass a waypoint, described as a position and a bearing. The line out of those two
* must be crossed.
*/
private Iterable<Util.Pair<Position, Bearing>> getCrossingInformation(Waypoint w, TimePoint t) {
private Iterable<Util.Pair<Position, Bearing>> getCrossingInformation(Waypoint w, TimePoint t, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
List<Util.Pair<Position, Bearing>> result = new ArrayList<>();
PassingInstruction instruction = getPassingInstructions(w);
if (instruction == PassingInstruction.Line) {
Util.Pair<Mark, Mark> marks = getPortAndStarboardMarks(t, w);
Util.Pair<Mark, Mark> marks = getPortAndStarboardMarks(t, w, markPositionCache);
Position portPosition = null;
Bearing b = null;
Mark portMark = marks.getA();
Mark starBoardMark = marks.getB();
if (portMark != null && starBoardMark != null) {
portPosition = race.getOrCreateTrack(portMark).getEstimatedPosition(t, false);
Position starboardPosition = race.getOrCreateTrack(starBoardMark).getEstimatedPosition(t, false);
portPosition = markPositionCache.getEstimatedPosition(portMark);
Position starboardPosition = markPositionCache.getEstimatedPosition(starBoardMark);
if (portPosition != null && starboardPosition != null) {
b = portPosition.getBearingGreatCircle(starboardPosition);
result.add(new Util.Pair<Position, Bearing>(portPosition, b));
}
}
} else if (instruction == PassingInstruction.Gate) {
Position before = race.getApproximatePosition(race.getTrackedLegFinishingAt(w).getLeg().getFrom(), t);
Position after = race.getApproximatePosition(race.getTrackedLegStartingAt(w).getLeg().getTo(), t);
Util.Pair<Mark, Mark> pos = getPortAndStarboardMarks(t, w);
Position before = markPositionCache.getApproximatePosition(race.getTrackedLegFinishingAt(w).getLeg().getFrom());
Position after = markPositionCache.getApproximatePosition(race.getTrackedLegStartingAt(w).getLeg().getTo());
Util.Pair<Mark, Mark> pos = getPortAndStarboardMarks(t, w, markPositionCache);
Mark portMark = pos.getA();
if (portMark != null) {
Position portPosition = race.getOrCreateTrack(portMark).getEstimatedPosition(t, false);
Position portPosition = markPositionCache.getEstimatedPosition(portMark);
Bearing crossingPort = before.getBearingGreatCircle(portPosition).middle(
after.getBearingGreatCircle(portPosition));
result.add(new Util.Pair<Position, Bearing>(portPosition, crossingPort));
}
Mark starboardMark = pos.getB();
if (starboardMark != null) {
Position starboardPosition = race.getOrCreateTrack(starboardMark).getEstimatedPosition(t, false);
Position starboardPosition = markPositionCache.getEstimatedPosition(starboardMark);
Bearing crossingStarboard = before.getBearingGreatCircle(starboardPosition).middle(
after.getBearingGreatCircle(starboardPosition));
result.add(new Util.Pair<Position, Bearing>(starboardPosition, crossingStarboard));
}
} else { // single mark
Bearing b = null;
Position p = race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, false);
Position p = markPositionCache.getEstimatedPosition(w.getMarks().iterator().next());
if (instruction == PassingInstruction.FixedBearing) {
b = w.getFixedBearing();
} else {
@@ -1339,23 +1357,23 @@ public class CandidateFinderImpl implements CandidateFinder {
// orthogonally to the adjacent leg:
if (w == race.getRace().getCourse().getFirstWaypoint()) {
if (instruction == PassingInstruction.None || instruction == PassingInstruction.Single_Unknown) {
b = race.getTrackedLegStartingAt(w).getLegBearing(t).add(new DegreeBearingImpl(90));
b = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)).add(new DegreeBearingImpl(90));
result.add(new Pair<>(p, b));
b = race.getTrackedLegStartingAt(w).getLegBearing(t).add(new DegreeBearingImpl(270));
b = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)).add(new DegreeBearingImpl(270));
} else {
b = race.getTrackedLegStartingAt(w).getLegBearing(t).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270));
b = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270));
}
} else if (w == race.getRace().getCourse().getLastWaypoint()) {
if (instruction == PassingInstruction.None || instruction == PassingInstruction.Single_Unknown) {
b = race.getTrackedLegFinishingAt(w).getLegBearing(t).add(new DegreeBearingImpl(90));
b = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)).add(new DegreeBearingImpl(90));
result.add(new Pair<>(p, b));
b = race.getTrackedLegFinishingAt(w).getLegBearing(t).add(new DegreeBearingImpl(270));
b = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)).add(new DegreeBearingImpl(270));
} else {
b = race.getTrackedLegFinishingAt(w).getLegBearing(t).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270));
b = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270));
}
} else {
Bearing before = race.getTrackedLegFinishingAt(w).getLegBearing(t);
Bearing after = race.getTrackedLegStartingAt(w).getLegBearing(t);
Bearing before = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w));
Bearing after = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w));
if (before != null && after != null) {
b = before.middle(after.reverse());
}
@@ -1367,14 +1385,17 @@ public class CandidateFinderImpl implements CandidateFinder {
}
/**
* @param markPositionCache
* a mark position cache for this finder's {@link #race} for time point {@code t}
* @return the marks of a waypoint with two marks in the order port, starboard (when approaching the waypoint from
* the direction of the waypoint beforehand.
*/
private Util.Pair<Mark, Mark> getPortAndStarboardMarks(TimePoint t, Waypoint w) {
private Util.Pair<Mark, Mark> getPortAndStarboardMarks(TimePoint t, Waypoint w, MarkPositionAtTimePointCache markPositionCache) {
assert t.equals(markPositionCache.getTimePoint());
assert race == markPositionCache.getTrackedRace();
List<Position> markPositions = new ArrayList<Position>();
for (Mark mark : w.getMarks()) {
final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */
false);
final Position estimatedMarkPosition = markPositionCache.getEstimatedPosition(mark);
if (estimatedMarkPosition == null) {
return new Util.Pair<Mark, Mark>(null, null);
}
@@ -1389,8 +1410,8 @@ public class CandidateFinderImpl implements CandidateFinder {
return new Util.Pair<Mark, Mark>(null, null);
}
final boolean isStartLine = indexOfWaypoint == 0;
final Bearing legDeterminingDirectionBearing = race.getTrackedLeg(
legs.get(isStartLine ? 0 : indexOfWaypoint - 1)).getLegBearing(t);
final Bearing legDeterminingDirectionBearing = markPositionCache.getLegBearing(race.getTrackedLeg(
legs.get(isStartLine ? 0 : indexOfWaypoint - 1)));
if (legDeterminingDirectionBearing == null) {
return new Util.Pair<Mark, Mark>(null, null);
}
@@ -1414,8 +1435,9 @@ public class CandidateFinderImpl implements CandidateFinder {
final TimePoint newNonInferredStartTime = race.getStartOfRace(/* inferred */ false);
final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime);
if (!Util.equalsWithNull(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from())) {
final TimePoint oldTimePointWhenToStartConsideringCandidates = timeRangeForValidCandidates.from();
timeRangeForValidCandidates = new TimeRangeImpl(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.to());
result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from());
result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, oldTimePointWhenToStartConsideringCandidates);
} else {
result = Collections.emptyMap();
}
@@ -1427,12 +1449,10 @@ public class CandidateFinderImpl implements CandidateFinder {
TimePoint oldFinishedTime, TimePoint newFinishedTime) {
final Map<Competitor, Pair<Iterable<Candidate>, Iterable<Candidate>>> result;
final TimePoint newTimePointWhenToFinishConsideringCandidates = getTimePointWhenToFinishConsideringCandidates(newFinishedTime);
final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to();
if (!Util.equalsWithNull(timeRangeForValidCandidates.to(), newTimePointWhenToFinishConsideringCandidates)) {
final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to();
timeRangeForValidCandidates = new TimeRangeImpl(timeRangeForValidCandidates.from(), newTimePointWhenToFinishConsideringCandidates);
result = updateCandiatesAfterRaceTimeRangeChanged(
oldTimePointWhenToFinishConsideringCandidates,
newTimePointWhenToFinishConsideringCandidates);
result = updateCandiatesAfterRaceTimeRangeChanged(oldTimePointWhenToFinishConsideringCandidates, newTimePointWhenToFinishConsideringCandidates);
} else {
result = Collections.emptyMap();
}
@@ -37,7 +37,7 @@ public interface DoubleVectorFixImporter {
* @throws IOException
* if there is a problem while reading the file
*/
void importFixes(InputStream inputStream, Callback callback, String sourceName)
void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName)
throws FormatNotSupportedException, IOException;
/**
@@ -0,0 +1,58 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sse.common.TimePoint;
/**
* Some calculations that span several method calls require access to the same {@link Mark} positions for the same
* {@link TimePoint} within the scope of the same {@link TrackedRace} repeatedly. Usually, for such a compound
* calculation it doesn't matter whether a new fix has arrived in between, making it valid to cache the mark
* positions---once calculated---for the remainder of the compound operation. Based on these positions, approximate
* waypoint positions and leg bearings can also be calculated and cached.
* <p>
*
* Implementations of this interface may cache the results of computing a mark's position for the duration of this
* object.
*
* @author Axel Uhl (D043530)
*
*/
public interface MarkPositionAtTimePointCache {
/**
* Gets the non-extrapolated mark position in this cache's tracked race at the time point
* specified.
*
* @see TrackedRace#getOrCreateTrack(Mark)
* @see GPSFixTrack#getEstimatedPosition(TimePoint, boolean)
*/
Position getEstimatedPosition(Mark mark);
/**
* Gets the approximate position of a waypoint in the scope of this cache's tracked race at
* the time point specified.
*
* @see TrackedRace#getApproximatePosition(Waypoint, TimePoint)
*/
Position getApproximatePosition(Waypoint waypoint);
/**
* Gets the bearing of the tracked leg at the time point specified, optionally based on mark position
* information already stored in this cache.
*
* @see TrackedLeg#getLegBearing(TimePoint)
*/
Bearing getLegBearing(TrackedLeg trackedLeg);
/**
* @return the time point for which this cache holds values
*/
TimePoint getTimePoint();
/**
* @return the tracked race for which this cache holds values
*/
TrackedRace getTrackedRace();
}
@@ -56,6 +56,16 @@ public interface TrackedLeg extends Serializable {
Bearing getLegBearing(TimePoint at);
/**
* Same as {@link #getLegBearing(TimePoint)}, but giving the caller the possibility to provide a cache for
* mark positions and related position and bearing data
*
* @param markPositionCache
* a cache for this tracked leg's {@link MarkPositionAtTimePointCache#getTrackedRace() race} and the
* {@link MarkPositionAtTimePointCache#getTimePoint() timePoint} passed
*/
Bearing getLegBearing(TimePoint at, MarkPositionAtTimePointCache markPositionCache);
/**
* Returns the positive (absolute) distance of <code>p</code> to this leg's course middle line at <code>timePoint</code>,
* based on the position of the waypoints delimiting this leg at that time.
@@ -80,6 +90,16 @@ public interface TrackedLeg extends Serializable {
*/
Distance getGreatCircleDistance(TimePoint timePoint);
/**
* Same as {@link #getGreatCircleDistance(TimePoint)}, but giving the caller the possibility to provide a cache for
* mark positions and related position and bearing data
*
* @param markPositionCache
* a cache for this tracked leg's {@link MarkPositionAtTimePointCache#getTrackedRace() race} and the
* {@link MarkPositionAtTimePointCache#getTimePoint() timePoint} passed
*/
Distance getGreatCircleDistance(TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache);
/**
* If the current {@link #getLeg() leg} is +/- {@link TrackedLegImpl#UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees
* collinear with the wind's bearing, <code>pos1</code> is projected onto the line crossing <code>pos2</code> in the
@@ -144,7 +164,8 @@ public interface TrackedLeg extends Serializable {
* is not available
* @throws NoWindException no wind available. unable to determine legtypes for given timepoint
*/
Duration getEstimatedTimeToComplete(PolarDataService polarDataService, TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, NoWindException;
Duration getEstimatedTimeToComplete(PolarDataService polarDataService, TimePoint timepoint, MarkPositionAtTimePointCache markPositionCache)
throws NotEnoughDataHasBeenAddedException, NoWindException;
/**
* Computes the windward distance (for upwind/downwind legs) or the along-course distance (for reaching legs) at a reference time point
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.ranking.RankingMetric;
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
@@ -316,8 +317,22 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
* If the <code>waypoint</code> only has one {@link #getMarks() mark}, its position at time <code>timePoint</code>
* is returned. Otherwise, the center of gravity between the mark positions is computed and returned.
*/
Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint);
default Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
return getApproximatePosition(waypoint, timePoint, new NonCachingMarkPositionAtTimePointCache(this, timePoint));
}
/**
* Same as {@link #getApproximatePosition(Waypoint, TimePoint)}, but giving the caller the possibility to pass a
* cache of mark positions and related information that can help speed up compound operations requiring frequent
* access to the same marks in the same race for the same time point.
*
* @param markPositionCache
* a cache for this {@link MarkPositionAtTimePointCache#getTrackedRace() race} and the
* {@link MarkPositionAtTimePointCache#getTimePoint() timePoint} passed
*/
Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint,
MarkPositionAtTimePointCache markPositionCache);
/**
* 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
@@ -946,4 +961,5 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
* @return all currently attached {@link RaceLog}s or an empty Iterable if there aren't any
*/
Iterable<RaceLog> getAttachedRaceLogs();
}
@@ -1075,8 +1075,10 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends MappedTra
unlockAfterRead();
}
}
for (GPSTrackListener<ItemType, FixType> listener : getListeners()) {
listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
if (result) {
for (GPSTrackListener<ItemType, FixType> listener : getListeners()) {
listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
}
}
return result;
}
@@ -0,0 +1,62 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
public class MarkPositionAtTimePointCacheImpl extends NonCachingMarkPositionAtTimePointCache {
private final Map<Mark, Position> estimatedMarkPositions;
private final Map<Waypoint, Position> approximateWaypointPositions;
private final Map<TrackedLeg, Bearing> legBearings;
public MarkPositionAtTimePointCacheImpl(TrackedRace trackedRace, TimePoint timePoint) {
super(trackedRace, timePoint);
estimatedMarkPositions = new HashMap<>();
approximateWaypointPositions = new HashMap<>();
legBearings = new HashMap<>();
}
@Override
public Position getEstimatedPosition(Mark mark) {
final Position result;
if (estimatedMarkPositions.containsKey(mark)) {
result = estimatedMarkPositions.get(mark);
} else {
result = super.getEstimatedPosition(mark);
estimatedMarkPositions.put(mark, result);
}
return result;
}
@Override
public Position getApproximatePosition(Waypoint waypoint) {
final Position result;
if (approximateWaypointPositions.containsKey(waypoint)) {
result = approximateWaypointPositions.get(waypoint);
} else {
result = super.getApproximatePosition(waypoint);
approximateWaypointPositions.put(waypoint, result);
}
return result;
}
@Override
public Bearing getLegBearing(TrackedLeg trackedLeg) {
final Bearing result;
if (legBearings.containsKey(trackedLeg)) {
result = legBearings.get(trackedLeg);
} else {
result = super.getLegBearing(trackedLeg);
legBearings.put(trackedLeg, result);
}
return result;
}
}
@@ -0,0 +1,46 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
public class NonCachingMarkPositionAtTimePointCache implements MarkPositionAtTimePointCache {
private final TrackedRace trackedRace;
private final TimePoint timePoint;
public NonCachingMarkPositionAtTimePointCache(TrackedRace trackedRace, TimePoint timePoint) {
super();
this.trackedRace = trackedRace;
this.timePoint = timePoint;
}
@Override
public Position getEstimatedPosition(Mark mark) {
return trackedRace.getOrCreateTrack(mark).getEstimatedPosition(timePoint, /* extrapolate */ false);
}
@Override
public Position getApproximatePosition(Waypoint waypoint) {
return trackedRace.getApproximatePosition(waypoint, timePoint, this);
}
@Override
public Bearing getLegBearing(TrackedLeg trackedLeg) {
return trackedLeg.getLegBearing(timePoint, this);
}
@Override
public TrackedRace getTrackedRace() {
return trackedRace;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
}
@@ -28,6 +28,8 @@ import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.SensorFix;
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
@@ -740,6 +742,16 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
scheduleCacheRefresh(startOfInvalidation, endOfInvalidation);
}
}
@Override
public void competitorSensorFixAdded(Competitor competitor, String trackName, SensorFix fix) {
// no action required
}
@Override
public void competitorSensorTrackAdded(DynamicSensorFixTrack<Competitor, ?> track) {
// no action required
}
}
/**
@@ -39,6 +39,7 @@ import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuse
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -204,8 +205,15 @@ public class TrackedLegImpl implements TrackedLeg {
@Override
public Bearing getLegBearing(TimePoint at) {
Position startMarkPos = getTrackedRace().getApproximatePosition(getLeg().getFrom(), at);
Position endMarkPos = getTrackedRace().getApproximatePosition(getLeg().getTo(), at);
return getLegBearing(at, new MarkPositionAtTimePointCacheImpl(getTrackedRace(), at));
}
@Override
public Bearing getLegBearing(TimePoint at, MarkPositionAtTimePointCache markPositionCache) {
assert markPositionCache.getTimePoint().equals(at);
assert markPositionCache.getTrackedRace() == getTrackedRace();
Position startMarkPos = markPositionCache.getApproximatePosition(getLeg().getFrom());
Position endMarkPos = markPositionCache.getApproximatePosition(getLeg().getTo());
Bearing legBearing = (startMarkPos != null && endMarkPos != null) ? startMarkPos.getBearingGreatCircle(endMarkPos) : null;
return legBearing;
}
@@ -337,10 +345,12 @@ public class TrackedLegImpl implements TrackedLeg {
}
@Override
public Distance getGreatCircleDistance(TimePoint timePoint) {
public Distance getGreatCircleDistance(TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
assert markPositionCache.getTimePoint().equals(timePoint);
assert markPositionCache.getTrackedRace() == getTrackedRace();
final Distance result;
final Position approximatePositionOfFrom = getTrackedRace().getApproximatePosition(getLeg().getFrom(), timePoint);
final Position approximatePositionOfTo = getTrackedRace().getApproximatePosition(getLeg().getTo(), timePoint);
final Position approximatePositionOfFrom = markPositionCache.getApproximatePosition(getLeg().getFrom());
final Position approximatePositionOfTo = markPositionCache.getApproximatePosition(getLeg().getTo());
if (approximatePositionOfFrom != null && approximatePositionOfTo != null) {
result = approximatePositionOfFrom.getDistance(approximatePositionOfTo);
} else {
@@ -349,6 +359,11 @@ public class TrackedLegImpl implements TrackedLeg {
return result;
}
@Override
public Distance getGreatCircleDistance(TimePoint timePoint) {
return getGreatCircleDistance(timePoint, new NonCachingMarkPositionAtTimePointCache(getTrackedRace(), timePoint));
}
@Override
public Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) {
return getAbsoluteWindwardDistance(pos1, pos2, at, windPositionMode, new LeaderboardDTOCalculationReuseCache(at));
@@ -498,8 +513,10 @@ public class TrackedLegImpl implements TrackedLeg {
}
@Override
public Duration getEstimatedTimeToComplete(PolarDataService polarDataService, TimePoint timepoint)
public Duration getEstimatedTimeToComplete(PolarDataService polarDataService, TimePoint timepoint, MarkPositionAtTimePointCache markPositionCache)
throws NotEnoughDataHasBeenAddedException, NoWindException {
assert timepoint.equals(markPositionCache.getTimePoint());
assert getTrackedRace() == markPositionCache.getTrackedRace();
Position centralPosition = trackedRace.getCenterOfCourse(timepoint);
Wind wind = trackedRace.getWind(centralPosition, timepoint, getTrackedRace().getWindSources(WindSourceType.TRACK_BASED_ESTIMATION));
Position from = trackedRace.getApproximatePosition(leg.getFrom(), timepoint);
@@ -127,6 +127,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.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
import com.sap.sailing.domain.tracking.RaceListener;
@@ -158,6 +159,7 @@ import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater;
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
import com.sap.sse.util.impl.ArrayListNavigableSet;
import com.sap.sse.util.impl.FutureTaskWithTracingGet;
import difflib.DiffUtils;
import difflib.Patch;
@@ -750,7 +752,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
/**
* Monitor object to synchronize access to the {@link #updateStartAndEndOfTracking(boolean)} method. See bug 3922.
*/
private final Serializable updateStartAndEndOfTrackingMonitor = "";
private final Serializable updateStartAndEndOfTrackingMonitor = "updateStartAndEndOfTrackingMonitor";
/**
* Updates the start and end of tracking in the following precedence order:
@@ -1670,10 +1672,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
assert timePoint.equals(markPositionCache.getTimePoint());
assert this == markPositionCache.getTrackedRace();
Position result = null;
for (Mark mark : waypoint.getMarks()) {
Position nextPos = getOrCreateTrack(mark).getEstimatedPosition(timePoint, /* extrapolate */false);
Position nextPos = markPositionCache.getEstimatedPosition(mark);
if (result == null) {
result = nextPos;
} else if (nextPos != null) {
@@ -1875,7 +1879,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
synchronized (directionFromStartToNextMarkCache) {
future = directionFromStartToNextMarkCache.get(at);
if (future == null) {
newFuture = new FutureTask<Wind>(new Callable<Wind>() {
newFuture = new FutureTaskWithTracingGet<Wind>("getDirectionFromStartToNextMark for "+this, new Callable<Wind>() {
@Override
public Wind call() {
Wind result;
@@ -3892,8 +3896,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
throw new NotEnoughDataHasBeenAddedException("Target time estimation failed. No polar service available.");
}
Duration durationOfAllLegs = Duration.NULL;
final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(this, timepoint);
for (TrackedLeg leg : trackedLegs.values()) {
Duration durationOfLeg = leg.getEstimatedTimeToComplete(polarDataService, timepoint);
Duration durationOfLeg = leg.getEstimatedTimeToComplete(polarDataService, timepoint, markPositionCache);
durationOfAllLegs = durationOfAllLegs.plus(durationOfLeg);
}
return durationOfAllLegs;
@@ -50,6 +50,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
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.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
@@ -352,7 +353,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
return null;
}
@@ -28,6 +28,7 @@ import com.sap.sailing.gwt.autoplay.client.place.player.AutoPlayerConfiguration;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveLifecycle;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveSettings;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle;
@@ -44,7 +45,6 @@ import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeLifecycleTa
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycle;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.UserService;
public class DesktopStartView extends Composite implements StartView {
@@ -53,7 +53,7 @@ public class DesktopStartView extends Composite implements StartView {
interface StartPageViewUiBinder extends UiBinder<Widget, DesktopStartView> {
}
@UiField(provided=true) SAPHeaderWithAuthentication sapHeader;
@UiField(provided=true) SAPSailingHeaderWithAuthentication sapHeader;
@UiField(provided=true) ListBox localeSelectionBox;
@UiField(provided=true) ListBox eventSelectionBox;
@UiField(provided=true) ListBox leaderboardSelectionBox;
@@ -82,8 +82,7 @@ public class DesktopStartView extends Composite implements StartView {
this.eventBus = eventBus;
this.events = new ArrayList<EventDTO>();
sapHeader = new SAPHeaderWithAuthentication(StringMessages.INSTANCE.sapSailingAnalytics(),
StringMessages.INSTANCE.autoplayConfiguration());
sapHeader = new SAPSailingHeaderWithAuthentication(StringMessages.INSTANCE.autoplayConfiguration());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
eventSelectionBox = new ListBox();
@@ -4,23 +4,23 @@ import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.FullscreenUtil;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.shared.components.AbstractCompositeComponent;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.UserService;
public class SAPHeaderComponent extends AbstractCompositeComponent<SAPHeaderComponentSettings> {
private SAPHeaderComponentSettings settings;
private final SAPHeaderComponentLifecycle componentLifecycle;
private final SAPHeaderWithAuthentication sapHeader;
private final SAPSailingHeaderWithAuthentication sapHeader;
public SAPHeaderComponent(SAPHeaderComponentLifecycle componentLifecycle, UserService userService, SAPHeaderComponentSettings settings,
StringMessages stringMessages, boolean startInAutoScreenMode) {
this.componentLifecycle = componentLifecycle;
this.settings = settings;
this.sapHeader = new SAPHeaderWithAuthentication(stringMessages.sapSailingAnalytics(), settings.getTitle());
this.sapHeader = new SAPSailingHeaderWithAuthentication(settings.getTitle());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
initWidget(sapHeader);
@@ -0,0 +1,20 @@
package com.sap.sailing.gwt.common.authentication;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class SAPSailingHeaderWithAuthentication extends SAPHeaderWithAuthentication {
private static final String SAP_SAILING_URL = "http://www.sapsailing.com";
private static final String SAP_SAILING_APP_NAME = StringMessages.INSTANCE.sapSailingAnalytics();
public SAPSailingHeaderWithAuthentication() {
super(SAP_SAILING_APP_NAME, SAP_SAILING_URL);
}
public SAPSailingHeaderWithAuthentication(String headerText) {
super(SAP_SAILING_APP_NAME, SAP_SAILING_URL, headerText);
}
}
@@ -14,6 +14,7 @@ import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.regattaoverview.client.RegattaRaceStatesComponent.EntryHandler;
import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaOverviewBaseSettings;
@@ -21,7 +22,6 @@ import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaRaceStatesSett
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO;
import com.sap.sse.gwt.settings.SettingsToUrlSerializer;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
@@ -33,7 +33,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
private final Label clockLabel = new Label();
private final RegattaOverviewResources.LocalCss style = RegattaOverviewResources.INSTANCE.css();
private SAPHeaderWithAuthentication siteHeader;
private SAPSailingHeaderWithAuthentication siteHeader;
@Override
public void doOnModuleLoad() {
@@ -50,8 +50,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint {
clockLabel.addStyleName(style.clockLabel());
siteHeader = new SAPHeaderWithAuthentication(getStringMessages()
.sapSailingAnalytics());
siteHeader = new SAPSailingHeaderWithAuthentication();
new FixedSailingAuthentication(getUserService(), siteHeader.getAuthenticationMenuView());
siteHeader.addWidgetToRightSide(clockLabel);
@@ -41,6 +41,7 @@ public abstract class AbstractFileImportWidget extends Composite {
protected final SailingServiceAsync sailingService;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private boolean shouldClearListOnNewUploadComplete = true;
protected AbstractFileImportWidget(String formActionUrl, StringMessages stringMessages,
TrackFileImportDeviceIdentifierTableWrapper table, SailingServiceAsync sailingService,
@@ -73,7 +74,9 @@ public abstract class AbstractFileImportWidget extends Composite {
@UiHandler("formPanelUi")
void onFileImportComplete(SubmitCompleteEvent event) {
table.getDataProvider().getList().clear();
if (shouldClearListOnNewUploadComplete) {
table.getDataProvider().getList().clear();
}
String[] uuids = event.getResults().split("\\n");
if (uuids.length != 1 || (uuids.length > 0 && !uuids[0].isEmpty())) {
sailingService.getTrackFileImportDeviceIds(Arrays.asList(uuids),
@@ -106,4 +109,7 @@ public abstract class AbstractFileImportWidget extends Composite {
protected abstract void getImporterTypes(AsyncCallback<Collection<String>> callback);
protected void setShouldClearListOnNewUploadComplete(boolean shouldClearListOnNewUploadComplete) {
this.shouldClearListOnNewUploadComplete = shouldClearListOnNewUploadComplete;
}
}
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.Roles;
import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer;
import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher;
@@ -45,7 +46,6 @@ import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorat
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.component.UserManagementPanel;
import com.sap.sse.security.ui.client.i18n.StringMessages;
@@ -66,8 +66,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements
private void createUI() {
HeaderPanel headerPanel = new HeaderPanel();
SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages().sapSailingAnalytics(),
getStringMessages().administration());
SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().administration());
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
@@ -2,9 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CaptionPanel;
@@ -12,7 +10,6 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -31,12 +28,6 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
protected final CompetitorTableWrapper<RefreshableSingleSelectionModel<CompetitorDTO>> competitorTable;
private final StringMessages stringMessages;
private final Map<TrackFileImportDeviceIdentifierDTO, CompetitorDTO> mappings = new HashMap<>();
private TrackFileImportDeviceIdentifierDTO deviceToSelect;
private CompetitorDTO compToSelect;
private boolean inInstableTransitionState = false;
public RegattaLogImportSensorDataAndAddMappingsDialog(SailingServiceAsync sailingService,
final ErrorReporter errorReporter, final StringMessages stringMessages, String leaderboardName,
DialogCallback<Collection<TypedDeviceMappingDTO>> callback) {
@@ -50,11 +41,16 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
}, true, callback);
this.stringMessages = stringMessages;
deviceIdTable = new TrackFileImportDeviceIdentifierTableWrapper(sailingService, stringMessages, errorReporter);
deviceIdTable.removeTrackNameColumn();
importWidget = new SensorDataImportWidget(deviceIdTable, stringMessages, sailingService, errorReporter);
deviceIdTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override
public void onSelectionChange(SelectionChangeEvent event) {
deviceSelectionChanged(deviceIdTable.getSelectionModel().getSelectedObject());
CompetitorDTO mappedComp = deviceIdTable.getMappedCompetitorForCurrentSelection();
if (mappedComp != null) {
competitorTable.getSelectionModel().setSelected(mappedComp, true);
}
}
});
competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */
@@ -63,7 +59,8 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@Override
public void onSelectionChange(SelectionChangeEvent event) {
mappedToSelectionChanged(competitorTable.getSelectionModel().getSelectedObject());
deviceIdTable.didSelectCompetitorForMapping(competitorTable.getSelectionModel().getSelectedObject());
validate();
}
});
@@ -87,50 +84,6 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
});
}
private static <T> void selectOrClear(SingleSelectionModel<T> selectionModel, T object) {
if (object == null) {
selectionModel.clear();
} else {
selectionModel.setSelected(object, true);
}
}
/**
* Avoid programmatic deselections that re-trigger the selection listeners and lead to a loop.
*/
private void select() {
if (inInstableTransitionState) {
if (deviceIdTable.getSelectionModel().getSelectedObject() == deviceToSelect
&& competitorTable.getSelectionModel().getSelectedObject() == compToSelect) {
inInstableTransitionState = false;
}
} else {
inInstableTransitionState = true;
selectOrClear(deviceIdTable.getSelectionModel(), deviceToSelect);
selectOrClear(competitorTable.getSelectionModel(), compToSelect);
}
}
private void mappedToSelectionChanged(CompetitorDTO mappedTo) {
if (!inInstableTransitionState) {
TrackFileImportDeviceIdentifierDTO device = deviceIdTable.getSelectionModel().getSelectedObject();
if (device != null) {
mappings.put(device, mappedTo);
}
compToSelect = mappedTo;
}
select();
validate();
}
private void deviceSelectionChanged(TrackFileImportDeviceIdentifierDTO deviceId) {
if (!inInstableTransitionState) {
deviceToSelect = deviceId;
compToSelect = deviceId != null ? mappings.get(deviceId) : null;
}
select();
}
@Override
protected Widget getAdditionalWidget() {
HorizontalPanel panel = new HorizontalPanel();
@@ -150,9 +103,9 @@ public class RegattaLogImportSensorDataAndAddMappingsDialog extends DataEntryDia
protected Collection<TypedDeviceMappingDTO> getResult() {
List<TypedDeviceMappingDTO> result = new ArrayList<>();
String dataType = importWidget.getSelectedImporterType();
for (TrackFileImportDeviceIdentifierDTO device : mappings.keySet()) {
for (TrackFileImportDeviceIdentifierDTO device : deviceIdTable.getMappings().keySet()) {
DeviceIdentifierDTO deviceIdDto = new DeviceIdentifierDTO("FILE", device.uuidAsString);
MappableToDevice mappedTo = mappings.get(device);
MappableToDevice mappedTo = deviceIdTable.getMappings().get(device);
result.add(new TypedDeviceMappingDTO(deviceIdDto, device.from, device.to, mappedTo, null, dataType));
}
return result;
@@ -14,6 +14,7 @@ public class SensorDataImportWidget extends AbstractFileImportWidget implements
final SailingServiceAsync sailingService, final ErrorReporter errorReporter) {
super("/sailingserver/sensordata/import", stringMessages, table, sailingService, errorReporter);
setMultipleFileUploadEnabled(true);
setShouldClearListOnNewUploadComplete(false);
}
@Override
@@ -0,0 +1,60 @@
package com.sap.sailing.gwt.ui.adminconsole;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.gwt.ui.client.StringMessages;
/**
* Can be used in {@link AbstractEventManagementPanel}s to enable some sort of time-delayed race simulation.
* Provides a checkbox (see {@link #isSimulate()}) that enables simulation and an offset in minutes (see
* {@link #getOffsetToStartTimeInMinutes()}).
*
* @author Axel Uhl (d043530)
*
*/
public class SimulationPanel extends VerticalPanel {
private static final String ZERO_AS_STRING = "0";
final CheckBox simulateWithStartTimeNowCheckBox;
final TextBox offsetToStartTimeOfSimulatedRaceTextBox;
public SimulationPanel(StringMessages stringMessages) {
offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
simulateWithStartTimeNowCheckBox.setWordWrap(false);
simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
}
});
final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
simulateWithOffsetPanel.add(offsetToStartLabel);
simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
add(simulateAsLiveRacePanel);
add(simulateWithOffsetPanel);
}
public boolean isSimulate() {
return simulateWithStartTimeNowCheckBox.getValue();
}
public String getOffsetToStartTimeInMinutes() {
return offsetToStartTimeOfSimulatedRaceTextBox.getValue();
}
}
@@ -23,7 +23,6 @@ import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HorizontalPanel;
@@ -85,7 +84,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
private FlushableCellTable<TracTracRaceRecordDTO> racesTable;
private static final String ZERO_AS_STRING = "0";
public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
RegattaRefresher regattaRefresher, StringMessages stringMessages) {
@@ -275,32 +273,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
correctWindCheckBox.setWordWrap(false);
correctWindCheckBox.setValue(Boolean.TRUE);
final TextBox offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
simulateWithStartTimeNowCheckBox.setWordWrap(false);
simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
}
});
final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
simulateWithOffsetPanel.add(offsetToStartLabel);
simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
final SimulationPanel simulationPanel = new SimulationPanel(stringMessages);
final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
ignoreTracTracMarkPassingsCheckbox.setWordWrap(false);
ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE);
@@ -308,8 +281,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
layoutTable.setWidget(++row, 0, trackSettingsLabel);
layoutTable.setWidget(row, 1, trackWindCheckBox);
layoutTable.setWidget(++row, 1, correctWindCheckBox);
layoutTable.setWidget(++row, 1, simulateAsLiveRacePanel);
layoutTable.setWidget(++row, 1, simulateWithOffsetPanel);
layoutTable.setWidget(++row, 1, simulationPanel);
layoutTable.setWidget(++row, 1, ignoreTracTracMarkPassingsCheckbox);
// Filter
@@ -413,8 +385,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
@Override
public void onClick(ClickEvent event) {
Duration offsetToStartTimeOfSimulatedRace = null;
if(simulateWithStartTimeNowCheckBox.getValue().booleanValue()) {
offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(offsetToStartTimeOfSimulatedRaceTextBox.getValue());
if (simulationPanel.isSimulate()) {
offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(simulationPanel.getOffsetToStartTimeInMinutes());
}
trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(),
offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue());
@@ -1,6 +1,18 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.dom.builder.shared.TableCellBuilder;
import com.google.gwt.dom.builder.shared.TableRowBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.AbstractCellTable;
import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
import com.google.gwt.user.cellview.client.DefaultCellTableBuilder;
import com.google.gwt.user.cellview.client.RowStyles;
import com.google.gwt.user.cellview.client.TextColumn;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
@@ -9,11 +21,13 @@ import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
public class TrackFileImportDeviceIdentifierTableWrapper extends TableWrapper<TrackFileImportDeviceIdentifierDTO,
RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,
ErrorReporter errorReporter) {
public class TrackFileImportDeviceIdentifierTableWrapper extends
TableWrapper<TrackFileImportDeviceIdentifierDTO, RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
private TextColumn<TrackFileImportDeviceIdentifierDTO> trackNameColumn;
private final Map<TrackFileImportDeviceIdentifierDTO, CompetitorDTO> mappings = new HashMap<>();
public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService,
StringMessages stringMessages, ErrorReporter errorReporter) {
super(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true,
new EntityIdentityComparator<TrackFileImportDeviceIdentifierDTO>() {
@Override
@@ -21,54 +35,49 @@ RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
TrackFileImportDeviceIdentifierDTO dto2) {
return dto1.uuidAsString.equals(dto2.uuidAsString);
}
@Override
public int hashCode(TrackFileImportDeviceIdentifierDTO t) {
return t.uuidAsString.hashCode();
}
});
TextColumn<TrackFileImportDeviceIdentifierDTO> uuidColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.uuidAsString;
}
};
TextColumn<TrackFileImportDeviceIdentifierDTO> fileNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.fileName;
}
};
TextColumn<TrackFileImportDeviceIdentifierDTO> trackNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
trackNameColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return object.trackName;
}
};
TextColumn<TrackFileImportDeviceIdentifierDTO> fromColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return DateAndTimeFormatterUtil.formatDateAndTime(object.from);
}
};
TextColumn<TrackFileImportDeviceIdentifierDTO> toColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return DateAndTimeFormatterUtil.formatDateAndTime(object.to);
}
};
TextColumn<TrackFileImportDeviceIdentifierDTO> numberOfFixesColumn = new TextColumn<TrackFileImportDeviceIdentifierDTO>() {
@Override
public String getValue(TrackFileImportDeviceIdentifierDTO object) {
return "" + object.numFixes;
}
};
table.setTableBuilder(new MappingTableBuilder(table));
table.addColumn(uuidColumn, "UUID");
table.addColumn(fileNameColumn, "Filename");
table.addColumn(trackNameColumn, "Trackname");
@@ -77,4 +86,87 @@ RefreshableSingleSelectionModel<TrackFileImportDeviceIdentifierDTO>> {
table.addColumn(numberOfFixesColumn, "# fixes");
}
public void removeTrackNameColumn() {
table.removeColumn(trackNameColumn);
}
class MappingTableBuilder extends DefaultCellTableBuilder<TrackFileImportDeviceIdentifierDTO> {
private final String evenRowStyle;
private final String oddRowStyle;
private final String cellStyle;
private final String evenCellStyle;
private final String oddCellStyle;
private final String firstColumnStyle;
private final AbstractCellTable<TrackFileImportDeviceIdentifierDTO> cellTable;
public MappingTableBuilder(AbstractCellTable<TrackFileImportDeviceIdentifierDTO> cellTable) {
super(cellTable);
this.cellTable = cellTable;
Style style = cellTable.getResources().style();
evenRowStyle = style.evenRow();
oddRowStyle = style.oddRow();
cellStyle = style.cell();
evenCellStyle = " " + style.evenRowCell();
oddCellStyle = " " + style.oddRowCell();
firstColumnStyle = " " + style.firstColumn();
}
@Override
public void buildRowImpl(TrackFileImportDeviceIdentifierDTO rowValue, int absRowIndex) {
super.buildRowImpl(rowValue, absRowIndex);
boolean isEven = absRowIndex % 2 == 0;
StringBuilder trClasses = new StringBuilder(isEven ? evenRowStyle : oddRowStyle);
RowStyles<TrackFileImportDeviceIdentifierDTO> rowStyles = cellTable.getRowStyles();
if (rowStyles != null) {
String extraRowStyles = rowStyles.getStyleNames(rowValue, absRowIndex);
if (extraRowStyles != null) {
trClasses.append(" ").append(extraRowStyles);
}
}
StringBuilder tdClasses = new StringBuilder(cellStyle);
tdClasses.append(isEven ? evenCellStyle : oddCellStyle);
TableRowBuilder tr = startRow();
tr.className(trClasses.toString());
tr.startTD().className(new StringBuilder(tdClasses).append(firstColumnStyle).toString())
.html(SafeHtmlUtils.fromSafeConstant(" ")).end();
TableCellBuilder td = tr.startTD();
td.className(tdClasses.toString());
td.colSpan(getColumns().size() - 1);
CompetitorDTO competitorDTO = mappings.get(rowValue);
if (competitorDTO == null) {
td.html(new SafeHtmlBuilder().appendEscaped("--").toSafeHtml());
} else {
td.html(new SafeHtmlBuilder().appendEscaped(competitorDTO.getName()).toSafeHtml());
}
td.end();
tr.end();
}
}
public CompetitorDTO getMappedCompetitorForCurrentSelection() {
TrackFileImportDeviceIdentifierDTO selectedObject = getSelectionModel().getSelectedObject();
if (selectedObject != null) {
return mappings.get(selectedObject);
}
return null;
}
public void didSelectCompetitorForMapping(CompetitorDTO competitor) {
TrackFileImportDeviceIdentifierDTO selectedObject = getSelectionModel().getSelectedObject();
if (selectedObject != null) {
if (competitor != null) {
mappings.put(selectedObject, competitor);
} else {
mappings.remove(selectedObject);
}
}
table.redraw();
}
public Map<TrackFileImportDeviceIdentifierDTO, CompetitorDTO> getMappings() {
return mappings;
}
}
@@ -491,12 +491,12 @@ mediaPanel=音声と動画
url=URL
mimeType=MIME タイプ
title=表題
subtitle=Subtitle
copyright=Copyright
widthInPx=Width (pixel)
heightInPx=Height (pixel)
tags=Tags
size=Size
subtitle=サブタイトル
copyright=著作権
widthInPx=幅 (ピクセル)
heightInPx=高さ (ピクセル)
tags=タグ
size=サイズ
duration=時間 (h:mm:ss.ms)
addMediaTrack=音声/動画追加
addYoutubeTrack=YouTube 追加
@@ -1132,6 +1132,7 @@ lastScoreCorrectionsTime=最終得点修正時刻
lastScoreCorrectionsComment=最終得点修正コメント
setTimeToNow=時刻を ''現在'' に設定
useStartTimeInference=スタートマーク通過からの推定スタート時刻を使用
controlTrackingFromStartAndFinishTimes=スタート時刻およびフィニッシュ時刻からの追跡の制御
numberOfQueueMessagesSent=送信済キューメッセージ数
totalSize=合計サイズ
averageMessageSize=メッセージごとの平均サイズ (バイト)
@@ -1363,6 +1364,7 @@ gotoDesktop=デスクトップバージョンにジャンプ
goBack=戻る
showAllX=全 {0} を表示
collapseX={0} を圧縮
numberOfGPSFixes=GPS フィックス
fastestSailor=最速
numberWindFixes=風フィックス
@@ -1611,8 +1613,11 @@ raceAnalysis=レース分析
startAnalysis=スタート分析
winningLanes=勝利レーン
maximumNumberOfDiscards=カットレースの最大数
notificationRegattaConfigurationUpdatedUsingByMarks="マーク別" コースデザイナーを使用して、有効なレガッタ設定が行われました。
warningOverrideRegattaConfigurationCourseDesignerToByMarks=有効なレガッタ設定がすでに登録されていますが、コースデザイナーの設定が適切でありません。\n"マーク別" へのコースデザイナー設定を上書きしますか。
allRacesHaveBeenDenoted=すべてのレースがレースログ追跡に表示されています。
chartSelectionHint=ヒント: リーダーボード (リーダーボードタブ内) を使用して、チャートに表示する競技者を選択することもできます。
chartSelectionHint=ヒント: リーダーボード (リーダーボードタブ内) を使用して、チャートに表示する競技者を選択することもできます。
chooseALanguage=言語を選択してください
solutions=ソリューション
blog=ブログ
@@ -1644,7 +1649,7 @@ eventRegattaLeaderboardLastScoreUpdate=前回スコア更新
eventRegattaRaceNotTracked=未追跡
footerJobs=採用
footerCopyright=© 2011-2016 SAP Sailing Analytics
footerLanguage=SAP Sailing -
footerLanguage=SAP Sailing -
footerLegal=利用規約
footerPrivacy=個人情報保護
footerFeedback=フィードバック
@@ -1683,17 +1688,17 @@ contentSapInSailing11=世界最大のレガッタである Kieler Woche (Kiel We
contentSapInSailing12=SAP 5O5 World Championships のタイトルスポンサー
sapSailingAnalyticsTitle=SAP Sailing Analytics
contentSapSailingAnalytics1=レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを簡略に分かりやすく説明することをお手伝いしています。
contentSapSailingAnalytics2=SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできる多様なフロントエンドでコンテンツを視覚化して、セーリングの世界に洞察力と透明性を提供します。
contentSapSailingAnalytics2=SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできるさまざまなフロントエンドでコンテンツを視覚化することによってセーリングの世界にインサイトと透明性を提供します。
sapSailingAnalyticsReadMore=続きを読む
sapSailingRaceManager=SAP Sailing Race Manager
contentSapSailingRaceManager=SAP Sailing Race Manager は、セーリングレガッタ運営中にレース組織者が手動で行う作業や連絡業務を自動化/簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営/管理業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。
sapSailingRaceManagerReadMore=詳細情報
sapSailInSight=SAP Sail InSight
contentSapSailInSight=With the SAP Sail InSight mobile app for iPhone, iPad and Android phones, sailboat racers can manage their regatta participation and performance anywhere and anytime. This app connects to the SAP Sailing Analytics solution and enables racers to sign in to an SAP Sail InSight-enabled regatta, track their location together with performance indicators like speed or distance sailed, and analyze their performance.
sapSailInSightReadMore=Learn More
contentSapSailInSight=iPhone、iPad、および Android フォン向け SAP Sail InSight モバイルアプリにより、ヨット競技者はいつでもどこでもレガッタ参加やパフォーマンスを管理することができます。このアプリは SAP Sailing Analytics ソリューションに接続され、競技者が SAP Sail InSight 対応のレガッタへの受付を行い、自分がどこにいるかを艇速や帆走距離などのパフォーマンス指標とともに追跡し、自らのパフォーマンスを分析することを可能にします。
sapSailInSightReadMore=詳細情報
sapSailingBuoyPinger=SAP Sailing Buoy Pinger
contentSapSailingBuoyPinger=With the SAP Sailing Buoy Pinger mobile app for Android phones, sailboat race managers can simplify the positioning of race marks and communication with the mark layers anywhere and anytime. This app connects to the SAP Sailing Analytics solution and allows regatta managers to track the geographical position of their race marks, enabling the use of the SAP Sail InSight mobile app by sailors participating in the regatta.
sapSailingBuoyPingerReadMore=Learn More
contentSapSailingBuoyPinger=Android フォン向け SAP Sailing Buoy Pinger モバイルアプリにより、ヨットレース運営者はいつでもどこでもレース用のマークの位置決めや、マーク設置担当者とのやり取りを簡略化することができます。このアプリは SAP Sailing Analytics ソリューションに接続され、レガッタ運営者がレース用のマークの地理的位置を追跡することを可能にし、レガッタに参加しているセーラーが SAP Sail InSight モバイルアプリを使用できるようにします。
sapSailingBuoyPingerReadMore=詳細情報
postRaceAnalytics=レース後分析
contentPostRaceAnalytics1=セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上に役立つ、セーラーがそのパフォーマンスを評価および分析する機会が提供されます。
contentPostRaceAnalytics2=SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタで成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。
@@ -1702,4 +1707,6 @@ i18nDeSolutionsTrainingText=STG Training Diary は、選手の体調、ならび
strategySimulator=戦略シミュレータ
contentStrategySimulator=適切な戦略を選択すれば、レース中に大きな競争上の優位性を得ることが可能です。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが容易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。
strategySimulatorReadMore=詳細情報
impressum=刻印
impressum=刻印
lastFix=最終フィックス
filterDeviceMappings=デバイスマッピングのフィルタリング
@@ -491,12 +491,12 @@ mediaPanel=Аудио и видео
url=URL-адрес
mimeType=Тип MIME
title=Название
subtitle=Subtitle
subtitle=Подзаголовок
copyright=Copyright
widthInPx=Width (pixel)
heightInPx=Height (pixel)
tags=Tags
size=Size
widthInPx=Ширина (в пикселях)
heightInPx=Высота (в пикселях)
tags=Теги
size=Размер
duration=Длительность (ч:мм:сс.мс)
addMediaTrack=Добавить аудио/видео
addYoutubeTrack=Добавить Youtube
@@ -1132,6 +1132,7 @@ lastScoreCorrectionsTime=Время последнего исправления
lastScoreCorrectionsComment=Комментарий последнего исправления оценки
setTimeToNow=Установить время "сейчас"
useStartTimeInference=Использовать вывод о времени старта из прохождений стартовой отметки
controlTrackingFromStartAndFinishTimes=Контролировать отслеживание от времени старта и финиша
numberOfQueueMessagesSent=Число отправленных сообщений очереди
totalSize=Общий размер
averageMessageSize=Средний размер сообщения в байтах
@@ -1612,8 +1613,11 @@ raceAnalysis=Анализ гонки
startAnalysis=Начать анализ
winningLanes=Выигрышные маршруты
maximumNumberOfDiscards=Максимальное число исключений
notificationRegattaConfigurationUpdatedUsingByMarks=Активная конфигурация регаты была настроена с использованием дизайнера курса "По отметкам".
warningOverrideRegattaConfigurationCourseDesignerToByMarks=Активная конфигурация регаты уже существует, но с неверно выбранным дизайнером курса.\nПереопределить выбор дизайнера курса на "По отметкам"?
allRacesHaveBeenDenoted=Отслеживание журнала всех гонок отменено.
chartSelectionHint=Совет: таблицу лидеров (на вкладке ТАБЛИЦА ЛИДЕРОВ) также можно использовать для выбора участников, отображаемых в диаграмме.
chartSelectionHint=Совет: таблицу лидеров (на вкладке ТАБЛИЦА ЛИДЕРОВ) также можно использовать для выбора участников, отображаемых в диаграмме.
chooseALanguage=Выбор языка
solutions=Решения
blog=Блог
@@ -1640,7 +1644,7 @@ countdownMinute=мин.
countdownSecond=сек.
liveCapitalLetter=Активно
watchNow=Смотреть сейчас
seriesHeaderUpcoming=Предстоит
seriesHeaderUpcoming=Скоро
eventRegattaLeaderboardLastScoreUpdate=Последнее обновление оценок
eventRegattaRaceNotTracked=Не отслеживается
footerJobs=Работа
@@ -1690,11 +1694,11 @@ sapSailingRaceManager=SAP Sailing Race Manager
contentSapSailingRaceManager=Приложение SAP Sailing Race Manager повышает уровень эффективности и контроля для организаторов гонки за счет автоматизации и упрощения выполняемых вручную задач и общения во время проведения парусных регат. Приложение Race Committee помогает упростить проведение событий и управление ими, освобождая для оргкомитета время на лучшую проработку деталей.
sapSailingRaceManagerReadMore=Узнать больше
sapSailInSight=SAP Sail InSight
contentSapSailInSight=With the SAP Sail InSight mobile app for iPhone, iPad and Android phones, sailboat racers can manage their regatta participation and performance anywhere and anytime. This app connects to the SAP Sailing Analytics solution and enables racers to sign in to an SAP Sail InSight-enabled regatta, track their location together with performance indicators like speed or distance sailed, and analyze their performance.
sapSailInSightReadMore=Learn More
contentSapSailInSight=С помощью мобильного приложения SAP Sail InSight для смартфонов iPhone, iPad и Android яхтсмены гонок могут управлять своим участием и эффективностью в регате откуда и когда угодно. Это приложение подключается к решению SAP Sailing Analytics и позволяет яхтсменам выполнять вход в регаты, использующие SAP Sail InSight, отслеживать свое местоположение вместе с индикаторами эффективности, такими как скорость или пройденное расстояние, а также анализировать эффективность.
sapSailInSightReadMore=Узнать больше
sapSailingBuoyPinger=SAP Sailing Buoy Pinger
contentSapSailingBuoyPinger=With the SAP Sailing Buoy Pinger mobile app for Android phones, sailboat race managers can simplify the positioning of race marks and communication with the mark layers anywhere and anytime. This app connects to the SAP Sailing Analytics solution and allows regatta managers to track the geographical position of their race marks, enabling the use of the SAP Sail InSight mobile app by sailors participating in the regatta.
sapSailingBuoyPingerReadMore=Learn More
contentSapSailingBuoyPinger=Мобильное приложение SAP Sailing Buoy Pinger для смартфонов Android позволяет руководителям парусных гонок упростить позиционирование отметок и общение с установщиками отметок откуда и когда угодно. Это приложение подключается к решению SAP Sailing Analytics и позволяет руководителям гонок отслеживать географическое местоположение гоночных отметок, обеспечивая использование мобильного приложения SAP Sail InSight для яхтсменов, участвующих в регате.
sapSailingBuoyPingerReadMore=Узнать больше
postRaceAnalytics=Аналитика после гонки
contentPostRaceAnalytics1=Доступ к данным, собранным на парусных событиях, дает мореходам возможность оценить и проанализировать свои показатели, помогая разрабатывать стратегии и повышать мастерство.
contentPostRaceAnalytics2=Решения SAP Business Analytics предоставляют мореходам, тренерам, болельщикам и СМИ инструменты для просмотра и анализа исторических данных по множеству регат и гонок. SAP HANA позволяет изучать паттерны эффективности, тенденции и профили мореходов простым, быстрым и эффективным образом для оптимизации своих показателей и разработки успешных стратегий на будущие регаты.
@@ -1704,4 +1708,5 @@ strategySimulator=Симулятор стратегии
contentStrategySimulator=Выбор правильной стратегии может обеспечить большое конкурентное преимущество во время гонки. Симулятор стратегии упрощает определение лучшей стратегии хождения под парусом для разных атмосферных условий и водных течений. Возможность мгновенного изменения ветра и течения в симуляции позволяет легко оценить надежность стратегии даже для неопределенных погодных условий.
strategySimulatorReadMore=Узнать больше
impressum=Печать
lastFix=Последний замер
filterDeviceMappings=Фильтровать сопоставления устройств
@@ -491,12 +491,12 @@ mediaPanel=音频和视频
url=URL
mimeType=MIME 类型
title=标题
subtitle=Subtitle
copyright=Copyright
widthInPx=Width (pixel)
heightInPx=Height (pixel)
tags=Tags
size=Size
subtitle=副标题
copyright=版权
widthInPx=宽度(像素)
heightInPx=高度(像素)
tags=标记
size=大小
duration=持续时间 (h:mm:ss.ms)
addMediaTrack=添加音频/视频
addYoutubeTrack=添加 Youtube
@@ -944,7 +944,7 @@ maximumRoundsForCourse=场地的最大轮次
resultsMailRecipient=结果邮件收件人
deviceConfiguration=设备配置
none=无
courseDesignerMode=场地设计师
courseDesignerMode=场地设计器
racingProcedure=比赛程序
courseNames=场地名称
generate=生成
@@ -1132,6 +1132,7 @@ lastScoreCorrectionsTime=上次得分更正时间
lastScoreCorrectionsComment=上次得分更正评论
setTimeToNow=将时间设为“现在”
useStartTimeInference=使用从开始绕标推断的开始时间
controlTrackingFromStartAndFinishTimes=从开始和结束时间控制跟踪
numberOfQueueMessagesSent=发送的队列消息的数量
totalSize=总大小
averageMessageSize=各消息的平均大小(字节)
@@ -1363,6 +1364,7 @@ gotoDesktop=进入桌面版
goBack=返回
showAllX=显示所有{0}
collapseX=折叠{0}
numberOfGPSFixes=GPS 修复
fastestSailor=最快
numberWindFixes=风力修复
@@ -1511,7 +1513,7 @@ playstoreInsightApp=https://play.google.com/store/apps/details?id=com.sap.sailin
playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap.sailing.racecommittee.app
playStoreBuoyPingerApp=https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app
playstoreBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png
appstoreSapSailnsight=http://appstore.com/sapsailinsight
appstoreSapSailInsight=http://appstore.com/sapsailinsight
appstoreStgTrainingstagebuch=http://appstore.com/stgtrainingstagebuch
appstoreBadgeSuffix=_en
distanceFromAveragePosition=与平均位置的距离
@@ -1611,14 +1613,17 @@ raceAnalysis=比赛轮次分析
startAnalysis=起航分析
winningLanes=获胜的船道
maximumNumberOfDiscards=最大放弃次数
notificationRegattaConfigurationUpdatedUsingByMarks=已使用“按标志”场地设计器设置有效的比赛配置。
warningOverrideRegattaConfigurationCourseDesignerToByMarks=有效的比赛配置已存在,但是场地设计器设置错误。\n是否将场地设计器设置覆盖为“按标志”?
allRacesHaveBeenDenoted=已为比赛轮次日志跟踪描述所有比赛轮次。
chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。
chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。
chooseALanguage=选择一种语言
solutions=解决方案
blog=博客
officalEventWebsite=官方活动网站
moreInfo=更多信息
viewAnalysis=查看分析
viewAnalysis=查看分析
overallLeaderboard=总积分榜
pleaseTryAgain=请重试...
reloadPage=重新加载页面
@@ -1649,7 +1654,7 @@ footerLegal=法律声明
footerPrivacy=隐私
footerFeedback=反馈
headerLogo=Sailing Analytics
headerSearchPlaceholder=搜索
headerSearchPlaceholder=搜索
mainEventShowAll=显示所有活动
mainEventTitle=来自风帆世界的最新活动
mainMediaSubtitle=徜徉在让人着迷、震撼人心的最新航行活动中。
@@ -1686,20 +1691,22 @@ contentSapSailingAnalytics1=通过实时分析比赛轮次排名、速度、操
contentSapSailingAnalytics2=SAP Sailing Analytics 利用云和内存技术实时处理 GPS 和风力测量数据,并在可从任何地方访问的前端显示,让用户全面、透彻地了解航行运动。
sapSailingAnalyticsReadMore=更多信息
sapSailingRaceManager=SAP Sailing Race Manager
contentSapSailingRaceManager=SAP Sailing Race Manager 通过自动化及简化赛事主办机构在航行比赛期间的人工作业和沟通,实现更高的效率和控制能力。 Race Committee 应用简化举办和管理比赛活动的操作,使竞赛委员会的操作更加智能。
contentSapSailingRaceManager=SAP Sailing Race Manager 通过自动化及简化赛事主办机构在航行比赛期间的人工作业和沟通,实现更高的效率和控制能力。Race Committee 应用简化举办和管理比赛活动的操作,使竞赛委员会的操作更加智能。
sapSailingRaceManagerReadMore=了解更多
sapSailInSight=SAP Sail InSight
contentSapSailInSight=With the SAP Sail InSight mobile app for iPhone, iPad and Android phones, sailboat racers can manage their regatta participation and performance anywhere and anytime. This app connects to the SAP Sailing Analytics solution and enables racers to sign in to an SAP Sail InSight-enabled regatta, track their location together with performance indicators like speed or distance sailed, and analyze their performance.
sapSailInSightReadMore=Learn More
contentSapSailInSight=使用适用于 iPhone、iPad 和 Android 手机的 SAP Sail InSight 移动应用,帆船选手可以随时随地管理其参赛情况和表现。此应用与 SAP Sailing Analytics 解决方案相连,选手可以登录启用 SAP Sail InSight 的比赛,跟踪其所处地点和速度或航行距离等表现指标,并分析其表现。
sapSailInSightReadMore=了解更多
sapSailingBuoyPinger=SAP Sailing Buoy Pinger
contentSapSailingBuoyPinger=With the SAP Sailing Buoy Pinger mobile app for Android phones, sailboat race managers can simplify the positioning of race marks and communication with the mark layers anywhere and anytime. This app connects to the SAP Sailing Analytics solution and allows regatta managers to track the geographical position of their race marks, enabling the use of the SAP Sail InSight mobile app by sailors participating in the regatta.
sapSailingBuoyPingerReadMore=Learn More
contentSapSailingBuoyPinger=使用适用于 Android 手机的 SAP Sailing Buoy Pinger 移动应用,帆船赛赛事经理可以随时随地简化比赛航标的定位和与布标裁判的沟通。此应用与 SAP Sailing Analytics 解决方案相连,比赛经理可以跟踪其比赛航标的地理位置,并允许参赛水手使用 SAP Sail InSight 移动应用。
sapSailingBuoyPingerReadMore=了解更多
postRaceAnalytics=赛后分析
contentPostRaceAnalytics1=水手通过访问航行活动期间收集的数据评估和分析其表现,帮助他们制定策略和优化航行方案。
contentPostRaceAnalytics2=SAP Business Analytics 解决方案为水手、教练、粉丝和媒体提供跨比赛和轮次浏览和分析历史数据的工具。 SAP HANA 通过简单、快速和高效的方式使其了解表现模式、趋势和水手资料,从而提高自己的表现并为今后的比赛制定成功的策略。
contentPostRaceAnalytics2=SAP Business Analytics 解决方案为水手、教练、粉丝和媒体提供跨比赛和轮次浏览和分析历史数据的工具。SAP HANA 通过简单、快速和高效的方式使其了解表现模式、趋势和水手资料,从而提高自己的表现并为今后的比赛制定成功的策略。
i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Training Diary
i18nDeSolutionsTrainingText=STG Training Diary 可每天记录和分析运动员的身体状况,在水上训练、体能训练、船艇工作、心理、理论以及教育或旅行等其他活动领域的训练力度。
strategySimulator=策略模拟器
contentStrategySimulator=选择正确的策略可在比赛期间极大地提高竞争优势。 策略模拟器为不同的风力条件和水流简化确定最佳的航行策略。 由于可以在模拟中即时更改风和水流,即便在不确定的天气条件下,也可以轻松地评估策略的稳健性。
contentStrategySimulator=选择正确的策略可在比赛期间极大地提高竞争优势。策略模拟器为不同的风力条件和水流简化确定最佳的航行策略。由于可以在模拟中即时更改风和水流,即便在不确定的天气条件下,也可以轻松地评估策略的稳健性。
strategySimulatorReadMore=了解更多
impressum=版本说明
impressum=版本说明
lastFix=上次修复
filterDeviceMappings=筛选设备映射
@@ -1,71 +1,71 @@
package com.sap.sailing.gwt.ui.leaderboardedit;
import java.util.List;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class LeaderboardEditPage extends AbstractSailingEntryPoint {
@Override
protected void doOnModuleLoad() {
super.doOnModuleLoad();
sailingService.getLeaderboardNames(new MarkedAsyncCallback<List<String>>(
new AsyncCallback<List<String>>() {
@Override
public void onSuccess(List<String> leaderboardNames) {
final String leaderboardName = Window.Location.getParameter("name");
if (leaderboardNames.contains(leaderboardName)) {
SAPHeaderWithAuthentication header = initHeader();
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
authorizedContentDecorator.setPermissionToCheck(Permission.MANAGE_LEADERBOARD_RESULTS, SailingPermissionsForRoleProvider.INSTANCE);
authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
@Override
public Widget get() {
EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null,
LeaderboardEditPage.this, getStringMessages(), userAgent);
leaderboardPanel.ensureDebugId("EditableLeaderboardPanel");
return leaderboardPanel;
}
});
RootPanel.get().add(authorizedContentDecorator);
RootPanel.get().add(header);
} else {
RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard()));
}
}
@Override
public void onFailure(Throwable t) {
reportError("Error trying to obtain list of leaderboard names: "+t.getMessage());
}
}));
}
private SAPHeaderWithAuthentication initHeader() {
SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages().sapSailingAnalytics(),
getStringMessages().editScores());
header.getElement().getStyle().setPosition(Position.FIXED);
header.getElement().getStyle().setTop(0, Unit.PX);
header.getElement().getStyle().setWidth(100, Unit.PCT);
return header;
}
}
package com.sap.sailing.gwt.ui.leaderboardedit;
import java.util.List;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class LeaderboardEditPage extends AbstractSailingEntryPoint {
@Override
protected void doOnModuleLoad() {
super.doOnModuleLoad();
sailingService.getLeaderboardNames(new MarkedAsyncCallback<List<String>>(
new AsyncCallback<List<String>>() {
@Override
public void onSuccess(List<String> leaderboardNames) {
final String leaderboardName = Window.Location.getParameter("name");
if (leaderboardNames.contains(leaderboardName)) {
SAPHeaderWithAuthentication header = initHeader();
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
authorizedContentDecorator.setPermissionToCheck(Permission.MANAGE_LEADERBOARD_RESULTS, SailingPermissionsForRoleProvider.INSTANCE);
authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() {
@Override
public Widget get() {
EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null,
LeaderboardEditPage.this, getStringMessages(), userAgent);
leaderboardPanel.ensureDebugId("EditableLeaderboardPanel");
return leaderboardPanel;
}
});
RootPanel.get().add(authorizedContentDecorator);
RootPanel.get().add(header);
} else {
RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard()));
}
}
@Override
public void onFailure(Throwable t) {
reportError("Error trying to obtain list of leaderboard names: "+t.getMessage());
}
}));
}
private SAPHeaderWithAuthentication initHeader() {
SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().editScores());
header.getElement().getStyle().setPosition(Position.FIXED);
header.getElement().getStyle().setTop(0, Unit.PX);
header.getElement().getStyle().setWidth(100, Unit.PCT);
return header;
}
}
@@ -2175,7 +2175,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
} else {
actualTimePoint = timePoint;
}
RaceDefinition race = trackedRace.getRace();
final RaceDefinition race = trackedRace.getRace();
int rank = 1;
final List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(actualTimePoint);
for (Competitor competitor : competitorsFromBestToWorst) {
@@ -10,10 +10,7 @@ import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
@@ -86,7 +83,7 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
import com.sap.sse.util.ThreadPoolUtil;
public class SimulatorServiceImpl extends RemoteServiceServlet implements SimulatorService {
@@ -107,12 +104,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
private SpeedWithBearing averageWind = null;
public SimulatorServiceImpl() {
final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/2, 3);
Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */THREAD_POOL_SIZE,
/* maximumPoolSize */THREAD_POOL_SIZE,
/* keepAliveTime */60, TimeUnit.SECONDS,
/* workQueue */new LinkedBlockingQueue<Runnable>(),
new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
final ScheduledExecutorService simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
// TODO: initialize smart-future-cache for simulation-results and add to simulation-service
simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, null);
}
@@ -10,6 +10,7 @@ import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
import com.sap.sailing.gwt.ui.client.SimulatorService;
@@ -21,7 +22,6 @@ import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorat
import com.sap.sse.security.ui.authentication.decorator.WidgetFactory;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
public class SimulatorEntryPoint extends AbstractSailingEntryPoint {
@@ -187,8 +187,7 @@ public class SimulatorEntryPoint extends AbstractSailingEntryPoint {
}
private void createSimulatorPanel() {
SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages()
.sapSailingAnalytics(), getStringMessages().strategySimulatorTitle());
SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(getStringMessages().strategySimulatorTitle());
GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication);
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:44:58Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:10Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="99b784fd6920a22f02e85034e5d2ef70" form="base64">H4sIAAAAAAAAAF2PTQrCQAyF93OKXEH32TiiFBSGunAdOrEObTPQaaHHd/6s4Cbv4yW8JMaPNOs3OTmgSRyU2a0jnqgbWCzUVksdaz+uk+AP1Y37RixvGAEylYwzLVRCIWExw3fN1TwubsMoEFU9ndiWpGdMBBmz2QTt5eUsy4I7QR66M4V15ika8TQZMBVVb67b/j74ALSew+fwAAAA</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:06:35Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:10Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="99b784fd6920a22f02e85034e5d2ef70" form="base64">H4sIAAAAAAAAAF2PTQrCQAyF93OKXEH32TiiFBSGunAdOrEObTPQaaHHd/6s4Cbv4yW8JMaPNOs3OTmgSRyU2a0jnqgbWCzUVksdaz+uk+AP1Y37RixvGAEylYwzLVRCIWExw3fN1TwubsMoEFU9ndiWpGdMBBmz2QTt5eUsy4I7QR66M4V15ika8TQZMBVVb67b/j74ALSew+fwAAAA</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:06:35Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:10Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="99b784fd6920a22f02e85034e5d2ef70" form="base64">H4sIAAAAAAAAAF2PTQrCQAyF93OKXEH32TiiFBSGunAdOrEObTPQaaHHd/6s4Cbv4yW8JMaPNOs3OTmgSRyU2a0jnqgbWCzUVksdaz+uk+AP1Y37RixvGAEylYwzLVRCIWExw3fN1TwubsMoEFU9ndiWpGdMBBmz2QTt5eUsy4I7QR66M4V15ika8TQZMBVVb67b/j74ALSew+fwAAAA</internal-file>
@@ -17,8 +17,12 @@ public class FlatGPSFixJsonSerializer implements JsonSerializer<GPSFix> {
result.put(FIELD_LON_DEG, object.getPosition().getLngDeg());
result.put(FIELD_LAT_DEG, object.getPosition().getLatDeg());
result.put(FIELD_TIME_MILLIS, object.getTimePoint().asMillis());
// TODO the default accuracy is 1.0; when fixes ever have an accuracy, report it here
result.put(FIELD_ACCURACY, 1.0);
// TODO Insert Accuracy if someday available from the server
// Buoy Pinger app will interpret no accuracy in JSON message as MarkPingInfo.NOT_SET_BY_USER (-1) and store that in DB
// For the user these values are displayed as R.string.unknown ("n/a");
// (CheckinManager in Buoy Pinger can accept messages with and without accuracy)
// result.put(FIELD_ACCURACY, XXXXXXXXX);
return result;
}
}
@@ -4,11 +4,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.fileupload.FileItem;
import org.junit.Test;
import com.sap.sailing.domain.common.tracking.GPSFix;
@@ -30,32 +34,33 @@ public class GPSFixImportTest {
@Test
public void testReReadingEOFFromBufferedInputStream() throws IOException {
InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
BufferedInputStreamWithPublicBuffer bis = new BufferedInputStreamWithPublicBuffer(in);
bis.mark(10*1024*1024); // the file has ~2MB, so 10MB is much more than twice the size, so should never be required
int size = 0;
while (bis.read() != -1) {
size++;
try (InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
BufferedInputStreamWithPublicBuffer bis = new BufferedInputStreamWithPublicBuffer(in)) {
bis.mark(10 * 1024 * 1024); // the file has ~2MB, so 10MB is much more than twice the size, so should never
// be required
int size = 0;
while (bis.read() != -1) {
size++;
}
bis.reset();
int secondSize = 0;
while (bis.read() != -1) {
secondSize++;
}
assertEquals(size, secondSize);
assertTrue(bis.getBuffer().length < 8 * 1024 * 1024);
bis.reset();
int thirdSize = 0;
while (bis.read() != -1) {
thirdSize++;
}
assertEquals(size, thirdSize);
assertTrue(bis.getBuffer().length < 8 * 1024 * 1024);
// Now try to read EOF multiple times
for (int i = 1; i < 1000; i++) {
assertEquals(-1, bis.read());
}
}
bis.reset();
int secondSize = 0;
while (bis.read() != -1) {
secondSize++;
}
assertEquals(size, secondSize);
assertTrue(bis.getBuffer().length < 8*1024*1024);
bis.reset();
int thirdSize = 0;
while (bis.read() != -1) {
thirdSize++;
}
assertEquals(size, thirdSize);
assertTrue(bis.getBuffer().length < 8*1024*1024);
// Now try to read EOF multiple times
for (int i=1; i<1000; i++) {
assertEquals(-1, bis.read());
}
bis.close();
}
@Test
@@ -73,8 +78,81 @@ public class GPSFixImportTest {
public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) {
}
};
InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
servlet.importFiles(Arrays.asList(new Pair<>("test.gpx", in)), new AlwaysFailingGPSFixImporter(-1));
FileItem fi = new FileItem() {
private static final long serialVersionUID = 1L;
@Override
public void write(File file) throws Exception {
}
@Override
public void setFormField(boolean state) {
}
@Override
public void setFieldName(String name) {
}
@Override
public boolean isInMemory() {
return false;
}
@Override
public boolean isFormField() {
return false;
}
@Override
public String getString(String encoding) throws UnsupportedEncodingException {
return null;
}
@Override
public String getString() {
return null;
}
@Override
public long getSize() {
return 0;
}
@Override
public OutputStream getOutputStream() throws IOException {
return null;
}
@Override
public String getName() {
return "Cardiff Race17 - COMPETITORS.gpx";
}
@Override
public InputStream getInputStream() throws IOException {
return getClass().getResourceAsStream("/" + getName());
}
@Override
public String getFieldName() {
return null;
}
@Override
public String getContentType() {
return null;
}
@Override
public byte[] get() {
return null;
}
@Override
public void delete() {
}
};
servlet.importFiles(Arrays.asList(new Pair<>("test.gpx", fi)), new AlwaysFailingGPSFixImporter(-1));
// getting to here without errors is good enough
}
}
@@ -2,7 +2,6 @@ package com.sap.sailing.server.gateway.trackfiles.impl;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -32,13 +31,12 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* Import servlet for sensor data files. Importers are located through the OSGi service registry and mathed against the
* name provided by the upload formular.
* Import servlet for sensor data files. Importers are located through the OSGi service registry and matched against the
* name provided by the upload form.
*/
public class SensorDataImportServlet extends AbstractFileUploadServlet {
private static final long serialVersionUID = 1120226743039934620L;
private static final Logger logger = Logger.getLogger(SensorDataImportServlet.class.getName());
private static final int READ_BUFFER_SIZE = 1024 * 1024 * 1024;
public void storeFixes(Iterable<DoubleVectorFix> fixes, DeviceIdentifier deviceIdentifier) {
try {
@@ -54,21 +52,16 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
*
* @throws IOException
*/
private Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, InputStream>> files)
private Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, FileItem>> files)
throws IOException {
final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>();
final Map<DeviceIdentifier, TimePoint> from = new HashMap<>();
final Map<DeviceIdentifier, TimePoint> to = new HashMap<>();
final Collection<DoubleVectorFixImporter> availableImporters = new LinkedHashSet<>();
availableImporters.addAll(getOSGiRegisteredImporters());
for (Pair<String, InputStream> file : files) {
for (Pair<String, FileItem> file : files) {
final String requestedImporterName = file.getA();
BufferedInputStream in = new BufferedInputStream(file.getB()) {
@Override
public void close() throws IOException {
// prevent importers from closing this stream
}
};
final FileItem fi = file.getB();
DoubleVectorFixImporter importerToUse = null;
for (DoubleVectorFixImporter candidate : availableImporters) {
if (candidate.getType().equals(requestedImporterName)) {
@@ -77,43 +70,37 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
}
}
if (importerToUse == null) {
in.close();
throw new RuntimeException("Sensor importer not found");
}
in.mark(READ_BUFFER_SIZE);
try {
in.reset();
} catch (IOException e1) {
logger.log(Level.SEVERE, "Could not reset stream", e1);
}
logger.log(Level.INFO,
"Going to import sensor data file with importer " + importerToUse.getClass().getSimpleName());
try {
importerToUse.importFixes(in, new DoubleVectorFixImporter.Callback() {
@Override
public void addFixes(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier device) {
deviceIds.add(device);
storeFixes(fixes, device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
for (DoubleVectorFix fix : fixes) {
if (earliestFixSoFarFromCurrentDevice == null
|| earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
from.put(device, earliestFixSoFarFromCurrentDevice);
}
if (latestFixSoFarFromCurrentDevice == null
|| latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
try (BufferedInputStream in = new BufferedInputStream(fi.getInputStream())) {
try {
importerToUse.importFixes(in, new DoubleVectorFixImporter.Callback() {
@Override
public void addFixes(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier device) {
deviceIds.add(device);
storeFixes(fixes, device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
for (DoubleVectorFix fix : fixes) {
if (earliestFixSoFarFromCurrentDevice == null
|| earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
from.put(device, earliestFixSoFarFromCurrentDevice);
}
if (latestFixSoFarFromCurrentDevice == null
|| latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
}
}
}
}
}, requestedImporterName);
logger.log(Level.INFO, "Successfully imported file " + requestedImporterName);
} catch (FormatNotSupportedException e) {
logger.log(Level.INFO, "Failed to import file " + requestedImporterName);
}, fi.getName(), requestedImporterName);
logger.log(Level.INFO, "Successfully imported file " + requestedImporterName);
} catch (FormatNotSupportedException e) {
logger.log(Level.INFO, "Failed to import file " + requestedImporterName);
}
}
}
return deviceIds;
@@ -125,7 +112,6 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
@Override
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp)
throws IOException {
List<Pair<String, InputStream>> files = new ArrayList<>();
String importerName = null;
searchForPrefferedImporter: for (FileItem fi : fileItems) {
if ("preferredImporter".equalsIgnoreCase(fi.getFieldName())) {
@@ -134,11 +120,12 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
}
}
if (importerName == null) {
importerName = "BRAVO";
throw new RuntimeException("Missing preferred importer");
}
List<Pair<String, FileItem>> files = new ArrayList<>();
for (FileItem fi : fileItems) {
if ("file".equalsIgnoreCase(fi.getFieldName())) {
files.add(new Pair<>(importerName, fi.getInputStream()));
files.add(new Pair<>(importerName, fi));
}
}
final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(files);
@@ -2,18 +2,17 @@ package com.sap.sailing.server.gateway.trackfiles.impl;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -42,7 +41,8 @@ import com.sap.sse.common.Util.Pair;
* The available importers are tried one by one in the following order, until the first one is found that does not fail
* with an {@link FormatNotSupportedException}:
* <ul>
* <li>If the type of a {@link #PREFERRED_IMPORTER preferred importer} is transmitted, this is the first that is used.</li>
* <li>If the type of a {@link #PREFERRED_IMPORTER preferred importer} is transmitted, this is the first that is used.
* </li>
* <li>Then the importers registered for a matching {@link GPSFixImporter#FILE_EXTENSION_PROPERTY file extension} are
* used.</li>
* <li>If all this fails, all other available importers are used.</li>
@@ -55,8 +55,6 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
public static final String PREFERRED_IMPORTER = "preferredImporter";
private static final long serialVersionUID = 1120226743039934620L;
private static final Logger logger = Logger.getLogger(TrackFilesImportServlet.class.getName());
private static final int READ_BUFFER_SIZE = 1024 * 1024 * 1024;
public void storeFix(GPSFix fix, DeviceIdentifier deviceIdentifier) {
try {
@@ -84,69 +82,67 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
}
return result;
}
Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, InputStream>> files, GPSFixImporter preferredImporter)
throws IOException {
protected Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, FileItem>> files,
GPSFixImporter preferredImporter) throws IOException {
final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>();
final Map<DeviceIdentifier, TimePoint> from = new HashMap<>();
final Map<DeviceIdentifier, TimePoint> to = new HashMap<>();
for (Pair<String, InputStream> file : files) {
final String fileName = file.getA();
for (Pair<String, FileItem> pair : files) {
final String fileName = pair.getA();
final FileItem fileItem = pair.getB();
String fileExt = null;
if (fileName.contains(".")) {
fileExt = fileName.substring(fileName.lastIndexOf(".") + 1);
}
Collection<GPSFixImporter> importersToTry = new LinkedHashSet<>();
Set<GPSFixImporter> importersToTry = new LinkedHashSet<>();
if (preferredImporter != null) {
importersToTry.add(preferredImporter);
}
importersToTry.addAll(getGPSFixImporters(fileExt));
importersToTry.addAll(getGPSFixImporters(null));
BufferedInputStream in = new BufferedInputStream(file.getB()) {
@Override
public void close() throws IOException {
//prevent importers from closing this stream
}
};
in.mark(READ_BUFFER_SIZE);
boolean done = false;
Iterator<GPSFixImporter> iter = importersToTry.iterator();
while (iter.hasNext() && ! done) {
try {
in.reset();
} catch (IOException e1) {
e1.printStackTrace();
}
boolean failed = false;
GPSFixImporter importer = iter.next();
logger.log(Level.INFO, "Trying to import file " + fileName + " with importer " + importer.getType());
try {
importer.importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
deviceIds.add(device);
storeFix(fix, device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
if (earliestFixSoFarFromCurrentDevice == null || earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
from.put(device, earliestFixSoFarFromCurrentDevice);
}
TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
if (latestFixSoFarFromCurrentDevice == null || latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
}
}
}, true, fileName);
} catch (FormatNotSupportedException e) {
failed = true;
logger.log(Level.INFO,
"System knows " +
importersToTry.size() +
" importers: "+
importersToTry.stream().map(i -> i.getType()).collect(Collectors.joining(", "))
);
parsersLoop: for (GPSFixImporter importer : importersToTry) {
boolean succeeded = false;
logger.log(Level.INFO, "Trying to import file " + fileName + " with importer " + importer.getType());
try (BufferedInputStream in = new BufferedInputStream(fileItem.getInputStream())) {
try {
importer.importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
deviceIds.add(device);
storeFix(fix, device);
TimePoint earliestFixSoFarFromCurrentDevice = from.get(device);
if (earliestFixSoFarFromCurrentDevice == null
|| earliestFixSoFarFromCurrentDevice.after(fix.getTimePoint())) {
earliestFixSoFarFromCurrentDevice = fix.getTimePoint();
from.put(device, earliestFixSoFarFromCurrentDevice);
}
TimePoint latestFixSoFarFromCurrentDevice = to.get(device);
if (latestFixSoFarFromCurrentDevice == null
|| latestFixSoFarFromCurrentDevice.before(fix.getTimePoint())) {
latestFixSoFarFromCurrentDevice = fix.getTimePoint();
to.put(device, latestFixSoFarFromCurrentDevice);
}
}
}, true, fileName);
succeeded = true;
} catch (Exception e) {
logger.log(Level.INFO, "Failed with " + e.getClass().getSimpleName()
+ " while importing file using " + importer.getType());
}
}
if (! failed) {
done = true;
logger.log(Level.INFO, "Successfully imported file " + fileName);
if (succeeded) {
logger.log(Level.INFO, "Successfully imported file " + fileName + " using " + importer.getType());
break parsersLoop;
}
}
}
@@ -156,10 +152,10 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
@Override
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException {
String prefImporterType = null;
List<Pair<String, InputStream>> files = new ArrayList<>();
List<Pair<String, FileItem>> files = new ArrayList<>();
for (FileItem item : fileItems) {
if (!item.isFormField())
files.add(new Pair<String, InputStream>(item.getName(), item.getInputStream()));
files.add(new Pair<String, FileItem>(item.getName(), item));
else {
if (item.getFieldName() != null && item.getFieldName().equals(PREFERRED_IMPORTER)) {
prefImporterType = item.getString();
@@ -167,18 +163,15 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet {
}
}
GPSFixImporter preferredImporter = null;
if (prefImporterType != null && ! prefImporterType.isEmpty()) {
preferredImporter = getServiceFinderFactory().createServiceFinder(GPSFixImporter.class).
findService(prefImporterType);
if (prefImporterType != null && !prefImporterType.isEmpty()) {
preferredImporter = getServiceFinderFactory().createServiceFinder(GPSFixImporter.class)
.findService(prefImporterType);
}
final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(files, preferredImporter);
//setJsonResponseHeader(resp);
//DO NOT set a JSON response header. This causes the browser to wrap the response in a
//<pre> tag when uploading from GWT, as this is an AJAX-request inside an iFrame.
// setJsonResponseHeader(resp);
// DO NOT set a JSON response header. This causes the browser to wrap the response in a
// <pre> tag when uploading from GWT, as this is an AJAX-request inside an iFrame.
resp.setContentType("text/html");
for (TrackFileImportDeviceIdentifier mapping : mappingList) {
String stringRep = mapping.getId().toString();
resp.getWriter().println(stringRep);
@@ -47,7 +47,9 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningConf
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
import com.sap.sailing.domain.base.BoatClass;
@@ -94,7 +96,9 @@ import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -108,11 +112,12 @@ import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.persistence.PersistenceFactory;
import com.sap.sailing.domain.persistence.media.MediaDBFactory;
import com.sap.sailing.domain.racelog.tracking.EmptySensorFixStore;
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.test.TrackBasedTest;
import com.sap.sailing.domain.test.TrackBasedTest;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
@@ -123,9 +128,10 @@ import com.sap.sailing.server.gateway.jaxrs.spi.MasterDataResource;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sailing.server.masterdata.DummyTrackedRace;
import com.sap.sailing.server.masterdata.MasterDataImporter;
import com.sap.sailing.server.testsupport.RacingEventServiceImplMock;
import com.sap.sailing.server.testsupport.RacingEventServiceImplMock;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -289,6 +295,18 @@ public class MasterDataImportTest {
GPSFix gpsFix = new GPSFixMovingImpl(new DegreePosition(54.333, 10.133), logTimePoint2,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90)));
sourceService.getSensorFixStore().storeFix(deviceIdentifier, gpsFix);
// Add sensor fix
DeviceIdentifier deviceIdentifier2 = new SmartphoneImeiIdentifier("b");
RegattaLogDeviceMappingEvent<Competitor> bravoMappingEvent = new RegattaLogDeviceCompetitorBravoMappingEventImpl(
logTimePoint4, author, competitor, deviceIdentifier2, logTimePoint,
logTimePoint3);
regatta.getRegattaLog().add(bravoMappingEvent);
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
double rideHeightValue = 1337.0;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeightValue;
DoubleVectorFixImpl doubleVectorFix = new DoubleVectorFixImpl(logTimePoint2, fixData);
sourceService.getSensorFixStore().storeFix(deviceIdentifier2, doubleVectorFix);
// Set score correction
double scoreCorrection = 12.0;
@@ -427,7 +445,13 @@ public class MasterDataImportTest {
Assert.assertEquals(competitor.getId(), registerEventOnTarget.getCompetitor().getId());
// Check for import of racelogtracking fix
Assert.assertTrue(destService.getSensorFixStore().getNumberOfFixes(deviceIdentifier) == 1);
SensorFixStore sensorFixStore = destService.getSensorFixStore();
Assert.assertEquals(1, sensorFixStore.getNumberOfFixes(deviceIdentifier));
verifyFix(gpsFix, sensorFixStore, logTimePoint, logTimePoint3, deviceIdentifier);
// Check for import of sensor fix
Assert.assertEquals(1, sensorFixStore.getNumberOfFixes(deviceIdentifier2));
verifyFix(doubleVectorFix, sensorFixStore, logTimePoint, logTimePoint3, deviceIdentifier2);
// Check for persisting of race log events:
RacingEventService dest2 = new RacingEventServiceImplMock(){};
@@ -446,6 +470,17 @@ public class MasterDataImportTest {
Assert.assertNotNull(registerEventOnTarget2);
Assert.assertEquals(registerEvent.getId(), registerEventOnTarget2.getId());
}
private void verifyFix(Timed expectedFix, SensorFixStore store, TimePoint start, TimePoint end, DeviceIdentifier deviceIdentifier) {
List<Timed> fixes = new ArrayList<>(1);
try {
store.loadFixes(fixes::add, deviceIdentifier, start, end, true);
assertEquals(1, fixes.size());
assertEquals(expectedFix, fixes.get(0));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void testMasterDataImportForScoreCorrections() throws MalformedURLException, IOException,
@@ -53,7 +53,7 @@ public class BravoDataImportTest {
callbackCallCount++;
sumRideHeight += new BravoFixImpl(fix).getRideHeight();
}
}, "source");
}, "filename", "source");
Assert.assertEquals(importData.expectedFixesCount, callbackCallCount);
Assert.assertEquals(importData.expectedAverageRideHeight,
callbackCallCount == 0 ? sumRideHeight : sumRideHeight / callbackCallCount, 0.00001);
@@ -13,46 +13,43 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.w3c.dom.Element;
import slash.navigation.base.BaseNavigationPosition;
import slash.navigation.gpx.binding11.WptType;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.server.trackfiles.impl.BaseRouteConverterGPSFixImporterImpl;
import com.sap.sailing.server.trackfiles.impl.RouteConverterGPSFixImporterImpl;
import slash.navigation.base.BaseNavigationPosition;
import slash.navigation.gpx.binding11.WptType;
public class RouteConverterGpx11ExtensionsTest {
private boolean extensionsFound = false;
@Test
public void areExtensionsRead() throws Exception {
String testFile = "/equine-extensions.gpx";
InputStream in = getClass().getResourceAsStream(testFile);
assert in != null : new Exception("Input file not found");
BaseRouteConverterGPSFixImporterImpl importer = spy(new RouteConverterGPSFixImporterImpl());
doAnswer(new Answer<GPSFix>() {
@Override
public GPSFix answer(InvocationOnMock invocation) throws Throwable {
BaseNavigationPosition p = (BaseNavigationPosition) invocation.getArguments()[0];
WptType waypoint = p.asGpxPosition().getOrigin(WptType.class);
final List<Object> extensions = waypoint.getExtensions().getAny();
for (Object o : extensions) {
Element el = (Element) o;
System.out.println(el.getLocalName() + " " + el.getTextContent());
extensionsFound = true;
try (InputStream in = getClass().getResourceAsStream(testFile)) {
assert in != null : new Exception("Input file not found");
BaseRouteConverterGPSFixImporterImpl importer = spy(new RouteConverterGPSFixImporterImpl());
doAnswer(new Answer<GPSFix>() {
@Override
public GPSFix answer(InvocationOnMock invocation) throws Throwable {
BaseNavigationPosition p = (BaseNavigationPosition) invocation.getArguments()[0];
WptType waypoint = p.asGpxPosition().getOrigin(WptType.class);
final List<Object> extensions = waypoint.getExtensions().getAny();
for (Object o : extensions) {
Element el = (Element) o;
System.out.println(el.getLocalName() + " " + el.getTextContent());
extensionsFound = true;
}
return (GPSFix) invocation.callRealMethod();
}
return (GPSFix) invocation.callRealMethod();
}
}).when(importer).convertToGPSFix(any(BaseNavigationPosition.class));
importer.importFixes(in, new com.sap.sailing.domain.trackimport.GPSFixImporter.Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
}
}, false, "source");
assertTrue(extensionsFound);
}).when(importer).convertToGPSFix(any(BaseNavigationPosition.class));
importer.importFixes(in, new com.sap.sailing.domain.trackimport.GPSFixImporter.Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
}
}, false, "source");
assertTrue(extensionsFound);
}
}
}
@@ -17,37 +17,39 @@ import com.sap.sailing.server.trackfiles.impl.RouteConverterGPSFixImporterImpl;
public class TrackFileImportTest {
private boolean callbackCalled = false;
@Before
public void setup() {
callbackCalled = false;
}
@Test
public void testGpx() throws IOException, FormatNotSupportedException {
InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx");
new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
if (fix instanceof GPSFixMoving) {
callbackCalled = true;
try (InputStream in = getClass().getResourceAsStream("/Cardiff Race17 - COMPETITORS.gpx")) {
new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
if (fix instanceof GPSFixMoving) {
callbackCalled = true;
}
}
}
}, false, "source");
assertTrue(callbackCalled);
}, false, "source");
assertTrue(callbackCalled);
}
}
@Test
public void testKmlOnlyLatLong() throws IOException, FormatNotSupportedException {
InputStream in = getClass().getResourceAsStream("/Cardiff Race22 - COMPETITORS.kml");
new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
if (fix instanceof GPSFix) {
callbackCalled = true;
try (InputStream in = getClass().getResourceAsStream("/Cardiff Race22 - COMPETITORS.kml")) {
new RouteConverterGPSFixImporterImpl().importFixes(in, new Callback() {
@Override
public void addFix(GPSFix fix, TrackFileImportDeviceIdentifier device) {
if (fix instanceof GPSFix) {
callbackCalled = true;
}
}
}
}, false, "source");
assertTrue(callbackCalled);
}, false, "source");
assertTrue(callbackCalled);
}
}
}
@@ -26,7 +26,6 @@ import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
public class TrackedRacesExportTest extends OnlineTracTracBasedTest {
public TrackedRacesExportTest() throws MalformedURLException, URISyntaxException {
super();
}
@@ -37,35 +36,34 @@ public class TrackedRacesExportTest extends OnlineTracTracBasedTest {
}
@Before
public void setUp() throws URISyntaxException, IOException, InterruptedException, SubscriberInitializationException, CreateModelException {
URI storedUri = new URI("file:///"+new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.mtb").getCanonicalPath().replace('\\', '/'));
super.setUp(new URL("file:///"+new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.txt").getCanonicalPath()),
public void setUp() throws URISyntaxException, IOException, InterruptedException, SubscriberInitializationException,
CreateModelException {
URI storedUri = new URI("file:///" + new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.mtb")
.getCanonicalPath().replace('\\', '/'));
super.setUp(
new URL("file:///"
+ new File("resources/event_20120905_erEuropean-Gold_fleet_-_race_1.txt").getCanonicalPath()),
/* liveUri */ null, /* storedUri */ storedUri,
new ReceiverType[] { ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS, ReceiverType.MARKPASSINGS });
}
private byte[] getBytes(TrackFilesDataSource data, TrackFilesFormat format, TrackedRace race,
boolean dataBeforeAfter, boolean rawFixes) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(out);
TrackFileExporterImpl.INSTANCE.writeAllData(Collections.singletonList(data), format, Collections.singletonList(race),
true, true, zip);
zip.flush();
byte[] result = out.toByteArray();
out.close();
return result;
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ZipOutputStream zip = new ZipOutputStream(out);
TrackFileExporterImpl.INSTANCE.writeAllData(Collections.singletonList(data), format,
Collections.singletonList(race), true, true, zip);
zip.flush();
byte[] result = out.toByteArray();
return result;
}
}
@Test
public void doAllFormatsWork() throws IOException, FormatNotSupportedException {
TrackedRace race = getTrackedRace();
for (TrackFilesFormat format : TrackFilesFormat.values()) {
byte[] data = getBytes(TrackFilesDataSource.COMPETITORS, format, race, true, true);
assertTrue(data.length > 4000);
}
}
@@ -73,10 +71,8 @@ public class TrackedRacesExportTest extends OnlineTracTracBasedTest {
@Test
public void doAllDataSourcesWork() throws IOException, FormatNotSupportedException {
TrackedRace race = getTrackedRace();
for (TrackFilesDataSource source : TrackFilesDataSource.values()) {
byte[] data = getBytes(source, TrackFilesFormat.Gpx11, race, true, true);
assertTrue(data.length > 40);
}
}
@@ -46,15 +46,15 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
private final String BOF = "jjlDATE\tjjlTIME\tEpoch";
private static final int BATCH_SIZE = 5000;
public void importFixes(InputStream inputStream, Callback callback, String sourceName)
public void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName)
throws FormatNotSupportedException, IOException {
final TrackFileImportDeviceIdentifier trackIdentifier = new TrackFileImportDeviceIdentifierImpl(
UUID.randomUUID(), sourceName, sourceName + "_Imu", MillisecondsTimePoint.now());
UUID.randomUUID(), filename, sourceName, MillisecondsTimePoint.now());
try {
LOG.fine("Import CSV from " + sourceName);
LOG.fine("Import CSV from " + filename);
final InputStreamReader isr;
if (sourceName.endsWith("gz")) {
LOG.fine("Using gzip stream reader " + sourceName);
LOG.fine("Using gzip stream reader " + filename);
isr = new InputStreamReader(new GZIPInputStream(inputStream));
} else {
isr = new InputStreamReader(inputStream);
@@ -63,10 +63,10 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
try (BufferedReader buffer = new BufferedReader(isr)) {
String headerLine = null;
headerSearch: while (headerLine == null) {
LOG.fine("Searching for header in imu file");
LOG.fine("Searching for header in bravo file");
String headerCandidate = buffer.readLine();
if (headerCandidate == null) {
throw new RuntimeException("Missing required header in file " + sourceName);
throw new RuntimeException("Missing required header in file " + filename);
}
if (headerCandidate.startsWith(BOF)) {
LOG.fine("Found header");
@@ -91,7 +91,7 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
buffer.close();
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Exception parsing CSV file " + sourceName, e);
LOG.log(Level.SEVERE, "Exception parsing bravo CSV file " + filename, e);
}
}
@@ -125,7 +125,7 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
// long r16TP = ZonedDateTime.of(LocalDateTime.of(2016, 3, 19, 11, 55), ZoneId.of("Europe/Berlin"))
// .toEpochSecond() * 1000;
// fixTp = fixTp.plus(r16TP - 1461828459589l);
double[] fixData = new double[metadata.getColumns().size()];
double[] fixData = new double[metadata.columnCount];
for (int columnIndexInFix = 0; columnIndexInFix < fixData.length; columnIndexInFix++) {
String columnName = metadata.getColumns().get(columnIndexInFix);
Integer columnIndexInFile = colIndices.get(columnName);
@@ -6,7 +6,6 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -18,7 +17,7 @@ import com.sap.sailing.domain.common.trackfiles.TrackFilesFormat;
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.trackfiles.TrackFileExporter;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
import com.sap.sse.util.ThreadPoolUtil;
/**
* Export data to well-known formats such as GPX, KML etc. Internally, the RouteConverter Library is used.
@@ -56,7 +55,7 @@ public class TrackFileExporterImpl implements TrackFileExporter {
};
List<WriteRaceDataCallable> callables = new ArrayList<>();
ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
List<String> errors = new ArrayList<>();
for (TrackedRace race : races) {
for (TrackFilesDataSource d : data) {
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:45:20Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:47Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="c8a799f35957b659646c59bd67421d2f" form="base64">H4sIAAAAAAAAAKVUTW/bMAy9+1cQ2CEbkAVpdwvgQ5YuWIChDZr2sKPs0A1XWTIk2VkQ5L+PkvNhtzZarBfbEh/f4yNlGZHiQt3jk3BO3GU/KlQuNrwJ+/EBSIGpQzDYXx0GoDNAD+Hl9WEQmZA9l4iun+Tz/tvhy5tUEsUaTaKFWZ8YGlsMGrdzmCJqJq02enuPtpTO/iL1/EBOYuw3WTbshmLtXYWmIty+xPgY6GMwQC+svOiAX0AeMFVC7iy90vZNEMdYFDHczaSw1qfMSZHd4HpVJn8wdbHfg0wb8ChIPSz0byMsZEcsfAWFZ0sgKkFSJBKjTzCewJn+VuQ4hKtJ0L9BmxoqHGnVXcB3vd7Ftw3ajiJ4fNxyEAYvoqOGoVWqDc60MeyEhU6emqyt3A6NPg+NIXdaeSH9ATepzgt05LRZMhJt3aCTl9k5GjgKD1kfJwOSFHpaUZ+NYOVC1zuPbsVg4V1y7KJV93+NIW0p9dbeM4de8c5BtLXePFaPBSeQevLH9WRmWv9VHWPdkpRgnTAOrObS3vdTDOF6Ao5y7JYNNuavxXzdDUFRFEb/pVw4lDtgJN9q7MSWyVloobhmBvBn/FuXwTX3CqliKVDaUUZpCHOvjM5hNV3Cirviw+GCcZTaETxocKVRfBtm7awhFDwki1CRJQc7XRooLRrg0jLiaXNZboP+9DxDglJvR5GPL+tw/Oixx0VEeWGIb+FF/Y74WYl0Fy/rd2SFLF1t5qeQUvumDKN/18D20lAGAAA=</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:07:03Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:47Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="c8a799f35957b659646c59bd67421d2f" form="base64">H4sIAAAAAAAAAKVUTW/bMAy9+1cQ2CEbkAVpdwvgQ5YuWIChDZr2sKPs0A1XWTIk2VkQ5L+PkvNhtzZarBfbEh/f4yNlGZHiQt3jk3BO3GU/KlQuNrwJ+/EBSIGpQzDYXx0GoDNAD+Hl9WEQmZA9l4iun+Tz/tvhy5tUEsUaTaKFWZ8YGlsMGrdzmCJqJq02enuPtpTO/iL1/EBOYuw3WTbshmLtXYWmIty+xPgY6GMwQC+svOiAX0AeMFVC7iy90vZNEMdYFDHczaSw1qfMSZHd4HpVJn8wdbHfg0wb8ChIPSz0byMsZEcsfAWFZ0sgKkFSJBKjTzCewJn+VuQ4hKtJ0L9BmxoqHGnVXcB3vd7Ftw3ajiJ4fNxyEAYvoqOGoVWqDc60MeyEhU6emqyt3A6NPg+NIXdaeSH9ATepzgt05LRZMhJt3aCTl9k5GjgKD1kfJwOSFHpaUZ+NYOVC1zuPbsVg4V1y7KJV93+NIW0p9dbeM4de8c5BtLXePFaPBSeQevLH9WRmWv9VHWPdkpRgnTAOrObS3vdTDOF6Ao5y7JYNNuavxXzdDUFRFEb/pVw4lDtgJN9q7MSWyVloobhmBvBn/FuXwTX3CqliKVDaUUZpCHOvjM5hNV3Cirviw+GCcZTaETxocKVRfBtm7awhFDwki1CRJQc7XRooLRrg0jLiaXNZboP+9DxDglJvR5GPL+tw/Oixx0VEeWGIb+FF/Y74WYl0Fy/rd2SFLF1t5qeQUvumDKN/18D20lAGAAA=</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:07:03Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:47Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sailing.server/resources/stringmessages/StringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="c8a799f35957b659646c59bd67421d2f" form="base64">H4sIAAAAAAAAAKVUTW/bMAy9+1cQ2CEbkAVpdwvgQ5YuWIChDZr2sKPs0A1XWTIk2VkQ5L+PkvNhtzZarBfbEh/f4yNlGZHiQt3jk3BO3GU/KlQuNrwJ+/EBSIGpQzDYXx0GoDNAD+Hl9WEQmZA9l4iun+Tz/tvhy5tUEsUaTaKFWZ8YGlsMGrdzmCJqJq02enuPtpTO/iL1/EBOYuw3WTbshmLtXYWmIty+xPgY6GMwQC+svOiAX0AeMFVC7iy90vZNEMdYFDHczaSw1qfMSZHd4HpVJn8wdbHfg0wb8ChIPSz0byMsZEcsfAWFZ0sgKkFSJBKjTzCewJn+VuQ4hKtJ0L9BmxoqHGnVXcB3vd7Ftw3ajiJ4fNxyEAYvoqOGoVWqDc60MeyEhU6emqyt3A6NPg+NIXdaeSH9ATepzgt05LRZMhJt3aCTl9k5GjgKD1kfJwOSFHpaUZ+NYOVC1zuPbsVg4V1y7KJV93+NIW0p9dbeM4de8c5BtLXePFaPBSeQevLH9WRmWv9VHWPdkpRgnTAOrObS3vdTDOF6Ao5y7JYNNuavxXzdDUFRFEb/pVw4lDtgJN9q7MSWyVloobhmBvBn/FuXwTX3CqliKVDaUUZpCHOvjM5hNV3Cirviw+GCcZTaETxocKVRfBtm7awhFDwki1CRJQc7XRooLRrg0jLiaXNZboP+9DxDglJvR5GPL+tw/Oixx0VEeWGIb+FF/Y74WYl0Fy/rd2SFLF1t5qeQUvumDKN/18D20lAGAAA=</internal-file>
@@ -45,7 +45,7 @@
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="boatClassRaceFinishedBody" resname="boatClassRaceFinishedBody">
<source>New results for boat class <ph id="1">{0}</ph> in <ph id="2">{1}</ph> are available.</source>
<target state="translated"><ph id="2">{1}</ph>中船只级别<ph id="1">{0}</ph>的新结果可用。</target>
<target state="signed-off"><ph id="2">{1}</ph>中船只级别<ph id="1">{0}</ph>的新结果可用。</target>
<note annotates="source" from="Developer">0: boatClassName, 1: raceDescription</note>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="boatClassScoreCorrectionSubject" resname="boatClassScoreCorrectionSubject">
@@ -54,7 +54,7 @@
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="boatClassScoreCorrectionBody" resname="boatClassScoreCorrectionBody">
<source>New results for boat class <ph id="1">{0}</ph> in <ph id="2">{1}</ph> are available.</source>
<target state="translated"><ph id="2">{1}</ph>中船只级别<ph id="1">{0}</ph>的新结果可用。</target>
<target state="signed-off"><ph id="2">{1}</ph>中船只级别<ph id="1">{0}</ph>的新结果可用。</target>
<note annotates="source" from="Developer">0: boatClassName, 1: leaderboardDescription</note>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="competitorPassesFinishSubject" resname="competitorPassesFinishSubject">
@@ -72,7 +72,7 @@
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="competitorScoreCorrectionBody" resname="competitorScoreCorrectionBody">
<source>New results for competitor <ph id="1">{0}</ph> in <ph id="2">{1}</ph> are available.</source>
<target state="translated"><ph id="2">{1}</ph>中参赛队<ph id="1">{0}</ph>的新结果可用。</target>
<target state="signed-off"><ph id="2">{1}</ph>中参赛队<ph id="1">{0}</ph>的新结果可用。</target>
<note annotates="source" from="Developer">0: competitorName, 1: leaderboardDescription</note>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="boatClassUpcomingRaceSubject" resname="boatClassUpcomingRaceSubject">
@@ -102,7 +102,7 @@
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="salutation" resname="salutation">
<source>Hallo <ph id="1">{0}</ph>,</source>
<target state="translated"><ph id="1">{0}</ph>您好!</target>
<target state="signed-off"><ph id="1">{0}</ph>您好!</target>
</trans-unit>
</body>
</file>
@@ -28,7 +28,6 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -605,7 +604,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
operationExecutionListeners = new ConcurrentHashMap<>();
courseListeners = new ConcurrentHashMap<>();
persistentRegattasForRaceIDs = new ConcurrentHashMap<>();
Executor simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor();
final ScheduledExecutorService simulatorExecutor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor();
// TODO: initialize smart-future-cache for simulation-results and add to simulation-service
simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this);
this.raceLogReplicator = new RaceLogReplicatorAndNotifier(this);
@@ -41,6 +41,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
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.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
@@ -213,7 +214,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
return null;
}
@@ -1,6 +1,6 @@
package com.sap.sailing.server.simulation;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import com.sap.sailing.server.RacingEventService;
@@ -8,6 +8,6 @@ public interface SimulationServiceFactory {
static SimulationServiceFactory INSTANCE = new SimulationServiceFactoryImpl();
SimulationService getService(Executor executor, RacingEventService racingEventService);
SimulationService getService(ScheduledExecutorService executor, RacingEventService racingEventService);
}
@@ -1,12 +1,12 @@
package com.sap.sailing.server.simulation;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import com.sap.sailing.server.RacingEventService;
public class SimulationServiceFactoryImpl implements SimulationServiceFactory {
public SimulationService getService(Executor executor, RacingEventService racingEventService) {
public SimulationService getService(ScheduledExecutorService executor, RacingEventService racingEventService) {
return new SimulationServiceImpl(executor, racingEventService);
}
@@ -8,9 +8,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -59,13 +57,12 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
public class SimulationServiceImpl implements SimulationService {
private static final Logger logger = Logger.getLogger(SimulationService.class.getName());
final private Executor executor;
final private ScheduledExecutorService executor;
final private SmartFutureCache<LegIdentifier, SimulationResults, SmartFutureCache.EmptyUpdateInterval> cache;
final private RacingEventService racingEventService;
final private ScheduledExecutorService scheduler;
@@ -73,9 +70,9 @@ public class SimulationServiceImpl implements SimulationService {
final private HashMap<RaceIdentifier, LegChangeListener> legListeners;
final private long WAIT_MILLIS = 20000; // milliseconds to wait until earliest cache-update for simulation
public SimulationServiceImpl(Executor executor, RacingEventService racingEventService) {
public SimulationServiceImpl(ScheduledExecutorService executor, RacingEventService racingEventService) {
this.executor = executor;
this.scheduler = Executors.newScheduledThreadPool(1, new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
this.scheduler = executor;
this.racingEventService = racingEventService;
if (racingEventService != null) {
this.raceListeners = new HashMap<String, SimulationRaceListener>();
@@ -437,31 +434,26 @@ public class SimulationServiceImpl implements SimulationService {
Simulator simulator = new SimulatorImpl(simulationParameters);
Map<PathType, Path> result = new HashMap<PathType, Path>();
FutureTask<Path> taskOmniscient = null;
Future<Path> taskOmniscient = null;
if (simulationParameters.showOmniscient()) {
// schedule omniscient task
taskOmniscient = new FutureTask<Path>(() -> simulator.getPath(PathType.OMNISCIENT));
executor.execute(taskOmniscient);
taskOmniscient = executor.submit(() -> simulator.getPath(PathType.OMNISCIENT));
}
FutureTask<Path> task1TurnerLeft = null;
FutureTask<Path> task1TurnerRight = null;
Future<Path> task1TurnerLeft = null;
Future<Path> task1TurnerRight = null;
if (simulationParameters.getLegType() != LegType.REACHING) {
// schedule 1-turner tasks
task1TurnerLeft = new FutureTask<Path>(() -> simulator.getPath(PathType.ONE_TURNER_LEFT));
task1TurnerRight = new FutureTask<Path>(() -> simulator.getPath(PathType.ONE_TURNER_RIGHT));
executor.execute(task1TurnerLeft);
executor.execute(task1TurnerRight);
task1TurnerLeft = executor.submit(() -> simulator.getPath(PathType.ONE_TURNER_LEFT));
task1TurnerRight = executor.submit(() -> simulator.getPath(PathType.ONE_TURNER_RIGHT));
}
FutureTask<Path> taskOpportunistLeft = null;
FutureTask<Path> taskOpportunistRight = null;
Future<Path> taskOpportunistLeft = null;
Future<Path> taskOpportunistRight = null;
if (simulationParameters.showOpportunist()) {
// schedule opportunist tasks (which depend on 1-turner results)
taskOpportunistLeft = new FutureTask<Path>(() -> simulator.getPath(PathType.OPPORTUNIST_LEFT));
taskOpportunistRight = new FutureTask<Path>(() -> simulator.getPath(PathType.OPPORTUNIST_RIGHT));
executor.execute(taskOpportunistLeft);
executor.execute(taskOpportunistRight);
taskOpportunistLeft = executor.submit(() -> simulator.getPath(PathType.OPPORTUNIST_LEFT));
taskOpportunistRight = executor.submit(() -> simulator.getPath(PathType.OPPORTUNIST_RIGHT));
}
Path path1TurnerLeft = null;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:45:09Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:26Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="299055c6be272a85a07b907e3c6ce40a" form="base64">H4sIAAAAAAAAAD2MQQoCMQxF9znFLHWnB8hicCXSjYMHKBpLsE2hTQdhmLsbpuoieT958MeZig+EYyc4erAX7ICpJbQB59+cLH8JjqXfnXDKTRS3bQ1KpWKCi2St+BKzMdJdWQL+Yha4Um1RJw7CT6aCy2Eddstx3cP/d5absG6GZTAHH6Y6WeOvAAAA</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:06:52Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:26Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="299055c6be272a85a07b907e3c6ce40a" form="base64">H4sIAAAAAAAAAD2MQQoCMQxF9znFLHWnB8hicCXSjYMHKBpLsE2hTQdhmLsbpuoieT958MeZig+EYyc4erAX7ICpJbQB59+cLH8JjqXfnXDKTRS3bQ1KpWKCi2St+BKzMdJdWQL+Yha4Um1RJw7CT6aCy2Eddstx3cP/d5absG6GZTAHH6Y6WeOvAAAA</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:06:52Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:16:26Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="299055c6be272a85a07b907e3c6ce40a" form="base64">H4sIAAAAAAAAAD2MQQoCMQxF9znFLHWnB8hicCXSjYMHKBpLsE2hTQdhmLsbpuoieT958MeZig+EYyc4erAX7ICpJbQB59+cLH8JjqXfnXDKTRS3bQ1KpWKCi2St+BKzMdJdWQL+Yha4Um1RJw7CT6aCy2Eddstx3cP/d5absG6GZTAHH6Y6WeOvAAAA</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:45:49Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:17:40Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="37afe0b1eb405886cd475fe305c64722" form="base64">H4sIAAAAAAAAAJ1SwW7bMAy95yt4bNHBPeTcQ4tiQLGiGNrmAxiZTojSkiHKSb3Dvn2klGTddSdL1OPj4/PT9SNpuHsrKRMMLKSQItyP+Ms+b+sO3vcEmXSWwnFXERBxJGAFhIyxTyNsNk+PMMmsUAydMu84ojQwxd4au5Wu70Mg1ae+DmwXsL4rITwQbAXjB5QEsxL8vu3wqLchU0+xMIoCRy2E/fWF6ActTToZrMCJ0Kr/wfgwhw8qL7ZXpfQDpAFa+UxxtUf1M0q2tgXok7V8A7PAnzNEol5B52HgwDYEJsojq3KKer2SFFBOgvOBg7mdMqj5fvbVFVUDK7S5p4tpHDv4Xt93JsY7qHdohf3Esr9xaNXsWgJG2Nqx+mFILLBFpU2Wv7iXVMhG2ZPPCykW1yuLSzHmEx7GWSsi0+DxuLDyVjwUo7HI4hGxMPhY34g+cZyEKnbPdDAF2wXGNMcaIDz1fV3vax8M2QKlBQuHTnFSZPH8BKumZk+mSTi0f3HxoGsGPzTl1Wc/w+b1GW7gsvhdLRx9JZgnSdibvirlyCL/btgYn88TKme9gV/PqXDtbRf7Mbij1R9TuhdHUgMAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:07:36Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:17:40Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="37afe0b1eb405886cd475fe305c64722" form="base64">H4sIAAAAAAAAAJ1SwW7bMAy95yt4bNHBPeTcQ4tiQLGiGNrmAxiZTojSkiHKSb3Dvn2klGTddSdL1OPj4/PT9SNpuHsrKRMMLKSQItyP+Ms+b+sO3vcEmXSWwnFXERBxJGAFhIyxTyNsNk+PMMmsUAydMu84ojQwxd4au5Wu70Mg1ae+DmwXsL4rITwQbAXjB5QEsxL8vu3wqLchU0+xMIoCRy2E/fWF6ActTToZrMCJ0Kr/wfgwhw8qL7ZXpfQDpAFa+UxxtUf1M0q2tgXok7V8A7PAnzNEol5B52HgwDYEJsojq3KKer2SFFBOgvOBg7mdMqj5fvbVFVUDK7S5p4tpHDv4Xt93JsY7qHdohf3Esr9xaNXsWgJG2Nqx+mFILLBFpU2Wv7iXVMhG2ZPPCykW1yuLSzHmEx7GWSsi0+DxuLDyVjwUo7HI4hGxMPhY34g+cZyEKnbPdDAF2wXGNMcaIDz1fV3vax8M2QKlBQuHTnFSZPH8BKumZk+mSTi0f3HxoGsGPzTl1Wc/w+b1GW7gsvhdLRx9JZgnSdibvirlyCL/btgYn88TKme9gV/PqXDtbRf7Mbij1R9TuhdHUgMAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:07:36Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:17:40Z" original="/usr/sap/ljs/app/Converter_5483050776164983/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="37afe0b1eb405886cd475fe305c64722" form="base64">H4sIAAAAAAAAAJ1SwW7bMAy95yt4bNHBPeTcQ4tiQLGiGNrmAxiZTojSkiHKSb3Dvn2klGTddSdL1OPj4/PT9SNpuHsrKRMMLKSQItyP+Ms+b+sO3vcEmXSWwnFXERBxJGAFhIyxTyNsNk+PMMmsUAydMu84ojQwxd4au5Wu70Mg1ae+DmwXsL4rITwQbAXjB5QEsxL8vu3wqLchU0+xMIoCRy2E/fWF6ActTToZrMCJ0Kr/wfgwhw8qL7ZXpfQDpAFa+UxxtUf1M0q2tgXok7V8A7PAnzNEol5B52HgwDYEJsojq3KKer2SFFBOgvOBg7mdMqj5fvbVFVUDK7S5p4tpHDv4Xt93JsY7qHdohf3Esr9xaNXsWgJG2Nqx+mFILLBFpU2Wv7iXVMhG2ZPPCykW1yuLSzHmEx7GWSsi0+DxuLDyVjwUo7HI4hGxMPhY34g+cZyEKnbPdDAF2wXGNMcaIDz1fV3vax8M2QKlBQuHTnFSZPH8BKumZk+mSTi0f3HxoGsGPzTl1Wc/w+b1GW7gsvhdLRx9JZgnSdibvirlyCL/btgYn88TKme9gV/PqXDtbRf7Mbij1R9TuhdHUgMAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:46:17Z" original="/usr/sap/ljs/app/Converter_2475291648609719345/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/StringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:18:20Z" original="/usr/sap/ljs/app/Converter_2475291648609719345/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/StringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="85e48bcf9e250e66161b1b03d11cf699" form="base64">H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:08:06Z" original="/usr/sap/ljs/app/Converter_2475291648609719345/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/StringMessages.properties" source-language="en" target-language="ru">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:18:20Z" original="/usr/sap/ljs/app/Converter_2475291648609719345/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/StringMessages.properties" source-language="en" target-language="ru">
<header>
<skl>
<internal-file crc="85e48bcf9e250e66161b1b03d11cf699" form="base64">H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA==</internal-file>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T11:08:06Z" original="/usr/sap/ljs/app/Converter_2475291648609719345/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/StringMessages.properties" source-language="en" target-language="zh-CN">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:18:20Z" original="/usr/sap/ljs/app/Converter_2475291648609719345/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/StringMessages.properties" source-language="en" target-language="zh-CN">
<header>
<skl>
<internal-file crc="85e48bcf9e250e66161b1b03d11cf699" form="base64">H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA==</internal-file>
@@ -1,73 +1,81 @@
.siteheader {
font-size: 1rem;
line-height: 1.333;
background: #333;
border-bottom: 4px solid #f0ab00;
height: 75px;
width: 100%;
overflow: hidden;
vertical-align: top;
position: relative;
}
.siteHeaderTitle {
color: #fff;
vertical-align: top;
font-size: 20px;
font-weight: 700;
}
.siteHeaderSubTitle {
color: #fff;
font-size: 18px;
vertical-align: top;
}
.siteheaderLeft {
position: relative; top:10px;left:10px;
display: inline-block;
text-align: left;
height: 50px;
vertical-align: top;
}
.siteheader2ndCol {
position: absolute; top:10px;left:235px;
display: inline-block;
text-align: left;
height:50px;
vertical-align: top;
}
.siteheaderRight {
position: absolute; top:10px;right:10px;
text-align: right;
display: inline-block;
height:50px;
vertical-align: top;
}
.logo {
margin: 0px;
display: inline-block;
}
.logotitle {
font-size: 1rem;
display: inline-block;
color: #f6f9fc;
width: 150px;
height: 100%;
padding: 17px 15px 0 0;
vertical-align: top;
text-align: right;
border-right: 1px solid rgb(180,180,180);
margin: 0px;
margin-left: -25px;
box-sizing: border-box;
}
.pagetitle {
color: #ffffff;
font-size: 1.1rem;
font-weight: bold;
}
.siteheader {
font-size: 1rem;
line-height: 1.333;
background: #333;
border-bottom: 4px solid #f0ab00;
height: 75px;
width: 100%;
overflow: hidden;
vertical-align: top;
position: relative;
}
.siteHeaderTitle {
color: #fff;
vertical-align: top;
font-size: 20px;
font-weight: 700;
}
.siteHeaderSubTitle {
color: #fff;
font-size: 18px;
vertical-align: top;
}
.siteheaderLeft {
position: relative; top:10px;left:10px;
display: inline-block;
text-align: left;
height: 50px;
vertical-align: top;
}
.siteheader2ndCol {
position: absolute; top:10px;left:235px;
display: inline-block;
text-align: left;
height:50px;
vertical-align: top;
}
.siteheaderRight {
position: absolute; top:10px;right:10px;
text-align: right;
display: inline-block;
height:50px;
vertical-align: top;
}
.logo, .logo:hover, .logo:visited {
margin: 0px;
display: inline-block;
gwt-sprite: "logo";
background-size: cover;
width: 80px;
height: 40px;
border: none;
text-decoration: none;
}
.logotitle, .logotitle:hover, .logotitle:visited {
font-size: 1rem;
display: inline-block;
color: #fff;
width: 150px;
height: 100%;
padding: 17px 15px 0 0;
vertical-align: top;
text-align: right;
border: none;
border-right: 1px solid rgb(180,180,180);
margin: 0px;
margin-left: -25px;
box-sizing: border-box;
text-decoration: none;
}
.pagetitle {
color: #ffffff;
font-size: 1.1rem;
font-weight: bold;
}
@@ -1,65 +1,75 @@
package com.sap.sse.gwt.client.sapheader;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
/**
* Generic header widget that is designed with a SAP logo and SAP color scheme.
*
* The header has the following elements:
* <ul>
* <li>SAP Logo with an application title (like "Sailing Analytics")</li>
* <li>Page title</li>
* <li>Optional Subtitle</li>
* <li>Optional widget to show on the right side of the header</li>
* </ul>
*/
public class SAPHeader extends Composite {
private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class);
interface SAPHeaderUiBinder extends UiBinder<Widget, SAPHeader> {
}
@UiField
DivElement applicationNameUi;
@UiField
DivElement pageTitleUi;
@UiField
DivElement titleUi;
@UiField
DivElement subTitleUi;
@UiField
SimplePanel rightSideUi;
public SAPHeader(String applicationName) {
SAPHeaderResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
applicationNameUi.setInnerText(applicationName != null ? applicationName : "&nbsp;");
}
public void setHeaderTitle(String title) {
titleUi.setInnerText(title);
}
public void setHeaderSubTitle(String subtitle) {
if (subtitle == null || subtitle.isEmpty()) {
subTitleUi.getStyle().setDisplay(Display.NONE);
titleUi.getStyle().setMarginTop(14, Unit.PX);
} else {
subTitleUi.setInnerText(subtitle);
subTitleUi.getStyle().clearDisplay();
titleUi.getStyle().clearMarginTop();
}
}
public void addWidgetToRightSide(Widget widget) {
rightSideUi.add(widget);
}
}
package com.sap.sse.gwt.client.sapheader;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
/**
* Generic header widget that is designed with a SAP logo and SAP color scheme.
*
* The header has the following elements:
* <ul>
* <li>SAP Logo with an application title (like "Sailing Analytics")</li>
* <li>Page title</li>
* <li>Optional Subtitle</li>
* <li>Optional widget to show on the right side of the header</li>
* </ul>
*/
public class SAPHeader extends Composite {
private static final String LOGO_URL = "https://sapsponsorships.com/";
private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class);
interface SAPHeaderUiBinder extends UiBinder<Widget, SAPHeader> {
}
@UiField
AnchorElement applicationNameAnchor;
@UiField
DivElement pageTitleUi;
@UiField
DivElement titleUi;
@UiField
DivElement subTitleUi;
@UiField
SimplePanel rightSideUi;
@UiField
AnchorElement logoAnchor;
public SAPHeader(String applicationName, String applicationBaseUrl) {
SAPHeaderResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
applicationNameAnchor.setInnerText(applicationName != null ? applicationName : "&nbsp;");
logoAnchor.setHref(LOGO_URL);
String sapSailingUrl = applicationBaseUrl + "?locale=" + LocaleInfo.getCurrentLocale().getLocaleName();
applicationNameAnchor.setHref(sapSailingUrl);
}
public void setHeaderTitle(String title) {
titleUi.setInnerText(title);
}
public void setHeaderSubTitle(String subtitle) {
if (subtitle == null || subtitle.isEmpty()) {
subTitleUi.getStyle().setDisplay(Display.NONE);
titleUi.getStyle().setMarginTop(14, Unit.PX);
} else {
subTitleUi.setInnerText(subtitle);
subTitleUi.getStyle().clearDisplay();
titleUi.getStyle().clearMarginTop();
}
}
public void addWidgetToRightSide(Widget widget) {
rightSideUi.add(widget);
}
}
@@ -1,24 +1,20 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="local_res"
type="com.sap.sse.gwt.client.sapheader.SAPHeaderResources" />
<g:HTMLPanel>
<header class="{local_res.css.siteheader}">
<div class="{local_res.css.siteheaderLeft}">
<g:Image resource="{local_res.logo}" pixelSize="80, 40" altText="SAP Sailing Analytics" styleName="{local_res.css.logo}"/>
<div ui:field="applicationNameUi" class="{local_res.css.logotitle}"></div>
</div>
<div
ui:field="pageTitleUi"
class="{local_res.css.siteheader2ndCol}"
>
<div ui:field="titleUi" style="margin-top:14px;" class="{local_res.css.siteHeaderTitle}"></div>
<div ui:field="subTitleUi" style="display:none;" class="{local_res.css.siteHeaderSubTitle}"></div>
</div>
<g:SimplePanel ui:field="rightSideUi" styleName="{local_res.css.siteheaderRight}">
</g:SimplePanel>
</header>
</g:HTMLPanel>
</ui:UiBinder>
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:with field="local_res"
type="com.sap.sse.gwt.client.sapheader.SAPHeaderResources" />
<g:HTMLPanel>
<header class="{local_res.css.siteheader}">
<div class="{local_res.css.siteheaderLeft}">
<a class="{local_res.css.logo}" ui:field="logoAnchor" target="_blank"></a>
<a class="{local_res.css.logotitle}" target="_blank" ui:field="applicationNameAnchor"></a>
</div>
<div ui:field="pageTitleUi" class="{local_res.css.siteheader2ndCol}">
<div ui:field="titleUi" style="margin-top:14px;" class="{local_res.css.siteHeaderTitle}"></div>
<div ui:field="subTitleUi" style="display:none;" class="{local_res.css.siteHeaderSubTitle}"></div>
</div>
<g:SimplePanel ui:field="rightSideUi" styleName="{local_res.css.siteheaderRight}" />
</header>
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,28 +1,28 @@
package com.sap.sse.gwt.client.sapheader;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
public interface SAPHeaderResources extends ClientBundle {
public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class);
@Source("SAPHeader.gss")
LocalCss css();
public interface LocalCss extends CssResource {
String siteheader();
String siteheaderLeft();
String siteHeaderTitle();
String siteHeaderSubTitle();
String siteheader2ndCol();
String siteheaderRight();
String logo();
String logotitle();
String pagetitle();
}
@Source("logo-small@2x.png")
ImageResource logo();
}
package com.sap.sse.gwt.client.sapheader;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
public interface SAPHeaderResources extends ClientBundle {
public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class);
@Source("SAPHeader.gss")
LocalCss css();
public interface LocalCss extends CssResource {
String siteheader();
String siteheaderLeft();
String siteHeaderTitle();
String siteHeaderSubTitle();
String siteheader2ndCol();
String siteheaderRight();
String logo();
String logotitle();
String pagetitle();
}
@Source("logo-small@2x.png")
ImageResource logo();
}
@@ -1,58 +1,58 @@
package com.sap.sse.security.ui.authentication.generic.sapheader;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.sapheader.SAPHeader;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView;
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuViewImpl;
/**
* {@link SAPHeader} which is decorated by a authentication control on the right side. This is typically used to
* integrate with {@link GenericAuthentication}.
*/
public class SAPHeaderWithAuthentication extends SAPHeader {
private static final SAPHeaderWithAuthenticationResources res = SAPHeaderWithAuthenticationResources.INSTANCE;
private final SimplePanel rightWrapper;
private AuthenticationMenuView authenticationMenuView;
public SAPHeaderWithAuthentication(String applicationName, String headerTitle) {
this(applicationName);
setHeaderTitle(headerTitle);
}
public SAPHeaderWithAuthentication(String applicationName) {
super(applicationName);
res.css().ensureInjected();
FlowPanel rightWithAuthentication = new FlowPanel();
rightWithAuthentication.addStyleName(res.css().header_right_wrapper());
Anchor authenticationMenu = new Anchor();
authenticationMenu.addStyleName(res.css().usermanagement_icon());
rightWithAuthentication.add(authenticationMenu);
authenticationMenuView = new AuthenticationMenuViewImpl(authenticationMenu, res.css().usermanagement_loggedin(), res.css().usermanagement_open());
rightWrapper = new SimplePanel();
rightWrapper.addStyleName(res.css().header_right_extension());
rightWithAuthentication.add(rightWrapper);
super.addWidgetToRightSide(rightWithAuthentication);
}
@Override
public void addWidgetToRightSide(Widget widget) {
rightWrapper.add(widget);
}
/**
* @return the {@link AuthenticationMenuView} associated with the authentication control on the right side of the header.
*/
public AuthenticationMenuView getAuthenticationMenuView() {
return authenticationMenuView;
}
}
package com.sap.sse.security.ui.authentication.generic.sapheader;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.sapheader.SAPHeader;
import com.sap.sse.security.ui.authentication.generic.GenericAuthentication;
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView;
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuViewImpl;
/**
* {@link SAPHeader} which is decorated by a authentication control on the right side. This is typically used to
* integrate with {@link GenericAuthentication}.
*/
public class SAPHeaderWithAuthentication extends SAPHeader {
private static final SAPHeaderWithAuthenticationResources res = SAPHeaderWithAuthenticationResources.INSTANCE;
private final SimplePanel rightWrapper;
private AuthenticationMenuView authenticationMenuView;
public SAPHeaderWithAuthentication(String applicationName, String applicationBaseUrl, String headerTitle) {
this(applicationName, applicationBaseUrl);
setHeaderTitle(headerTitle);
}
public SAPHeaderWithAuthentication(String applicationName, String applicationBaseUrl) {
super(applicationName, applicationBaseUrl);
res.css().ensureInjected();
FlowPanel rightWithAuthentication = new FlowPanel();
rightWithAuthentication.addStyleName(res.css().header_right_wrapper());
Anchor authenticationMenu = new Anchor();
authenticationMenu.addStyleName(res.css().usermanagement_icon());
rightWithAuthentication.add(authenticationMenu);
authenticationMenuView = new AuthenticationMenuViewImpl(authenticationMenu, res.css().usermanagement_loggedin(), res.css().usermanagement_open());
rightWrapper = new SimplePanel();
rightWrapper.addStyleName(res.css().header_right_extension());
rightWithAuthentication.add(rightWrapper);
super.addWidgetToRightSide(rightWithAuthentication);
}
@Override
public void addWidgetToRightSide(Widget widget) {
rightWrapper.add(widget);
}
/**
* @return the {@link AuthenticationMenuView} associated with the authentication control on the right side of the header.
*/
public AuthenticationMenuView getAuthenticationMenuView() {
return authenticationMenuView;
}
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file datatype="javapropertyresourcebundle" date="2016-08-12T10:45:59Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties" source-language="en" target-language="ja">
<file datatype="javapropertyresourcebundle" date="2016-09-29T12:17:51Z" original="/usr/sap/ljs/app/Converter_4178050061048672287/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties" source-language="en" target-language="ja">
<header>
<skl>
<internal-file crc="d38efe4badae12b0c22cb2104d135a30" form="base64">H4sIAAAAAAAAAIVX3W8bNwx/11+hPmUDVqN7LSAUhpNgxfI1J24wYC/qibYPOUs3SRcvGPq/j6Sk052Tri93EiVSFPnjh6w+gLrBj2jcodf2Ra3SX/Q6hKPzRt3lwUhZQw86qvSThSo6t9uB+WzVFQ9ka6UOcgjg5b8fvr0Tod1ZXL3HH67xdNOn6dDz9HaIae6GKFr7rLvWbJDfkpKf05wF2qTw0JkbF+8z54rm0rooQ5bxkQ4WjUc1geSoFQ9ZhAgQY2t3Qd3ngdi2XQRPG4O65DHvDIvFQhyhQwOBekz/X9KVon9Bzgf3BXy7feEjHpgko5PPTGQRJGELsdnjEu36bLfOH3RsnVWXmZ4s1dYVYqKbkE2XgQ0zWpXuVdce2tjBbMNH0XQugFrRFz2jDZ6AjuE/CabDFKnCo8DDILQxm/VVurtaGiNxJpNZhHtSt7+LRtsGOrXinyi+UMVLAg667RS8p9+4fD2E+Jt+hmW8Ah3izWqvvW4inXvXIQUkWLK2Hp0rj23cS0QXLUe6rmxGphGIP5aL8lBq2S/jHkXu0X4/khzOnY3XGj0zwj/IAx4nD0QcsbeaYGuEX1NRlhG413YHRZBa8bQGDm08h4gWS36QJk2EgQ6y9HMeJuRW8h8DBAbRZPmTMO5PN6xBd93Lo7bxwZ1P5Dj54gbpeVUecZmgaio7KfxJgPfOM1tGrFpayUQ0X98DAe24bztIrIz4fVZPN2gdGxUKer9MYxHQS+oeP8LDweF4zT8CHOFMgGmjusBPOnnTG01C166DoC743CHTpCei3Do/6otW/vXbnPMO/KENAW3zir+vS29JISTSuZQU1QXjkk6UBEuKHN5QxU+2VcF1Mx3NICl2TMpQ7HLM9wnpI+5HPVJ0b/r7oWkghO2A/lLTiUw7SES5wUJm2Dd7aJ7I0V6mUAyckCh/EjInCzJ7CzOCfbLuaC9eabxJC9n9ccxvIWXudDrfotwA9QCPOME4w3jY5zya9nAI8kbd4SnmRcI/bYghpaRlol0wSS3fkH3CJprBe3RJDa40r9FVBlPbpQispU2GqWFTuFbOcwcBY/0aIK7h76FFCOMJoXIb3MCRf8AtGFt1TwYACSRQFiUTCJpMHnVN2SIXPvSBQRGtxqxQal9TaRwyF5xtKW5Gdxrj8SLzYMjb5mEwZ0hHT83AzGAS7wx4Q1o5kTCLpcVf9ktCG0VDgmOAlGwQ3Yu5fgSzO+8wfmL7Ot5ZZj8uf1fTEylvqlzFzNUVONu5iqJLnlYQpXw4+IkT14AdRNbV8EqtM57Wcu7XFpHBmwvnI+IZu5NsoGLiFW9MrFXSV2j0EGpqrljbYzbF2laC+tQdk24NBV619uke7Y8h1eGQL88njal7PJHK41cAW/314dvZWfj+QWyb4u052J5H6ilY3PY079LyaJJZ1vs+60SpKSCSSutyv5Owq/ce7/y6DEzFzZw3x9WJt2aYwitfk94zgyLSkAbmwd2ndviSp2M+be1Crmr2ngT8YmYiDKuNrUqqSbShff4nMmU6fyHPMZ9MWgECGeGgQ2tim/M8iV5n4JPgqqu4IItJBVWTQps7fIJ36fI5d1Mh3GHL4HX3OG+j3wkLRy4yN3CUWCjwoJ5LWLZOrmfFNKn4lmbz1mes2dKqpyrh/ClEcQm7cXZ/bWRcw7WjNDI6Rjj0MRe3Bvs/7AnZJIn7reJqIaLfn2i3hYatpa2hGin1TpPC1O5EwLTUACYJWKGtuffBKjwSJVGzjvio6jh78Qydw9NcRtzhMNi24YAiK6dNpZpMV1PNTCK5UuBh2MdDqhUloxJF4NnTZmnZdbMO6afWNt1AzwZ6mwAbbOvdIbVhP2MnbLftbvDpMbOazsYn1iXr3jtLGag8txiVlCGzw7GPbOLocJ4J15kx7m6xux7zMcKmLNx1uoE97qTH10sP/ABCzxzr7omYt3aTj91U+vyhO2XJb17imJ3gZyliPcsKAqGy9JBfqvxCxv5canR8bl4w/MqzlR9wC2KhFwg9bnLLYaqTmN04e3aWqwCpk7sOM3HeorTi7JXy3swtuWwmxISveaeHd9ritfZ4G/6LHgns/ytsWwa9A3VXKJg1Ekn8B7YNScxQEAAA</internal-file>
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more