Merge branch 'master' into improved-replication

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
This commit is contained in:
Axel Uhl committed 2014-07-15 11:15:14 +02:00
commit 93c9ea3a4d
185 files changed
+2348 -1234

No files matched your search

+3 -1
View File
@@ -16,6 +16,7 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[TracTrac|wiki/tractrac-lifecycle]]
* [[Analytics on a stick|wiki/analytics-on-a-stick]]
* [[Monitoring Apache and RabbitMQ|wiki/monitoring-apache-and-rabbitmq]]
* [[JMX Support|wiki/jmx]]
* Information for Developers
* [[Cook Book|wiki/cook-book]]
* [[OnBoarding Information|wiki/onboarding]]
@@ -43,11 +44,12 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[Racecommittee App|wiki/racecommittee-app]]
* [[Web Services API|wiki/sailing-webservices]]
* [[Theses (Bachelor, Master, ...)|wiki/theses]]
* [[Google Analytics (Web Page Tracking)|wiki/ganalytics]]
* Planning and Event Information
* [[Project Planning (bigger development)|wiki/planning]]
* [[General Event Planning|wiki/general-event-planning]]
* [[Information about Extreme Sailing Series|wiki/extreme-sailing-series]]
* [[Kieler Woche 2014 event page|wiki/kielerwoche2014]]
* [[Travemünder Woche 2014 event page|wiki/tw2014]]
### Internal services (not related to wiki but useful)
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.common;
import com.sap.sailing.domain.common.impl.CentralAngleDistance;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.RadianBearingImpl;
import com.sap.sailing.domain.common.impl.RadianPosition;
@@ -65,6 +66,17 @@ public class AbstractPosition implements Position {
}
return result;
}
@Override
public double getQuickApproximateNauticalMileDistance(Position p) {
double latDeg = getLatDeg();
double pLatDeg = p.getLatDeg();
final double latDiffDeg = Math.abs(latDeg - pLatDeg);
double cosineOfAverageLatitude = Math.cos((latDeg+pLatDeg)/2./180.*Math.PI);
final double normalizedLngDiffDeg = cosineOfAverageLatitude * Math.abs(getLngDeg() - p.getLngDeg());
// One degree of latitude or one degree of longitude at the equator each correspond to 60 nautical miles.
return Math.sqrt(latDiffDeg*latDiffDeg + normalizedLngDiffDeg*normalizedLngDiffDeg) * 60.;
}
@Override
public Bearing getBearingGreatCircle(Position p) {
@@ -161,6 +173,18 @@ public class AbstractPosition implements Position {
return result;
}
@Override
public Position getLocalCoordinates(Position localOrigin, Bearing localEquatorBearing) {
return this.getTargetCoordinates(localOrigin, localEquatorBearing, new DegreePosition(0.0, 0.0), new DegreeBearingImpl(90.0));
}
@Override
public Position getTargetCoordinates(Position localOrigin, Bearing localEquatorBearing, Position targetOrigin, Bearing targetEquatorBearing) {
Bearing localBearing = localEquatorBearing.getDifferenceTo(localOrigin.getBearingGreatCircle(this));
Distance localDistance = this.getDistance(localOrigin);
return targetOrigin.translateGreatCircle(targetEquatorBearing.add(localBearing), localDistance);
}
@Override
public String toString() {
return "(" + getLatDeg() + "," + getLngDeg() + ")";
@@ -71,4 +71,38 @@ public interface Position extends Serializable {
* is positive if <code>left<code> is actually the position farther to the left, as seen from this position.
*/
Distance getDistanceToLine(Position left, Position right);
/**
* Transform a position into its representation in a local geo-coordinate frame where <code>localOrigin</code> is
* locally represented as <code>(latitude, longitude) = (0,0)</code> and <code>localEquatorBearing</code> is locally
* represented as <code>East = 90 degrees</code>.
* <p>
*
* This can be used to move a GPS-track to the origin <code>(0,0)</code> and aligning wind direction with North, or
* bearing of start line with East.
*/
Position getLocalCoordinates(Position localOrigin, Bearing localEquatorBearing);
/**
* Transform a position from local geo-coordinate frame <code>(localOrigin, localEquatorBearing)</code>
* into the corresponding position in target geo-coordinate frame <code>(targetOrigin, targetEquatorBearing)</code>.
* The transformation is equivalent to a rotation of the earth, which moves <code>localOrigin</code>
* to <code>targetOrigin</code> while aligning <code>localEquatorBearing</code> to <code>targetEquatorBearing</code>.<p>
*
* This can be used to move a GPS-track from one location to another, in order to compare GPS-tracks from different areas
* by superposing and aligning them based on wind direction or bearing of start line.
*/
Position getTargetCoordinates(Position localOrigin, Bearing localEquatorBearing, Position targetOrigin, Bearing targetEquatorBearing);
/**
* Computes an approximate distance between this position and <code>p</code> by
* using the average latitude's cosine to scale the longitudes and then calculate
* the distance non-spherically in an imaginary Euklidian plane. The error is below
* 1% even for several hundred nautical miles of randomly chosen distances, and even
* lower (~.1%) for distances below ten nautical miles.<p>
*
* The method executes efficiently and requires only one call to the cosine method.
*/
double getQuickApproximateNauticalMileDistance(Position p);
}
@@ -4,6 +4,14 @@ import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.confidence.Weigher;
/**
* Uses {@link Position#getQuickApproximateNauticalMileDistance(Position)} to come
* to a quick and reasonably fair weight for distances where the weight asymptotically
* decreases hyperbolically with distance.
*
* @author Axel Uhl (d043530)
*
*/
public class HyperbolicDistanceWeigher implements Weigher<Position> {
private static final long serialVersionUID = 5521299690322077224L;
@@ -37,8 +45,8 @@ public class HyperbolicDistanceWeigher implements Weigher<Position> {
*/
@Override
public double getConfidence(Position fix, Position request) {
double x = fix.getDistance(request).getMeters();
double c = halfConfidence.getMeters();
double x = fix.getQuickApproximateNauticalMileDistance(request);
double c = halfConfidence.getNauticalMiles();
double y = c;
return c/(x+y);
}
@@ -1083,11 +1083,11 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private Regatta loadRegatta(DBObject dbRegatta, TrackedRegattaRegistry trackedRegattaRegistry) {
Regatta result = null;
if (dbRegatta != null) {
String baseName = (String) dbRegatta.get(FieldNames.REGATTA_BASE_NAME.name());
String name = (String) dbRegatta.get(FieldNames.REGATTA_NAME.name());
String boatClassName = (String) dbRegatta.get(FieldNames.BOAT_CLASS_NAME.name());
Serializable id = (Serializable) dbRegatta.get(FieldNames.REGATTA_ID.name());
if (id == null) {
id = baseName + "("+boatClassName+")";
id = name;
}
BoatClass boatClass = null;
if (boatClassName != null) {
@@ -1099,14 +1099,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(
new MongoObjectFactoryImpl(database, serviceFinderFactory),
this);
Serializable courseAreaId = (Serializable) dbRegatta.get(FieldNames.COURSE_AREA_ID.name());
CourseArea courseArea = null;
if (courseAreaId != null) {
UUID courseAreaUuid = UUID.fromString(courseAreaId.toString());
courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid);
}
RegattaConfiguration configuration = null;
if (dbRegatta.containsField(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())) {
try {
@@ -1116,8 +1114,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
logger.log(Level.WARNING, "Error loading racing procedure configration for regatta.", e);
}
}
result = new RegattaImpl(raceLogStore, baseName, boatClass, series, /* persistent */ true, loadScoringScheme(dbRegatta), id, courseArea);
Boolean useStartTimeInference = (Boolean) dbRegatta.get(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name());
result = new RegattaImpl(raceLogStore, name, boatClass, series, /* persistent */true,
loadScoringScheme(dbRegatta), id, courseArea, useStartTimeInference == null ? true
: useStartTimeInference);
result.setRegattaConfiguration(configuration);
}
return result;
@@ -26,6 +26,7 @@ public enum FieldNames {
REGATTA_BASE_NAME,
REGATTA_SERIES,
REGATTA_REGATTA_CONFIGURATION,
REGATTA_USE_START_TIME_INFERENCE,
SERIES_NAME,
SERIES_IS_MEDAL,
SERIES_STARTS_WITH_ZERO_SCORE,
@@ -585,7 +585,6 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
DBObject dbRegatta = new BasicDBObject();
DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
dbRegatta.put(FieldNames.REGATTA_BASE_NAME.name(), regatta.getBaseName());
dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId());
dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name());
if (regatta.getBoatClass() != null) {
@@ -605,7 +604,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
DBObject configurationObject = (DBObject) JSON.parse(json.toString());
dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject);
}
dbRegatta.put(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name(), regatta.useStartTimeInference());
regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
}
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
@@ -71,8 +72,8 @@ public class CreateAndTrackWithRaceLogTest {
author = service.getServerAuthor();
Series series = new SeriesImpl("series", false, Collections.singletonList(fleet),
Collections.singletonList(columnName), service);
regatta = service.createRegatta("regatta", "Laser", UUID.randomUUID(), Collections.<Series>singletonList(series),
false, new HighPoint(), UUID.randomUUID());
regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", UUID.randomUUID(), Collections.<Series>singletonList(series),
false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true);
leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {});
adapter = RaceLogTrackingAdapterFactoryImpl.INSTANCE.getAdapter(DomainFactory.INSTANCE);
}
@@ -32,8 +32,8 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
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.TrackedRegattaImpl;
public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
@@ -68,7 +68,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(i+1000, 10, 20, 30, 40));
}
TrackedRegatta regatta = new TrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, "regatta", boatClass, null, null, "a", null));
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, null, null, "a", null));
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections.<Sideline>emptyList(),
EmptyWindStore.INSTANCE, store, 0, 0, 0);
@@ -110,7 +110,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(i, 10, 20, 30, 40));
}
TrackedRegatta regatta = new TrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, "regatta", boatClass, null, null, "a", null));
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, null, null, "a", null));
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections.<Sideline>emptyList(),
EmptyWindStore.INSTANCE, store, 0, 0, 0);
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.common.racelog.tracking.RaceNotCreatedException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.racelog.RaceLog;
@@ -55,8 +56,9 @@ public class RaceLogConnectivityParams implements RaceTrackingConnectivityParame
WindStore windStore, GPSFixStore gpsFixStore) {
if (regatta == null) {
BoatClass boatClass = new RaceInformationFinder(getRaceLog()).analyze().getBoatClass();
regatta = service.getOrCreateDefaultRegatta("RaceLog-tracking default Regatta", boatClass.getName(),
UUID.randomUUID());
regatta = service.getOrCreateDefaultRegatta(
RegattaImpl.getDefaultName("RaceLog-tracking default Regatta", boatClass.getName()),
boatClass.getName(), UUID.randomUUID());
}
if (regatta == null) {
throw new RaceNotCreatedException("No regatta for race-log tracked race");
@@ -1,6 +1,5 @@
package com.sap.sailing.domain.racelogtracking.impl;
import java.util.Collections;
import java.util.Set;
import com.sap.sailing.domain.base.RaceDefinition;
@@ -22,12 +21,12 @@ public class RaceLogRacesHandle implements RaceHandle {
}
@Override
public Set<RaceDefinition> getRaces() {
return getRaces(-1);
public RaceDefinition getRace() {
return getRace(-1);
}
@Override
public Set<RaceDefinition> getRaces(long timeoutInMilliseconds) {
public RaceDefinition getRace(long timeoutInMilliseconds) {
long start = System.currentTimeMillis();
Set<RaceDefinition> raceDefs = tracker.getRaces();
@@ -54,7 +53,7 @@ public class RaceLogRacesHandle implements RaceHandle {
}
}
}
return result == null ? null : Collections.singleton(result);
return result == null ? null : result;
}
@Override
@@ -178,9 +178,9 @@ public abstract class EventBaseImpl implements EventBase {
}
@Override
public void addSponsorImageURL(URL SponsorImageURL) {
if (!sponsorImageURLs.contains(SponsorImageURL)) {
sponsorImageURLs.add(SponsorImageURL);
public void addSponsorImageURL(URL sponsorImageURL) {
if (!sponsorImageURLs.contains(sponsorImageURL)) {
sponsorImageURLs.add(sponsorImageURL);
}
}
@@ -28,7 +28,7 @@ public interface SwissTimingFactory {
* When the connector is used with SailMaster instances hidden behind a "bridge" / firewall, no explicit requests
* are possible, and the connector has to rely solely on the events it receives. It may, though, load recorded
* race-specific messages through a {@link RaceSpecificMessageLoader} object. If a non-<code>null</code>
* {@link RaceSpecificMessageLoader} is provided, the connector will fetch the {@link #getRaces() list of races}
* {@link RaceSpecificMessageLoader} is provided, the connector will fetch the {@link #getRace() race}
* from that loader. Additionally, the connector will use the loader upon each
* {@link SailMasterConnector#trackRace(String)} to load all messages recorded by the loader for the race requested
* so far.
@@ -99,8 +99,10 @@ public class DomainFactoryImpl implements DomainFactory {
if (result == null) {
BoatClass regattaBoatClass = boatClass != null ? boatClass : getRaceTypeFromRaceID(raceID).getBoatClass();
Calendar calendar = Calendar.getInstance();
result = new RegattaImpl(raceLogStore, "ST Regatta " + calendar.get(Calendar.YEAR)+" for race "+raceID, regattaBoatClass, trackedRegattaRegistry,
getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), raceID, null);
result = new RegattaImpl(raceLogStore, RegattaImpl.getDefaultName(
"ST Regatta " + calendar.get(Calendar.YEAR) + " for race " + raceID, regattaBoatClass.getName()),
regattaBoatClass, trackedRegattaRegistry, getBaseDomainFactory().createScoringScheme(
ScoringSchemeType.LOW_POINT), raceID, null);
logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+")");
raceIDToRegattaCache.put(raceID, result);
}
@@ -64,7 +64,7 @@ import com.sap.sse.common.Util;
* When the connector is used with SailMaster instances hidden behind a "bridge" / firewall, no explicit requests are
* possible, and the connector has to rely solely on the events it receives. It may, though, load recorded race-specific
* messages through a {@link RaceSpecificMessageLoader} object. If a non-<code>null</code> {@link RaceSpecificMessageLoader}
* is provided to the constructor, the connector will fetch the {@link #getRaces() list of races} from that loader.
* is provided to the constructor, the connector will fetch the {@link #getRace() race} from that loader.
* Additionally, the connector will use the loader upon each {@link #trackRace(String)} to load all messages recorded
* by the loader for the race requested so far.
* <p>
@@ -170,7 +170,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
}
@Override
public Set<RaceDefinition> getRaces() {
public RaceDefinition getRace() {
synchronized (this) {
while (race == null) {
try {
@@ -180,11 +180,11 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
}
}
}
return Collections.singleton(race);
return race;
}
@Override
public Set<RaceDefinition> getRaces(long timeoutInMilliseconds) {
public RaceDefinition getRace(long timeoutInMilliseconds) {
long start = System.currentTimeMillis();
synchronized (this) {
RaceDefinition preResult = race;
@@ -200,11 +200,11 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
interrupted = true;
}
}
final Set<RaceDefinition> result;
final RaceDefinition result;
if (preResult == null) {
result = Collections.emptySet();
result = null;
} else {
result = Collections.singleton(preResult);
result = preResult;
}
return result;
}
@@ -1,32 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<booleanAttribute key="com.sap.jvm.profiling.ui.widgets.allowDebugging" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="org.eclipse.jdt.junit.launchconfig"/>
<mapEntry key="[run]" value="org.eclipse.jdt.junit.launchconfig"/>
</mapAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sapjvm7"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -DspatialWind=true"/>
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ID%CTX_KEY%false%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%KEY_JRUBY_SUPPORT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-IO_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-METHOD_PARAMETER_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-NETWORK_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_RESOLVE_ALL_HOSTS%CTX_KEY%true%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-PERFORMANCE_HOTSPOT_TRACE" value="KEY_IGNORE_SLEEPING_THREADS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-SYNCHRONIZATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
</launchConfiguration>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<booleanAttribute key="com.sap.jvm.profiling.ui.widgets.allowDebugging" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[debug]" value="org.eclipse.jdt.junit.launchconfig"/>
<mapEntry key="[run]" value="org.eclipse.jdt.junit.launchconfig"/>
</mapAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -DspatialWind=true"/>
<stringAttribute key="profilingTraceType-ALLOCATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_INC_LINE_NRS%CTX_KEY%true%CTX_ENTRY%KEY_MAX_SIZE%CTX_KEY%65536%CTX_ENTRY%INCREASE_COUNT%CTX_KEY%8192%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ID%CTX_KEY%false%CTX_ENTRY%KEY_MIN_SIZE%CTX_KEY%32%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%CLASS_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ADAPTIVE%CTX_KEY%false%CTX_ENTRY%KEY_JRUBY_SUPPORT%CTX_KEY%false%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-IO_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-METHOD_PARAMETER_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-NETWORK_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_RESOLVE_ALL_HOSTS%CTX_KEY%true%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-PERFORMANCE_HOTSPOT_TRACE" value="KEY_IGNORE_SLEEPING_THREADS%CTX_KEY%true%CTX_ENTRY%KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
<stringAttribute key="profilingTraceType-SYNCHRONIZATION_TRACE" value="KEY_APPLICATION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_SESSION_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_ENABLEMENT%CTX_KEY%false%CTX_ENTRY%KEY_USER_FILTER%CTX_KEY%*%CTX_ENTRY%KEY_REQUEST_FILTER%CTX_KEY%*%CTX_ENTRY%"/>
</launchConfiguration>
@@ -0,0 +1,122 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
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.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.TrackedLegImpl;
import com.sap.sailing.domain.tracking.impl.TrackedLegOfCompetitorImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
public class BeatAngleTest {
private Competitor competitor;
private GPSFixTrack<Competitor, GPSFixMoving> competitorTrack;
private TrackedRace trackedRace;
private TrackedLeg trackedLeg;
private TrackedLegOfCompetitor trackedLegOfCompetitor;
@SuppressWarnings("unchecked")
@Before
public void setUp() {
competitor = mock(CompetitorImpl.class);
trackedRace = mock(DynamicTrackedRaceImpl.class);
trackedLeg = mock(TrackedLegImpl.class);
trackedLegOfCompetitor = mock(TrackedLegOfCompetitorImpl.class);
competitorTrack = mock(DynamicGPSFixMovingTrackImpl.class);
when(competitorTrack.getEstimatedPosition((TimePoint) anyObject(), /* extrapolate */ anyBoolean())).thenReturn(new DegreePosition(0, 0));
when(trackedLegOfCompetitor.getCompetitor()).thenReturn(competitor);
when(trackedLegOfCompetitor.getTrackedLeg()).thenReturn(trackedLeg);
when(trackedRace.getTrack(competitor)).thenReturn(competitorTrack);
when(trackedLeg.getTrackedRace()).thenReturn(trackedRace);
}
private void setUp(final Bearing windToBearing, final Bearing boatToBearing, final Bearing legBearing, LegType legType)
throws NoWindException {
when(trackedLeg.getLegBearing((TimePoint) anyObject())).thenReturn(legBearing);
when(trackedLeg.getLegType((TimePoint) anyObject())).thenReturn(legType);
when(trackedRace.getWind((Position) anyObject(), (TimePoint) anyObject())).thenReturn(
new WindImpl(new DegreePosition(0, 0), MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(12,
windToBearing)));
when(trackedLegOfCompetitor.getSpeedOverGround((TimePoint) anyObject())).thenReturn(new KnotSpeedWithBearingImpl(10, boatToBearing));
when(trackedLegOfCompetitor.getBeatAngle((TimePoint) anyObject())).thenCallRealMethod();
when(trackedLegOfCompetitor.getBeatAngle((TimePoint) anyObject(), (WindLegTypeAndLegBearingCache) anyObject())).thenCallRealMethod();
}
@Test
public void testBeatAngleUpwindPort() throws NoWindException {
final Bearing windToBearing = new DegreeBearingImpl(123);
final Bearing boatToBearing = new DegreeBearingImpl(348);
final Bearing legBearing = new DegreeBearingImpl(303);
setUp(windToBearing, boatToBearing, legBearing, LegType.UPWIND);
assertEquals(-45., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001);
}
@Test
public void testBeatAngleUpwindStarboard() throws NoWindException {
final Bearing windToBearing = new DegreeBearingImpl(123);
final Bearing boatToBearing = new DegreeBearingImpl(258);
final Bearing legBearing = new DegreeBearingImpl(303);
setUp(windToBearing, boatToBearing, legBearing, LegType.UPWIND);
assertEquals(45., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001);
}
@Test
public void testBeatAngleDownwindPort() throws NoWindException {
final Bearing windToBearing = new DegreeBearingImpl(123);
final Bearing boatToBearing = new DegreeBearingImpl(120);
final Bearing legBearing = new DegreeBearingImpl(123);
setUp(windToBearing, boatToBearing, legBearing, LegType.DOWNWIND);
assertEquals(-177., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001);
}
@Test
public void testBeatAngleDownwindStarboard() throws NoWindException {
final Bearing windToBearing = new DegreeBearingImpl(123);
final Bearing boatToBearing = new DegreeBearingImpl(126);
final Bearing legBearing = new DegreeBearingImpl(123);
setUp(windToBearing, boatToBearing, legBearing, LegType.DOWNWIND);
assertEquals(177., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001);
}
@Test
public void testBeatAngleReachingPort() throws NoWindException {
final Bearing windToBearing = new DegreeBearingImpl(123);
final Bearing boatToBearing = new DegreeBearingImpl(33);
final Bearing legBearing = new DegreeBearingImpl(33);
setUp(windToBearing, boatToBearing, legBearing, LegType.REACHING);
assertEquals(-90., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001);
}
@Test
public void testBeatAngleReachingStarboard() throws NoWindException {
final Bearing windToBearing = new DegreeBearingImpl(303);
final Bearing boatToBearing = new DegreeBearingImpl(33);
final Bearing legBearing = new DegreeBearingImpl(33);
setUp(windToBearing, boatToBearing, legBearing, LegType.REACHING);
assertEquals(90., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001);
}
}
@@ -0,0 +1,69 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.awt.Dimension;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import org.junit.Test;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.impl.EventImpl;
public class CalculateImageSizeFromUrlTest {
@Test
public void calculateImageSizeFromUrl() throws IOException {
int width = 350;
int height = 150;
Dimension size = calculate("http://placehold.it/" + width + "x" + height);
assertTrue(size.getWidth() == width);
assertTrue(size.getHeight() == height);
}
public Dimension calculate(String urlS) throws IOException {
ImageInputStream in = null;
try {
URL url = new URL(urlS);
URLConnection conn = url.openConnection();
in = ImageIO.createImageInputStream(conn.getInputStream());
final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
if (readers.hasNext()) {
ImageReader reader = readers.next();
try {
reader.setInput(in);
return new Dimension(reader.getWidth(0), reader.getHeight(0));
} finally {
reader.dispose();
}
}
} finally {
if (in != null) {
in.close();
}
}
return null;
}
@Test
public void testEventImageSize() throws MalformedURLException, InterruptedException, ExecutionException {
Event e = new EventImpl("Event Name", /* startDate */ null, /* endDate */ null, "Kiel", /* isPublic */ true, UUID.randomUUID());
int width = Math.max(10, (int) (200. * Math.random()));
int height = Math.max(10, (int) (100. * Math.random()));
URL imageURL = new URL("http://placehold.it/" + width + "x" + height);
e.addImageURL(imageURL);
Dimension expectedSize = new Dimension(width, height);
assertEquals(expectedSize, e.getImageSize(imageURL));
}
}
@@ -349,7 +349,7 @@ public class ConfidenceTest {
Position p = new DegreePosition(12, 13);
assertEquals(1., weigher.getConfidence(p, p), 0.00000001);
Position p2 = p.translateGreatCircle(new DegreeBearingImpl(123), new MeterDistance(1000));
assertEquals(.5, weigher.getConfidence(p, p2), 0.00000001);
assertEquals(.5, weigher.getConfidence(p, p2), 0.001); // somewhat more tolerant; using quick distance in weigher
Position p3 = p.translateGreatCircle(new DegreeBearingImpl(123), new NauticalMileDistance(1000));
final double threshold = 0.001;
assertTrue("Expected confidence in 1000nm distance to be less than "+threshold+" but was "+weigher.getConfidence(p, p3),
@@ -34,8 +34,8 @@ import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedLeg;
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.TrackedRegattaImpl;
import com.sap.sse.common.Util;
import difflib.PatchFailedException;
@@ -249,7 +249,7 @@ public class CourseTest {
Course course = new CourseImpl("Test Course", waypoints);
assertWaypointIndexes(course);
final Set<CompetitorImpl> hasso = Collections.singleton(AbstractLeaderboardTest.createCompetitor("Hasso"));
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */new TrackedRegattaImpl(
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */new DynamicTrackedRegattaImpl(
new RegattaImpl("test", null, new HashSet<Series>(), false, null, "test", null)),
new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */true), hasso),
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,/* delayToLiveInMillis */3000,
@@ -279,7 +279,7 @@ public class CourseTest {
waypoints.add(wp2);
Course course = new CourseImpl("Test Course", waypoints);
final Set<CompetitorImpl> hasso = Collections.singleton(AbstractLeaderboardTest.createCompetitor("Hasso"));
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */ new TrackedRegattaImpl(
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(/* trackedRegatta */ new DynamicTrackedRegattaImpl(
new RegattaImpl("test", null, new HashSet<Series>(), false, null, "test", null)),
new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */ true),
hasso), Collections.<Sideline> emptyList(),
@@ -84,7 +84,7 @@ public class LeaderboardCourseChangeTest {
String raceColumnName = "TestRace";
RaceColumn raceColumn = series.addRaceColumn(raceColumnName, trackedRegattaRegistry);
ScoringScheme scoringScheme = new LowPoint();
Regatta mockedRegatta = new RegattaImpl("TestRegatta", boatClass, seriesSet, false, scoringScheme,
Regatta mockedRegatta = new RegattaImpl(RegattaImpl.getDefaultName("TestRegatta", boatClass.getName()), boatClass, seriesSet, false, scoringScheme,
UUID.randomUUID(), mock(CourseArea.class));
ControlPoint start = new MarkImpl("Start");
ControlPoint m1 = new MarkImpl("M1");
@@ -1158,7 +1158,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
series.add(medalSeries);
}
Regatta regatta = new RegattaImpl(regattaBaseName, boatClass, series, /* persistent */ false, scoringScheme, "123", null);
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, series, /* persistent */ false, scoringScheme, "123", null);
return regatta;
}
@@ -1222,7 +1222,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
Series finalSeries = new SeriesImpl("After", /* isMedal */false, afterFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(finalSeries);
}
Regatta regatta = new RegattaImpl("Test Regatta",DomainFactory.INSTANCE.getOrCreateBoatClass("Extreme40", /* typicallyStartsUpwind */ false),
final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("Extreme40", /* typicallyStartsUpwind */ false);
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass,
series, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN), "123", /* course area */null);
List<Competitor> competitors = createCompetitors(12);
final int firstYellowCompetitorIndex = 3;
@@ -0,0 +1,118 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.common.TimePoint;
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.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
public class MarkPositionTimeFilterTest {
private DynamicTrackedRace trackedRace;
private MarkImpl m;
private DynamicGPSFixTrackImpl<Mark> track;
@Before
public void setUp() {
trackedRace = mock(DynamicTrackedRaceImpl.class);
m = new MarkImpl("Test Mark");
track = new DynamicGPSFixTrackImpl<Mark>(m, /* millisecondsOverWhichToAverage */ 5000);
when(trackedRace.getOrCreateTrack(m)).thenReturn(track);
doCallRealMethod().when(trackedRace).recordFix(same(m), (GPSFixMoving) anyObject());
}
@Test
public void generalSetupTest() {
assertSame(track, trackedRace.getOrCreateTrack(m));
}
@Test
public void testAddFixForMark() {
trackedRace.recordFix(m, new GPSFixMovingImpl(new DegreePosition(12, 13), MillisecondsTimePoint.now(),
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
track.lockForRead();
try {
assertEquals(1, track.getRawFixes().size());
} finally {
track.unlockAfterRead();
}
}
@Test
public void testAddFixForMarkWithinTrackingTimeRange() {
TimePoint start = MillisecondsTimePoint.now();
TimePoint fix = start.plus(10000);
TimePoint end = fix.plus(10000);
when(trackedRace.getStartOfTracking()).thenReturn(start);
when(trackedRace.getEndOfTracking()).thenReturn(end);
trackedRace.recordFix(m, new GPSFixMovingImpl(new DegreePosition(12, 13), fix,
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
track.lockForRead();
try {
assertEquals(1, track.getRawFixes().size());
} finally {
track.unlockAfterRead();
}
}
@Test
public void testAddFixForMarkOutsideTrackingTimeRange() {
TimePoint start = MillisecondsTimePoint.now();
TimePoint end = start.plus(10000);
TimePoint fix = end.plus(10000);
when(trackedRace.getStartOfTracking()).thenReturn(start);
when(trackedRace.getEndOfTracking()).thenReturn(end);
trackedRace.recordFix(m, new GPSFixMovingImpl(new DegreePosition(12, 13), fix,
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
track.lockForRead();
try {
assertTrue(track.getRawFixes().isEmpty());
} finally {
track.unlockAfterRead();
}
}
@Test
public void testAddFixForMarkFirstOutsideTrackingTimeRangeThenWithin() {
TimePoint start = MillisecondsTimePoint.now();
TimePoint end = start.plus(10000);
TimePoint fix = end.plus(10000);
when(trackedRace.getStartOfTracking()).thenReturn(start);
when(trackedRace.getEndOfTracking()).thenReturn(end);
trackedRace.recordFix(m, new GPSFixMovingImpl(new DegreePosition(12, 13), fix,
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
track.lockForRead();
try {
assertTrue(track.getRawFixes().isEmpty());
} finally {
track.unlockAfterRead();
}
when(trackedRace.getEndOfTracking()).thenReturn(fix.plus(10000));
trackedRace.recordFix(m, new GPSFixMovingImpl(new DegreePosition(12, 13), fix,
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
track.lockForRead();
try {
assertEquals(1, track.getRawFixes().size());
} finally {
track.unlockAfterRead();
}
}
}
@@ -240,4 +240,56 @@ public class PositionTest {
assertEquals(p6.getDistanceToLine(p1, p4).getMeters(), p6.getDistance(p1).scale(-1).getMeters(), delta);
assertEquals(right.getDistanceToLine(p1, p6).getMeters(), right.getDistance(p1).getMeters(), delta);
}
@Test
public void testQuickApproximateDistance1() {
Position p1 = new DegreePosition(0, 0);
Position p2 = new DegreePosition(1, 0);
assertEquals(60., p1.getQuickApproximateNauticalMileDistance(p2), 0.001);
}
@Test
public void testQuickApproximateDistance2() {
Position p1 = new DegreePosition(0, 0);
Position p2 = new DegreePosition(0, 1);
assertEquals(60., p1.getQuickApproximateNauticalMileDistance(p2), 0.001);
}
@Test
public void testQuickApproximateDistanceDiagonalNearEquator() {
Position p1 = new DegreePosition(0, 0);
Position p2 = new DegreePosition(0.1, 0.1);
assertEquals(p1.getDistance(p2).getNauticalMiles(), p1.getQuickApproximateNauticalMileDistance(p2), 0.01 * p1.getDistance(p2).getNauticalMiles());
}
@Test
public void testQuickApproximateDistanceDiagonalFarFromEquator() {
Position p1 = new DegreePosition(49, 8);
Position p2 = new DegreePosition(50, 9);
assertEquals(p1.getDistance(p2).getNauticalMiles(), p1.getQuickApproximateNauticalMileDistance(p2), 0.01 * p1.getDistance(p2).getNauticalMiles());
}
@Test
public void testQuickApproximateLongDistanceDiagonalFarFromEquator() {
Position p1 = new DegreePosition(20, -4);
Position p2 = new DegreePosition(50, 9);
assertEquals(p1.getDistance(p2).getNauticalMiles(), p1.getQuickApproximateNauticalMileDistance(p2), 0.01 * p1.getDistance(p2).getNauticalMiles());
}
@Test
public void performanceComparisonQuickAndFullDistance() {
Position p1 = new DegreePosition(20, -4);
Position p2 = new DegreePosition(50, 9);
long startFull = System.currentTimeMillis();
int numberOfExecutions = 1000000;
for (int i=0; i<numberOfExecutions; i++) {
p1.getDistance(p2);
}
long endFull = System.currentTimeMillis();
for (int i=0; i<numberOfExecutions; i++) {
p1.getQuickApproximateNauticalMileDistance(p2);
}
long endQuick = System.currentTimeMillis();
System.out.println("Full: "+(endFull-startFull)+"ms; Quick: "+(endQuick-endFull)+"ms");
}
}
@@ -50,10 +50,10 @@ import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.WindPositionMode;
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.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
public class ReachingLegTest extends TrackBasedTest {
@@ -87,9 +87,9 @@ public class ReachingLegTest extends TrackBasedTest {
protected DynamicTrackedRace createTrackedRace(String regattaName, String raceName, String boatClassName,
Iterable<Competitor> competitors, TimePoint timePointForFixes) {
BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true);
Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, regattaName, boatClass, /* trackedRegattaRegistry */ null,
Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, /* trackedRegattaRegistry */ null,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null);
TrackedRegatta trackedRegatta = new TrackedRegattaImpl(regatta);
TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta);
List<Waypoint> waypoints = new ArrayList<Waypoint>();
// create a two-lap upwind/downwind course:
MarkImpl left = new MarkImpl("Left lee gate buoy");
@@ -41,10 +41,10 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRegatta;
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.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
public abstract class TrackBasedTest {
@@ -96,9 +96,9 @@ public abstract class TrackBasedTest {
public static DynamicTrackedRace createTestTrackedRace(String regattaName, String raceName, String boatClassName,
Iterable<Competitor> competitors, TimePoint timePointForFixes) {
BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true);
Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, regattaName, boatClass, /* trackedRegattaRegistry */ null,
Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, /* trackedRegattaRegistry */ null,
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null);
TrackedRegatta trackedRegatta = new TrackedRegattaImpl(regatta);
TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta);
List<Waypoint> waypoints = new ArrayList<Waypoint>();
// create a two-lap upwind/downwind course:
MarkImpl left = new MarkImpl("Left lee gate buoy");
@@ -65,8 +65,7 @@ public class UnicodeCharactersInCompetitorNamesTest {
/* simulateWithStartTimeNow */ false, EmptyRaceLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, "tracTest", "tracTest", "",
new DummyTrackedRegattaRegistry());
Iterable<Competitor> competitors = fourtyninerYellow_2.getRacesHandle().getRaces().iterator().next()
.getCompetitors();
Iterable<Competitor> competitors = fourtyninerYellow_2.getRacesHandle().getRace().getCompetitors();
for (Competitor competitor : competitors) {
System.out.println(competitor.getName());
}
@@ -26,9 +26,9 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
import com.sap.sailing.domain.tracking.MarkPassing;
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.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
import com.sap.sse.common.Util;
public class UpdateMarkPassingTest {
@@ -43,7 +43,7 @@ public class UpdateMarkPassingTest {
when(race.getBoatClass()).thenReturn(new BoatClassImpl("49er", /* typicallyStartsUpwind */ true));
when(race.getCompetitors()).thenReturn(Collections.singleton(competitor));
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(
/* trackedRegatta */new TrackedRegattaImpl(new RegattaImpl("test", null, new HashSet<Series>(), false, null,
/* trackedRegatta */new DynamicTrackedRegattaImpl(new RegattaImpl("test", null, new HashSet<Series>(), false, null,
"test", null)), race, Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,
/* delayToLiveInMillis */1000, /* millisecondsOverWhichToAverageWind */30000,
/* millisecondsOverWhichToAverageSpeed */30000);
@@ -277,7 +277,7 @@ public class WindTest {
new BoatClassImpl("505", /* typicallyStartsUpwind */true), null));
final BoatClass boatClass = domainFactory.getOrCreateBoatClass("ESS40");
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl(
new RegattaImpl(EmptyRaceLogStore.INSTANCE, "Test Regatta", boatClass,
new RegattaImpl(EmptyRaceLogStore.INSTANCE, RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass,
/* trackedRegattaRegistry */ null, domainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null)),
new RaceDefinitionImpl("Test Race",
new CourseImpl("Test Course", Arrays.asList(new Waypoint[] { w1, w2, w3 })),
@@ -38,10 +38,10 @@ import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.GPSFixMoving;
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.GPSFixImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
import com.sap.sse.common.Util;
public class AbstractMockedRaceMarkPassingTest {
@@ -63,11 +63,12 @@ public class AbstractMockedRaceMarkPassingTest {
Waypoint w1 = new WaypointImpl(cp, PassingInstruction.Line);
Waypoint w2 = new WaypointImpl(m, PassingInstruction.Port);
Waypoint w3 = new WaypointImpl(cp, PassingInstruction.Line);
Regatta r = new RegattaImpl("regatta", new BoatClassImpl("boat", true), Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")),
final BoatClassImpl boatClass = new BoatClassImpl("boat", true);
Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")),
new ArrayList<String>(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5)));
Course course = new CourseImpl("course", Arrays.asList(w1, w2, w3));
RaceDefinition race = new RaceDefinitionImpl("Performance Race", course, new BoatClassImpl("boat", true), Arrays.asList(bob));
trackedRace = new DynamicTrackedRaceImpl(new TrackedRegattaImpl(r), race, new ArrayList<Sideline>(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000);
RaceDefinition race = new RaceDefinitionImpl("Performance Race", course, boatClass, Arrays.asList(bob));
trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl(r), race, new ArrayList<Sideline>(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000);
trackedRace.setStartTimeReceived(new MillisecondsTimePoint(System.currentTimeMillis() - 120000));
List<MillisecondsTimePoint> tps = Arrays.asList(new MillisecondsTimePoint(System.currentTimeMillis()), new MillisecondsTimePoint(System.currentTimeMillis() - 30000),
new MillisecondsTimePoint(System.currentTimeMillis() + 30000));
@@ -9,7 +9,6 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceColumnListener;
@@ -33,6 +32,7 @@ import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
@@ -46,6 +46,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
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.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
@@ -54,8 +55,8 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
@@ -369,12 +370,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public String getBaseName() {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<? extends Series> getSeries() {
// TODO Auto-generated method stub
@@ -453,9 +448,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public Event getEvent() {
public boolean useStartTimeInference() {
// TODO Auto-generated method stub
return null;
return false;
}
@Override
@@ -463,11 +458,17 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
}
@Override
public void setUseStartTimeInference(boolean useStartTimeInference) {
// TODO Auto-generated method stub
}
};
}
@Override
public Iterable<TrackedRace> getTrackedRaces() {
public Iterable<DynamicTrackedRace> getTrackedRaces() {
// TODO Auto-generated method stub
return null;
}
@@ -993,4 +994,16 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public void addRaceAbortedListener(RaceAbortedListener listener) {
// TODO Auto-generated method stub
}
@Override
public void onAbortedByRaceCommittee(Flags flag) {
// TODO Auto-generated method stub
}
}
@@ -34,6 +34,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.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -646,4 +647,10 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
}
@Override
public void addRaceAbortedListener(RaceAbortedListener listener) {
// TODO Auto-generated method stub
}
}
@@ -270,4 +270,12 @@ public interface DomainFactory {
List<Sideline> createSidelines(String raceMetadataString, Iterable<? extends TracTracControlPoint> allEventControlPoints);
/**
* When a tracked race has been created for tracking with the TracTrac adapter, change listeners have to be subscribed to
* the {@link TrackedRace} which will notify certain changes to the race's state to TracTrac. This includes the course layout,
* the start time and whether a race was aborted.
*/
void addTracTracUpdateHandlers(URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername,
String tracTracPassword, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace);
}
@@ -372,7 +372,7 @@ public class DomainFactoryImpl implements DomainFactory {
// This is particularly bad if a persistent regatta was loaded but a default regatta was accidentally created.
// Then, there is no way but restart the server to get rid of this stale cache entry here.
if (result == null) {
result = new RegattaImpl(raceLogStore, defaultRegattaNameAndBoatClass.getA(), boatClass, trackedRegattaRegistry,
result = new RegattaImpl(raceLogStore, RegattaImpl.getDefaultName(defaultRegattaNameAndBoatClass.getA(), boatClass.getName()), boatClass, trackedRegattaRegistry,
// use the low-point system as the default scoring scheme
getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), race.getId(), null);
regattaCache.put(key, result);
@@ -519,16 +519,8 @@ public class DomainFactoryImpl implements DomainFactory {
trackedRace = createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore, EmptyGPSFixStore.INSTANCE,
delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate);
logger.info("Added race " + raceDefinition + " to regatta " + trackedRegatta.getRegatta());
TracTracCourseDesignUpdateHandler courseDesignHandler = new TracTracCourseDesignUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
trackedRace.addCourseDesignChangedListener(courseDesignHandler);
TracTracStartTimeUpdateHandler startTimeHandler = new TracTracStartTimeUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
trackedRace.addStartTimeChangedListener(startTimeHandler);
addTracTracUpdateHandlers(tracTracUpdateURI, tracTracEventUuid, tracTracUsername, tracTracPassword,
raceDefinition, trackedRace);
raceCache.put(raceId, raceDefinition);
raceCache.notifyAll();
} else {
@@ -546,6 +538,23 @@ public class DomainFactoryImpl implements DomainFactory {
}
}
@Override
public void addTracTracUpdateHandlers(URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername,
String tracTracPassword, RaceDefinition raceDefinition, DynamicTrackedRace trackedRace) {
TracTracCourseDesignUpdateHandler courseDesignHandler = new TracTracCourseDesignUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
trackedRace.addCourseDesignChangedListener(courseDesignHandler);
TracTracStartTimeUpdateHandler startTimeHandler = new TracTracStartTimeUpdateHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
trackedRace.addStartTimeChangedListener(startTimeHandler);
TracTracRaceAbortedHandler raceAbortedHandler = new TracTracRaceAbortedHandler(
tracTracUpdateURI, tracTracUsername, tracTracPassword, tracTracEventUuid,
raceDefinition.getId());
trackedRace.addRaceAbortedListener(raceAbortedHandler);
}
private DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable<Sideline> sidelines, WindStore windStore, GPSFixStore gpsFixStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
return trackedRegatta.createTrackedRace(race, sidelines,
@@ -43,9 +43,6 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue<IControl, IP
private final IRace tractracRace;
final IControlPointPositionListener listener;
/**
* @param tractracRace TODO
*/
public MarkPositionReceiver(final DynamicTrackedRegatta trackedRegatta, IEvent tractracEvent,
IRace tractracRace, Simulator simulator, final DomainFactory domainFactory, IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber) {
super(domainFactory, tractracEvent, trackedRegatta, simulator, eventSubscriber, raceSubscriber);
@@ -220,13 +220,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
windStore, EmptyGPSFixStore.INSTANCE, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
raceDefinitionSetToUpdate);
TracTracCourseDesignUpdateHandler courseDesignHandler = new TracTracCourseDesignUpdateHandler(tracTracUpdateURI,
tracTracUsername, tracTracPassword,
getTracTracEvent().getId(), race.getId());
trackedRace.addCourseDesignChangedListener(courseDesignHandler);
TracTracStartTimeUpdateHandler startTimeHandler = new TracTracStartTimeUpdateHandler(tracTracUpdateURI,
tracTracUsername, tracTracPassword, getTracTracEvent().getId(), race.getId());
trackedRace.addStartTimeChangedListener(startTimeHandler);
getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, tracTracPassword, race, trackedRace);
if (!Activator.getInstance().isUseTracTracMarkPassings()) {
new MarkPassingCalculator(trackedRace, true);
}
@@ -1,12 +1,9 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.util.Collections;
import java.util.Set;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.tractrac.model.lib.api.event.IRace;
@@ -29,9 +26,8 @@ public class RaceHandleImpl implements RaceHandle {
}
@Override
public Set<RaceDefinition> getRaces() {
Set<RaceDefinition> result = Collections.singleton(domainFactory.getAndWaitForRaceDefinition(tractracRace.getId()));
return result;
public RaceDefinition getRace() {
return domainFactory.getAndWaitForRaceDefinition(tractracRace.getId());
}
@Override
@@ -45,13 +41,13 @@ public class RaceHandleImpl implements RaceHandle {
}
@Override
public Set<RaceDefinition> getRaces(long timeoutInMilliseconds) {
final Set<RaceDefinition> result;
public RaceDefinition getRace(long timeoutInMilliseconds) {
final RaceDefinition result;
final RaceDefinition raceDefinition = domainFactory.getAndWaitForRaceDefinition(tractracRace.getId(), timeoutInMilliseconds);
if (raceDefinition != null) { // may have time-outed
result = Collections.singleton(raceDefinition);
result = raceDefinition;
} else {
result = Collections.emptySet();
result = null;
}
return result;
}
@@ -0,0 +1,60 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.IOException;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
public class TracTracRaceAbortedHandler extends UpdateHandler implements RaceAbortedListener {
private final static String ACTION = "update_race_status";
private final static Logger logger = Logger.getLogger(TracTracRaceAbortedHandler.class.getName());
public TracTracRaceAbortedHandler(URI updateURI, String tracTracUsername, String tracTracPassword, Serializable tracTracEventId, Serializable raceId) {
super(updateURI, ACTION, tracTracUsername, tracTracPassword, tracTracEventId, raceId);
}
@Override
public void raceAborted(Flags flag) throws MalformedURLException, IOException {
if (!isActive()) {
logger.info("Not sending race abort notification to TracTrac because no URL has been provided.");
return;
}
final String raceStatus;
if (flag == Flags.AP) {
raceStatus = "POSTPONED";
} else {
raceStatus = "ABORTED";
}
Map<String, String> additionalArgs = new HashMap<>();
additionalArgs.put("race_status", raceStatus);
URL raceAbortedURL = buildUpdateURL(additionalArgs);
logger.info("Using " + raceAbortedURL.toString() + " for the race aborted notification!");
HttpURLConnection connection = (HttpURLConnection) raceAbortedURL.openConnection();
try {
setConnectionProperties(connection);
try {
checkAndLogUpdateResponse(connection);
} catch (ParseException e) {
e.printStackTrace();
}
} finally {
if (connection != null) {
connection.disconnect();
} else {
logger.severe("Connection to TracTrac race aborted URL " + raceAbortedURL.toString() + " could not be established");
}
}
}
}
@@ -14,6 +14,7 @@ import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
@@ -62,7 +63,7 @@ public class UpdateHandler {
}
}
protected URL buildUpdateURL(HashMap<String, String> additionalParameters) throws MalformedURLException, UnsupportedEncodingException {
protected URL buildUpdateURL(Map<String, String> additionalParameters) throws MalformedURLException, UnsupportedEncodingException {
String serverUpdateURI = this.updateURI.toString();
// make sure that the update URI always ends with a slash
if (!serverUpdateURI.endsWith("/")) {
@@ -1,6 +1,9 @@
package com.sap.sailing.domain.base;
import java.awt.Dimension;
import java.net.URL;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
@@ -46,5 +49,16 @@ public interface Event extends EventBase {
* leaderboard groups in <code>leaderboardGroups</code>.
*/
void setLeaderboardGroups(Iterable<LeaderboardGroup> leaderboardGroups);
/**
* For the images references by the image URLs in {@link #getImageURLs()}, {@link #getSponsorImageURLs()} and {@link #getLogoImageURL()}
* determines the image dimensions. The retrieval of the image size is started when the URL is added to this event, and the result is
* cached. Should the image change its size afterwards or change its availability status, this event won't notice unless the image is
* removed from this event and added again.<p>
*
* Note that exceptions may result should the image be unavailable. If the image URL leads to a document that is not an image,
* <code>null</code> will result.
*/
Dimension getImageSize(URL imageURL) throws InterruptedException, ExecutionException;
}
@@ -100,12 +100,6 @@ public interface Regatta extends Named, WithID {
RegattaIdentifier getRegattaIdentifier();
/**
* A regatta name may be composed, e.g., from an overall regatta name and the boat class name. A factory or constructor
* may require the base name to which the boat class name will be appended. This method emits the base name.
*/
String getBaseName();
/**
* Regattas may be constructed as implicit default regattas in which case they won't need to be stored
* durably and don't contain valuable information worth being preserved; or they are constructed explicitly
@@ -128,7 +122,30 @@ public interface Regatta extends Named, WithID {
RegattaAndRaceIdentifier getRaceIdentifier(RaceDefinition race);
/**
* @return the associated event. Can be null.
* When there is no race committee app in place and no operator is managing the race start times for this regatta,
* start times can optionally be inferred from the start mark passings by the {@link TrackedRace}s in this regatta.
* The default for this is <code>true</code>, particularly because the race committee app has not been used for all
* races, and start times need to be inferred for those.
* <p>
*
* This option should only be set to <code>false</code> if the race log is maintained thorougly for this regatta,
* either by a well-working race committee app or by an on-shore operator using the administration console to enter
* start times for all races of this regatta consistently.
* <p>
*
* Note that the effects of setting this option to <code>false</code> are severe when no start times are maintained.
* The races for which then no start time is entered will not be scored at all.
* <p>
*
* A discussion of this subject can also be found at <a
* href="http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=1994"
* >http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=1994</a>.
*/
Event getEvent();
boolean useStartTimeInference();
/**
* Updates the flag deciding whether to use start time inference. See {@link #useStartTimeInference()}.
*/
void setUseStartTimeInference(boolean useStartTimeInference);
}
@@ -1,12 +1,26 @@
package com.sap.sailing.domain.base.impl;
import java.awt.Dimension;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Regatta;
@@ -22,6 +36,9 @@ public class EventImpl extends EventBaseImpl implements Event {
private ConcurrentLinkedQueue<LeaderboardGroup> leaderboardGroups;
private transient ConcurrentHashMap<URL, Future<Dimension>> imageSizeFetchers;
private transient ExecutorService executor;
public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) {
this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id);
}
@@ -33,6 +50,8 @@ public class EventImpl extends EventBaseImpl implements Event {
super(name, startDate, endDate, venue, isPublic, id);
this.regattas = new HashSet<Regatta>();
this.leaderboardGroups = new ConcurrentLinkedQueue<>();
this.imageSizeFetchers = new ConcurrentHashMap<>();
this.executor = Executors.newCachedThreadPool();
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
@@ -40,6 +59,8 @@ public class EventImpl extends EventBaseImpl implements Event {
if (leaderboardGroups == null) {
leaderboardGroups = new ConcurrentLinkedQueue<>();
}
this.imageSizeFetchers = new ConcurrentHashMap<>();
this.executor = Executors.newCachedThreadPool();
}
@Override
@@ -81,4 +102,103 @@ public class EventImpl extends EventBaseImpl implements Event {
public boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
return leaderboardGroups.remove(leaderboardGroup);
}
private Future<Dimension> getOrCreateImageSizeCalculator(final URL imageURL) {
Future<Dimension> imageSizeFetcher = imageSizeFetchers.get(imageURL);
if (imageSizeFetcher == null) {
imageSizeFetcher = executor.submit(new Callable<Dimension>() {
@Override
public Dimension call() throws IOException {
Dimension result = null;
ImageInputStream in = null;
try {
URLConnection conn = imageURL.openConnection();
in = ImageIO.createImageInputStream(conn.getInputStream());
final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
if (readers.hasNext()) {
ImageReader reader = readers.next();
try {
reader.setInput(in);
result = new Dimension(reader.getWidth(0), reader.getHeight(0));
} finally {
reader.dispose();
}
}
} finally {
if (in != null) {
in.close();
}
}
return result;
}
});
imageSizeFetchers.put(imageURL, imageSizeFetcher);
}
return imageSizeFetcher;
}
@Override
public Dimension getImageSize(URL imageURL) throws InterruptedException, ExecutionException {
Future<Dimension> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL);
return imageSizeCalculator.get();
}
@Override
public void addImageURL(URL imageURL) {
super.addImageURL(imageURL);
refreshImageSizeFetcher(imageURL);
}
private void refreshImageSizeFetcher(URL imageURL) {
if (imageURL != null) {
removeImageSizeFetcher(imageURL);
getOrCreateImageSizeCalculator(imageURL);
}
}
@Override
public void removeImageURL(URL imageURL) {
super.removeImageURL(imageURL);
removeImageSizeFetcher(imageURL);
}
private void removeImageSizeFetcher(URL imageURL) {
if (imageURL != null) {
imageSizeFetchers.remove(imageURL);
}
}
@Override
public void setImageURLs(Iterable<URL> imageURLs) {
super.setImageURLs(imageURLs);
for (URL imageURL : imageURLs) {
refreshImageSizeFetcher(imageURL);
}
}
@Override
public void addSponsorImageURL(URL sponsorImageURL) {
super.addSponsorImageURL(sponsorImageURL);
refreshImageSizeFetcher(sponsorImageURL);
}
@Override
public void removeSponsorImageURL(URL sponsorImageURL) {
super.removeSponsorImageURL(sponsorImageURL);
removeImageSizeFetcher(sponsorImageURL);
}
@Override
public void setSponsorImageURLs(Iterable<URL> sponsorImageURLs) {
super.setSponsorImageURLs(sponsorImageURLs);
for (URL imageURL : sponsorImageURLs) {
refreshImageSizeFetcher(imageURL);
}
}
@Override
public void setLogoImageURL(URL logoImageURL) {
super.setLogoImageURL(logoImageURL);
refreshImageSizeFetcher(logoImageURL);
}
}
@@ -13,7 +13,6 @@ import java.util.logging.Logger;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
@@ -84,11 +83,16 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
*/
private final boolean persistent;
/**
* Defaults to <code>true</code>. See {@link Regatta#useStartTimeInference()}.
*/
private boolean useStartTimeInference;
/**
* Constructs a regatta with an empty {@link RaceLogStore}.
*/
public RegattaImpl(String baseName, BoatClass boatClass, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
this(EmptyRaceLogStore.INSTANCE, baseName, boatClass, series, persistent, scoringScheme, id, courseArea);
public RegattaImpl(String name, BoatClass boatClass, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
this(EmptyRaceLogStore.INSTANCE, name, boatClass, series, persistent, scoringScheme, id, courseArea, /* useStartTimeInference */ true);
}
/**
@@ -101,11 +105,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
* this column's series {@link Regatta}, respectively. If <code>null</code>, the re-association won't be
* carried out.
*/
public RegattaImpl(RaceLogStore raceLogStore, String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
this(raceLogStore, baseName, boatClass, Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME,
public RegattaImpl(RaceLogStore raceLogStore, String name, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
this(raceLogStore, name, boatClass, Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME,
/* isMedal */false, Collections
.singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList<String>(),
trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea);
trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea, /* useStartTimeInference */ true);
}
/**
@@ -113,8 +117,9 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
* all {@link Series} in this iterable will have their {@link Series#setRegatta(Regatta) regatta set} to
* this new regatta.
*/
public <S extends Series> RegattaImpl(RaceLogStore raceLogStore, String baseName, BoatClass boatClass, Iterable<S> series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea) {
super(getDefaultName(baseName, boatClass==null?null:boatClass.getName()));
public <S extends Series> RegattaImpl(RaceLogStore raceLogStore, String name, BoatClass boatClass, Iterable<S> series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, boolean useStartTimeInference) {
super(name);
this.useStartTimeInference = useStartTimeInference;
this.id = id;
this.raceLogStore = raceLogStore;
races = new HashSet<RaceDefinition>();
@@ -148,24 +153,13 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
new RaceLogOnRegattaIdentifier(this, raceColumn.getName())));
}
public static String getDefaultName(String baseName, String boatClassName) {
return baseName+(boatClassName==null?"":" ("+boatClassName+")");
}
@Override
public Serializable getId() {
return id;
}
@Override
public String getBaseName() {
String result;
if (boatClass == null) {
result = getName();
} else {
result = getName().substring(0, getName().length()-boatClass.getName().length()-3); // remove tralining boat class name and " (" and ")"
}
return result;
public static String getDefaultName(String baseName, String boatClassName) {
return baseName+(boatClassName==null?"":" ("+boatClassName+")");
}
@Override
@@ -417,6 +411,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
this.defaultCourseArea = newCourseArea;
}
@Override
public void setUseStartTimeInference(boolean useStartTimeInference) {
this.useStartTimeInference = useStartTimeInference;
}
@Override
public RegattaConfiguration getRegattaConfiguration() {
return configuration;
@@ -468,12 +467,6 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
registerRaceLogsOnRaceColumns(seriesToAdd);
}
@Override
public Event getEvent() {
// TODO Auto-generated method stub
return null;
}
@Override
public void removeSeries(Series series) {
Series existingSeries = getSeriesByName(series.getName());
@@ -499,4 +492,9 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
}
}
@Override
public boolean useStartTimeInference() {
return useStartTimeInference;
}
}
@@ -280,10 +280,6 @@ public interface Leaderboard extends LeaderboardBase {
/**
* Returns the total rank of the given competitor.
* @param competitor
* @param timePoint
* @return
* @throws NoWindException
*/
int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException;
@@ -105,6 +105,11 @@ public class LeaderboardCacheManager {
removeFromCache(leaderboard);
}
@Override
public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
removeFromCache(leaderboard);
}
@Override
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
removeFromCache(leaderboard);
@@ -347,6 +347,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void delayToLiveChanged(long delayToLiveInMillis) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
@@ -1287,7 +1292,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
} else {
result.discardThresholds = null;
}
// competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg
// Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep
// invalidating the ranks cache in TrackedLegImpl. Then problem then is that the sorting based on wind data is repeated for
// each competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg
// details are requested only once and pass them into getLeaderboardEntryDTO
final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>();
for (final RaceColumn raceColumn : this.getRaceColumns()) {
@@ -1300,7 +1307,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
trackedRace.getRace().getCourse().lockForRead();
try {
for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint));
legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache));
}
} finally {
trackedRace.getRace().getCourse().unlockAfterRead();
@@ -1679,7 +1686,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
if (legRanks != null) {
result.rank = legRanks.get(trackedLeg.getCompetitor());
} else {
result.rank = trackedLeg.getRank(timePoint);
result.rank = trackedLeg.getRank(timePoint, cache);
}
result.started = trackedLeg.hasStartedLeg(timePoint);
Speed velocityMadeGood;
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sse.common.Util;
public interface DynamicTrackedRace extends TrackedRace {
@@ -96,6 +97,8 @@ public interface DynamicTrackedRace extends TrackedRace {
void onCourseDesignChangedByRaceCommittee(CourseBase courseDesign);
void onStartTimeChangedByRaceCommittee(TimePoint newStartTime);
void onAbortedByRaceCommittee(Flags flag);
void invalidateStartTime();
@@ -7,6 +7,9 @@ import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
public interface DynamicTrackedRegatta extends TrackedRegatta {
DynamicTrackedRace getTrackedRace(RaceDefinition race);
@Override
Iterable<DynamicTrackedRace> getTrackedRaces();
/**
* Non-blocking call that returns <code>null</code> if no tracking information currently exists
@@ -0,0 +1,10 @@
package com.sap.sailing.domain.tracking;
import java.io.IOException;
import java.net.MalformedURLException;
import com.sap.sailing.domain.common.racelog.Flags;
public interface RaceAbortedListener {
void raceAborted(Flags flag) throws MalformedURLException, IOException;
}
@@ -32,6 +32,15 @@ public interface RaceChangeListener extends CourseListener {
void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage);
void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived);
/**
* Fired when the {@link TrackedRace#getStartOfRace() start of race} time point has changed for the tracked race
* observed by this listener. There can be several reasons for this to happen, among them the setting of the start
* time by the race committee app, or receiving a new start time from the tracking provider, or receiving a new
* start mark passing with start time inference enabled, or a switch of the flag determining whether start mark
* passing-based start time inference is active or not.
*/
void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace);
void delayToLiveChanged(long delayToLiveInMillis);
@@ -1,14 +1,12 @@
package com.sap.sailing.domain.tracking;
import java.util.Set;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
/**
* Gives clients a way to refer to a specific set of races even though the {@link RaceDefinition} objects may not have
* been created yet, but without exposing the tracking provider's internals. The {@link #getRaces()} call will block
* been created yet, but without exposing the tracking provider's internals. The {@link #getRace()} call will block
* until the {@link RaceDefinition}s have been created.
*
* @author Axel Uhl (d043530)
@@ -19,10 +17,10 @@ public interface RaceHandle {
Regatta getRegatta();
/**
* Fetch the race definitions. If the race definitions represented by this handle haven't been created yet, the call
* Fetch the race definitions. If the race definition represented by this handle hasn't been created yet, the call
* blocks until such a definition is provided by another call, usually by the {@link RaceCourseReceiver}.
*/
Set<RaceDefinition> getRaces();
RaceDefinition getRace();
/**
* Fetch the race definition. If the race definition represented by this handle hasn't been created yet, the call
@@ -32,7 +30,7 @@ public interface RaceHandle {
* <code>timeoutInMilliseconds</code> in case no modifications happen on the {@link DomainFactory}'s
* set of races during that time.
*/
Set<RaceDefinition> getRaces(long timeoutInMilliseconds);
RaceDefinition getRace(long timeoutInMilliseconds);
DynamicTrackedRegatta getTrackedRegatta();
@@ -16,7 +16,7 @@ import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
* tracker architectures may not be able to deliver all data for the {@link RaceDefinition} when created or started.
* Therefore, {@link #getRaces()} may return <code>null</code> if the race information hasn't been received by the
* tracker yet. Through the {@link RaceHandle} returned by {@link #getRacesHandle()} it is also possible to perform a
* {@link RaceHandle#getRaces() blocking get} for the races tracked by this tracker.
* {@link RaceHandle#getRace() blocking get} for the race tracked by this tracker.
* <p>
*
* The data received by the tracker is usually fed into {@link TrackedRace} objects that {@link TrackedRace#getRace()
@@ -9,5 +9,5 @@ import com.sap.sailing.domain.common.TimePoint;
public interface StartTimeChangedListener {
void startTimeChanged(TimePoint newTimePoint) throws MalformedURLException, IOException;
}
@@ -43,6 +43,13 @@ public interface TrackedLeg extends Serializable {
*/
LinkedHashMap<Competitor, Integer> getRanks(TimePoint timePoint);
/**
* Same as {@link #getRanks(TimePoint)}, only that a cache for wind and leg type
* and bearing data can be passed to avoid redundant calculations during a single
* round trip.
*/
LinkedHashMap<Competitor, Integer> getRanks(TimePoint timePoint, WindLegTypeAndLegBearingCache cache);
Bearing getLegBearing(TimePoint at);
/**
@@ -77,15 +84,22 @@ public interface TrackedLeg extends Serializable {
* returned. The distance returned from this method is always positive. See also {@link #getWindwardDistance}.
* @param at
* the wind estimation is performed for this point in time
* @param windPositionMode TODO
*/
Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException;
/**
* Same as {@link #getAbsoluteWindwardDistance(Position, Position, TimePoint, WindPositionMode)}, only that a cache
* for leg types, wind data and leg bearings can be passed to save the effort of redundant calculations of these
* values.
*/
Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache) throws NoWindException;
/**
* Same as {@link #getAbsoluteWindwardDistance(Position, Position, TimePoint, WindPositionMode)}, but this method considers the leg's
* direction and will return a negative distance if <code>pos1</code> is already "ahead" of <code>pos2</code> in the
* leg's direction, or a positive distance otherwise.
* @param windPositionMode TODO
*/
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException;
}
@@ -43,13 +43,22 @@ public interface TrackedLegOfCompetitor extends Serializable {
* Estimates how much the competitor still has to go to the end waypoint of this leg, projected onto the wind
* direction. If the competitor already finished this leg, a zero, non-<code>null</code> distance will result.
* If the competitor hasn't started the leg yet, the full leg distance is returned.
* @param windPositionMode TODO
*
* @throws NoWindException
* thrown in case no wind direction is known
*/
Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException;
/**
* Same as {@link #getWindwardDistanceToGo(TimePoint, WindPositionMode)}, only that a cache for wind and leg
* type / bearing can be passed.
*
* @throws NoWindException
* thrown in case no wind direction is known
*/
Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode legMiddle,
WindLegTypeAndLegBearingCache cache) throws NoWindException;
/**
* Computes an approximation for the average velocity made good (windward / leeward speed) of this leg's competitor at
* <code>timePoint</code>. If the competitor hasn't started the leg yet, <code>null</code> is returned. If the competitor
@@ -119,6 +128,12 @@ public interface TrackedLegOfCompetitor extends Serializable {
*/
int getRank(TimePoint timePoint);
/**
* Same as {@link #getRank(TimePoint)} with the additional option to provide a cache
* that can help avoid redundant calculations of wind and leg data.
*/
int getRank(TimePoint timePoint, WindLegTypeAndLegBearingCache cache);
/**
* Computes the gap in seconds to the leader / winner of this leg. Returns <code>null</code> in case this leg's
* competitor hasn't started the leg yet.
@@ -207,6 +222,13 @@ public interface TrackedLegOfCompetitor extends Serializable {
*/
Distance getWindwardDistanceToOverallLeader(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException;
/**
* Same as {@link #getWindwardDistanceToOverallLeader(TimePoint, WindPositionMode)}, only that a cache for leg type
* calculation is passed.
*/
Distance getWindwardDistanceToOverallLeader(TimePoint timePoint, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache) throws NoWindException;
/**
* Computes the average absolute cross track error for this leg. The cross track error for each fix is taken to be a
* positive number, thereby ignoring whether the competitor was left or right of the course middle line. If you
@@ -236,7 +258,9 @@ public interface TrackedLegOfCompetitor extends Serializable {
TrackedLeg getTrackedLeg();
/**
* Computes the angle between the competitors direction and the wind.
* Computes the angle between the competitors direction and the wind's "from" direction. The angle's direction is chosen such that
* it can be added to the boat's course over ground to arrive at the wind's {@link Wind#getFrom() "from"} direction. Example: wind
* from the north (0deg), boat's course over ground 90deg (moving east), then the bearing returned is -90deg.
*/
Bearing getBeatAngle(TimePoint at) throws NoWindException;
@@ -615,6 +615,8 @@ public interface TrackedRace extends Serializable {
void addStartTimeChangedListener(StartTimeChangedListener listener);
void addRaceAbortedListener(RaceAbortedListener listener);
/**
* Tells how far the given <code>competitor</code> was from the start line at the time point of the given seconds before the start.
* <p>
@@ -29,7 +29,7 @@ import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
public interface TrackedRegatta extends Serializable {
Regatta getRegatta();
Iterable<TrackedRace> getTrackedRaces();
Iterable<? extends TrackedRace> getTrackedRaces();
Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass);
@@ -72,5 +72,5 @@ public interface TrackedRegatta extends Serializable {
int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
void removeTrackedRace(RaceDefinition raceDefinition);
}
@@ -16,6 +16,10 @@ import com.sap.sailing.domain.tracking.Wind;
public abstract class AbstractRaceChangeListener implements RaceChangeListener {
@Override
public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
}
@@ -34,6 +34,7 @@ import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
@@ -45,6 +46,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
@@ -73,6 +75,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
private transient Set<CourseDesignChangedListener> courseDesignChangedListeners;
private transient Set<StartTimeChangedListener> startTimeChangedListeners;
private transient Set<RaceAbortedListener> raceAbortedListeners;
public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable<Sideline> sidelines,
WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
@@ -80,8 +83,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
super(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed,
delayForCacheInvalidationOfWindEstimation);
this.logListener = new DynamicTrackedRaceLogListener(this);
this.courseDesignChangedListeners = new HashSet<CourseDesignChangedListener>();
this.startTimeChangedListeners = new HashSet<StartTimeChangedListener>();
this.courseDesignChangedListeners = new HashSet<>();
this.startTimeChangedListeners = new HashSet<>();
this.raceAbortedListeners = new HashSet<>();
this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind();
if (!raceIsKnownToStartUpwind) {
Set<WindSource> windSourcesToExclude = new HashSet<WindSource>();
@@ -107,8 +111,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
ois.defaultReadObject();
listeners = new HashSet<RaceChangeListener>();
logListener = new DynamicTrackedRaceLogListener(this);
courseDesignChangedListeners = new HashSet<CourseDesignChangedListener>();
startTimeChangedListeners = new HashSet<StartTimeChangedListener>();
courseDesignChangedListeners = new HashSet<>();
startTimeChangedListeners = new HashSet<>();
raceAbortedListeners = new HashSet<>();
}
/**
@@ -147,7 +152,10 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void recordFix(Mark mark, GPSFix fix) {
getOrCreateTrack(mark).addGPSFix(fix);
if ((getStartOfTracking() == null || getStartOfTracking().compareTo(fix.getTimePoint()) <= 0) &&
(getEndOfTracking() == null || getEndOfTracking().compareTo(fix.getTimePoint()) >= 0)) {
getOrCreateTrack(mark).addGPSFix(fix);
}
}
@Override
@@ -251,6 +259,16 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
getListeners().add(listener);
}
}
@Override
public void invalidateStartTime() {
TimePoint oldStartOfRace = getStartOfRace();
super.invalidateStartTime();
TimePoint newStartOfRace = getStartOfRace();
if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
}
}
@Override
public void addListener(RaceChangeListener listener, final boolean notifyAboutWindFixesAlreadyLoaded,
@@ -362,6 +380,21 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
}
}
private void notifyListenersStartOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
RaceChangeListener[] listeners;
synchronized (getListeners()) {
listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]);
}
for (RaceChangeListener listener : listeners) {
try {
listener.startOfRaceChanged(oldStartOfRace, newStartOfRace);
} catch (Exception e) {
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
logger.log(Level.SEVERE, "notifyListenersStartOfRaceChanged(TimePoint, TimePoint)", e);
}
}
}
private void notifyListenersWaypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
RaceChangeListener[] listeners;
synchronized (getListeners()) {
@@ -548,6 +581,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
public void updateMarkPassings(Competitor competitor, Iterable<MarkPassing> markPassings) {
Map<Waypoint, MarkPassing> oldMarkPassings = new HashMap<Waypoint, MarkPassing>();
MarkPassing oldStartMarkPassing = null;
TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after updating the mark passings
boolean requiresStartTimeUpdate = true;
final NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
lockForRead(markPassingsForCompetitor);
@@ -622,11 +656,17 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
triggerManeuverCacheRecalculation(competitor);
// update the race times like start, end and the leg times
if (requiresStartTimeUpdate) {
TimePoint interimsStartOfRace = getStartOfRace();
invalidateStartTime();
TimePoint newStartOfRace = getStartOfRace();
if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
// invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it already saw the new
// start of race time; we have to throw the notification here:
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
}
}
invalidateMarkPassingTimes();
invalidateEndTime();
// notify *after* all mark passings have been re-established; should avoid flicker
notifyListeners(competitor, oldMarkPassings, markPassings);
}
@@ -676,10 +716,14 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void setStartTimeReceived(TimePoint startTimeReceived) {
if ((startTimeReceived == null) != (getStartTimeReceived() == null)
|| (startTimeReceived != null && !startTimeReceived.equals(getStartTimeReceived()))) {
if (!Util.equalsWithNull(startTimeReceived, getStartTimeReceived())) {
TimePoint oldStartOfRace = getStartOfRace();
super.setStartTimeReceived(startTimeReceived);
notifyListenersRaceTimesChanged(getStartOfTracking(), getEndOfTracking(), getStartTimeReceived());
TimePoint newStartOfRace = getStartOfRace();
if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
}
}
}
@@ -851,7 +895,18 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
startTimeChangedListener.startTimeChanged(newStartTime);
}
} catch (IOException e) {
e.printStackTrace();
logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e);
}
}
@Override
public void onAbortedByRaceCommittee(Flags flag) {
try {
for (RaceAbortedListener raceAbortedListener : raceAbortedListeners) {
raceAbortedListener.raceAborted(flag);
}
} catch (IOException e) {
logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e);
}
}
@@ -859,6 +914,12 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
public void addStartTimeChangedListener(StartTimeChangedListener listener) {
this.startTimeChangedListeners.add(listener);
}
@Override
public void addRaceAbortedListener(RaceAbortedListener listener) {
this.raceAbortedListeners.add(listener);
}
/**
* @return The Bearing of a line starting at <code>w</code> that needs to be crossed to pass a mark.
*/
@@ -10,9 +10,11 @@ import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.analyzing.impl.AbortingFlagFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.WindFixesFinder;
@@ -34,6 +36,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
private LastPublishedCourseDesignFinder courseDesignFinder;
private StartTimeFinder startTimeFinder;
private AbortingFlagFinder abortingFlagFinder;
public DynamicTrackedRaceLogListener(DynamicTrackedRace trackedRace) {
this.trackedRace = trackedRace;
@@ -49,6 +52,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
trackedRace.invalidateEndTime();
courseDesignFinder = new LastPublishedCourseDesignFinder(raceLog);
startTimeFinder = new StartTimeFinder(raceLog);
abortingFlagFinder = new AbortingFlagFinder(raceLog);
initializeWindTrack(raceLog);
analyze();
}
@@ -129,9 +133,14 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
@Override
public void visit(RaceLogPassChangeEvent event) {
trackedRace.invalidateStartTime();
trackedRace.invalidateStartTime(); // this will notify RaceStateListeners in case the start time changes by the event
/* reset start time */
trackedRace.onStartTimeChangedByRaceCommittee(null);
RaceLogFlagEvent abortingFlag = abortingFlagFinder.analyze();
if (abortingFlag != null) {
// previous pass was aborted; notify TracTrac
trackedRace.onAbortedByRaceCommittee(abortingFlag.getUpperFlag());
}
}
@Override
@@ -149,5 +158,4 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
// add the wind fix to the race committee WindTrack
trackedRace.recordWind(event.getWindFix(), raceCommitteeWindSource);
}
}
@@ -21,6 +21,12 @@ public class DynamicTrackedRegattaImpl extends TrackedRegattaImpl implements Dyn
return (DynamicTrackedRace) super.getTrackedRace(race);
}
@SuppressWarnings("unchecked") // the tracked races of a dynamic tracked regatta are always DynamicTrackedRace objects; see also getTrackedRace(RaceDefinition)
@Override
public Iterable<DynamicTrackedRace> getTrackedRaces() {
return (Iterable<DynamicTrackedRace>) super.getTrackedRaces();
}
@Override
public DynamicTrackedRace getExistingTrackedRace(RaceDefinition race) {
return (DynamicTrackedRace) super.getExistingTrackedRace(race);
@@ -11,6 +11,7 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -44,11 +45,12 @@ public class RaceRankComparator implements Comparator<Competitor> {
this.timePoint = timePoint;
this.markPassingWithTimePoint = new DummyMarkPassingWithTimePointOnly(timePoint);
this.windwardDistanceToGoInLegCache = new HashMap<Competitor, Distance>();
LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
final TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, timePoint);
if (trackedLegOfCompetitor != null) {
windwardDistanceToGoInLegCache.put(competitor, trackedLegOfCompetitor.getWindwardDistanceToGo(timePoint,
WindPositionMode.LEG_MIDDLE));
WindPositionMode.LEG_MIDDLE, cache));
}
}
}
@@ -1,20 +1,15 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.util.impl.LockUtil;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
import com.sap.sse.common.Util.Pair;
/**
@@ -27,15 +22,18 @@ import com.sap.sse.common.Util.Pair;
*/
public class ShortTimeWindCache {
private static final Logger logger = Logger.getLogger(ShortTimeWindCache.class.getName());
private final NamedReentrantReadWriteLock lock;
private final ConcurrentHashMap<Pair<Position, TimePoint>, Wind> cache;
/**
* The keys of {@link #cache} in the order in which to invalidate them, keyed by the time they were entered into the cache.
*/
private final LinkedList<Pair<Long, Pair<Position, TimePoint>>> order;
private final ConcurrentLinkedDeque<Pair<Long, Pair<Position, TimePoint>>> order;
/**
* Creation and removal / cancellation of the timer is synchronized using {@link #order}.
*/
private Timer timer;
private final long preserveHowManyMilliseconds;
private final TrackedRaceImpl trackedRace;
@@ -46,34 +44,15 @@ public class ShortTimeWindCache {
@Override
public void run() {
long oldestToKeep = System.currentTimeMillis() - preserveHowManyMilliseconds;
Set<Pair<Position, TimePoint>> remove = new HashSet<>();
LockUtil.lockForRead(lock);
try {
if (!order.isEmpty()) {
Iterator<Pair<Long, Pair<Position, TimePoint>>> i = order.iterator();
while (i.hasNext()) {
Pair<Long, Pair<Position, TimePoint>> next = i.next();
if (next.getA() < oldestToKeep) {
remove.add(next.getB());
} else {
break;
}
}
} else {
Pair<Long, Pair<Position, TimePoint>> next;
while ((next = order.pollFirst()) != null && next.getA() < oldestToKeep) {
cache.remove(next.getB());
}
synchronized (order) {
if (order.isEmpty()) {
cancel();
timer = null;
}
} finally {
LockUtil.unlockAfterRead(lock);
}
LockUtil.lockForWrite(lock);
try {
for (Pair<Position, TimePoint> keyToRemove : remove) {
cache.remove(keyToRemove);
order.removeFirst();
}
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
}
@@ -82,52 +61,33 @@ public class ShortTimeWindCache {
this.trackedRace = trackedRace;
this.preserveHowManyMilliseconds = preserveHowManyMilliseconds;
cache = new ConcurrentHashMap<>();
order = new LinkedList<>();
lock = new NamedReentrantReadWriteLock(getClass().getSimpleName()+" for "+trackedRace.getRace(), /* fair */ true);
order = new ConcurrentLinkedDeque<>();
}
public void add(Position position, TimePoint timePoint, Wind wind) {
LockUtil.lockForWrite(lock);
try {
final Pair<Position, TimePoint> key = new Pair<Position, TimePoint>(position, timePoint);
cache.put(key, wind);
boolean orderEmpty = order.isEmpty();
final Pair<Position, TimePoint> key = new Pair<Position, TimePoint>(position, timePoint);
cache.put(key, wind);
boolean orderEmpty = order.isEmpty();
synchronized (order) {
order.add(new Pair<Long, Pair<Position, TimePoint>>(System.currentTimeMillis(), key));
if (orderEmpty) {
ensureTimerIsRunning();
}
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
public Wind getWind(Position position, TimePoint timePoint) {
Wind wind;
final Pair<Position, TimePoint> key = new Pair<>(position, timePoint);
LockUtil.lockForRead(lock);
try {
wind = cache.get(key);
} finally {
LockUtil.unlockAfterRead(lock);
}
wind = cache.get(key);
if (wind == null) {
LockUtil.lockForWrite(lock);
try {
wind = cache.get(key); // try again, now holding the write lock
if (wind == null) {
misses++;
if (misses % 100000l == 0 && logger.isLoggable(Level.FINE)) {
logger.fine("hits: "+hits+", misses: "+misses);
}
wind = trackedRace.getWindUncached(position, timePoint);
if (wind != null) {
add(position, timePoint, wind);
}
} else {
hits++;
}
} finally {
LockUtil.unlockAfterWrite(lock);
misses++;
if (misses % 100000l == 0 && logger.isLoggable(Level.FINE)) {
logger.fine("hits: " + hits + ", misses: " + misses);
}
wind = trackedRace.getWindUncached(position, timePoint);
if (wind != null) {
add(position, timePoint, wind);
}
} else {
hits++;
@@ -671,6 +671,11 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
}
}
}
@Override
public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
// no action required; we're calculating based on each competitor's individual start time, not the start of race
}
@Override
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
@@ -95,6 +95,10 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
* consider the order of the boats not currently in this leg, too.
*/
protected List<TrackedLegOfCompetitor> getCompetitorTracksOrderedByRank(TimePoint timePoint) {
return getCompetitorTracksOrderedByRank(timePoint, new NoCachingWindLegTypeAndLegBearingCache());
}
List<TrackedLegOfCompetitor> getCompetitorTracksOrderedByRank(TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
List<TrackedLegOfCompetitor> rankedCompetitorList;
synchronized (competitorTracksOrderedByRank) {
rankedCompetitorList = competitorTracksOrderedByRank.get(timePoint);
@@ -111,7 +115,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
// synchronized, usually by read-write locks, so there is no major difference in synchronization issues
// an the asynchronous nature of how the data is being received
// TODO See bug 469; competitors already disqualified may need to be ranked worst
Collections.sort(rankedCompetitorList, new WindwardToGoComparator(this, timePoint));
Collections.sort(rankedCompetitorList, new WindwardToGoComparator(this, timePoint, cache));
rankedCompetitorList = Collections.unmodifiableList(rankedCompetitorList);
synchronized (competitorTracksOrderedByRank) {
competitorTracksOrderedByRank.put(timePoint, rankedCompetitorList);
@@ -127,7 +131,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
@Override
public LinkedHashMap<Competitor, Integer> getRanks(TimePoint timePoint) {
List<TrackedLegOfCompetitor> orderedTrackedLegsOfCompetitors = getCompetitorTracksOrderedByRank(timePoint);
return getRanks(timePoint, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public LinkedHashMap<Competitor, Integer> getRanks(TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
List<TrackedLegOfCompetitor> orderedTrackedLegsOfCompetitors = getCompetitorTracksOrderedByRank(timePoint, cache);
LinkedHashMap<Competitor, Integer> result = new LinkedHashMap<Competitor, Integer>();
int i=1;
for (TrackedLegOfCompetitor tloc : orderedTrackedLegsOfCompetitors) {
@@ -298,6 +307,10 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
public void delayToLiveChanged(long delayToLiveInMillis) {
}
@Override
public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
}
@Override
public void waypointsMayHaveChanges() {
clearCaches();
@@ -338,6 +351,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
@Override
public Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
return getAbsoluteWindwardDistance(pos1, pos2, at, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at,
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException {
final Distance preResult = getWindwardDistance(pos1, pos2, at, windPositionMode);
final Distance result;
if (preResult.getMeters() >= 0) {
@@ -172,14 +172,16 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
return track.getMaximumSpeedOverGround(legStart.getTimePoint(), to);
}
@Override
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException {
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache) throws NoWindException {
if (hasFinishedLeg(timePoint)) {
return Distance.NULL;
} else {
Distance result = null;
for (Mark mark : getLeg().getTo().getMarks()) {
Distance d = getWindwardDistanceTo(mark, timePoint, windPositionMode);
Distance d = getWindwardDistanceTo(mark, timePoint, windPositionMode, cache);
if (result == null || d != null && d.compareTo(result) < 0) {
result = d;
}
@@ -188,13 +190,23 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
}
@Override
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException {
return getWindwardDistanceToGo(timePoint, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
}
/**
* If the current {@link #getLeg() leg} is +/- {@link #UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees collinear with the
* wind's bearing, the competitor's position is projected onto the line crossing <code>mark</code> in the wind's
* bearing, and the distance from the projection to the <code>mark</code> is returned. Otherwise, it is assumed that
* the leg is neither an upwind nor a downwind leg, and hence the true distance to <code>mark</code> is returned.
* the leg is neither an upwind nor a downwind leg, and hence the true distance to <code>mark</code> is returned. A
* cache for wind and leg type / bearing can be passed to avoid their redundant calculation during a single
* round-trip.
*
* @throws NoWindException
*/
private Distance getWindwardDistanceTo(Mark mark, TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
private Distance getWindwardDistanceTo(Mark mark, TimePoint at, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache) throws NoWindException {
Position estimatedPosition = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false);
if (!hasStartedLeg(at) || estimatedPosition == null) {
// covers the case with no fixes for this leg yet, also if the mark passing has already been received
@@ -208,7 +220,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
if (estimatedMarkPosition == null) {
return null;
}
return getTrackedLeg().getAbsoluteWindwardDistance(estimatedPosition, estimatedMarkPosition, at, windPositionMode);
return getTrackedLeg().getAbsoluteWindwardDistance(estimatedPosition, estimatedMarkPosition, at, windPositionMode, cache);
}
/**
@@ -264,9 +276,14 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
*/
@Override
public int getRank(TimePoint timePoint) {
return getRank(timePoint, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public int getRank(TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
int result = 0;
if (hasStartedLeg(timePoint)) {
List<TrackedLegOfCompetitor> competitorTracksByRank = getTrackedLeg().getCompetitorTracksOrderedByRank(timePoint);
List<TrackedLegOfCompetitor> competitorTracksByRank = getTrackedLeg().getCompetitorTracksOrderedByRank(timePoint, cache);
result = competitorTracksByRank.indexOf(this)+1;
}
return result;
@@ -363,6 +380,11 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
@Override
public Distance getWindwardDistanceToOverallLeader(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException {
return getWindwardDistanceToOverallLeader(timePoint, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
}
@Override
public Distance getWindwardDistanceToOverallLeader(TimePoint timePoint, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException {
// FIXME bug 607 it seems the following fetches the leader of this leg, not the overall leader; validate!!! Use getTrackedRace().getRanks() instead
Competitor leader = getTrackedRace().getOverallLeader(timePoint);
TrackedLegOfCompetitor leaderLeg = getTrackedRace().getCurrentLeg(leader, timePoint);
@@ -384,14 +406,14 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
// add distance to next mark
Position nextMarkPosition = getTrackedRace().getApproximatePosition(leg.getTo(), timePoint);
Distance distanceToNextMark = getTrackedRace().getTrackedLeg(leg)
.getAbsoluteWindwardDistance(currentPosition, nextMarkPosition, timePoint, windPositionMode);
.getAbsoluteWindwardDistance(currentPosition, nextMarkPosition, timePoint, windPositionMode, cache);
result = new MeterDistance(result.getMeters() + distanceToNextMark.getMeters());
currentPosition = nextMarkPosition;
} else {
// we're now in the same leg with leader; compute windward distance to leader
result = new MeterDistance(result.getMeters()
+ getTrackedRace().getTrackedLeg(leg)
.getAbsoluteWindwardDistance(currentPosition, leaderPosition, timePoint, windPositionMode)
.getAbsoluteWindwardDistance(currentPosition, leaderPosition, timePoint, windPositionMode, cache)
.getMeters());
break;
}
@@ -820,18 +842,14 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
public Bearing getBeatAngle(TimePoint at, WindLegTypeAndLegBearingCache cache) throws NoWindException {
Bearing beatAngle = null;
Bearing projectToBearing;
if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) {
Wind wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
if (wind == null) {
throw new NoWindException("Need at least wind direction to determine windward speed");
}
projectToBearing = wind.getBearing();
} else {
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
Wind wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
if (wind == null) {
throw new NoWindException("Need at least wind direction to determine windward speed");
}
projectToBearing = wind.getFrom();
SpeedWithBearing speed = getSpeedOverGround(at);
if (speed != null) {
beatAngle = projectToBearing.getDifferenceTo(speed.getBearing());
beatAngle = speed.getBearing().getDifferenceTo(projectToBearing);
}
return beatAngle;
}
@@ -49,6 +49,7 @@ import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.BearingChangeAnalyzer;
import com.sap.sailing.domain.common.CourseChange;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Duration;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
@@ -590,9 +591,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// startTimeReceived; if so, return an adjusted, later start time.
// If no official start time was received, try to estimate the start time using the mark passings for
// the start line.
final Waypoint firstWaypoint = getRace().getCourse().getFirstWaypoint();
if (firstWaypoint != null) {
final Waypoint firstWaypoint;
if (getTrackedRegatta().getRegatta().useStartTimeInference() && (firstWaypoint = getRace().getCourse().getFirstWaypoint()) != null) {
if (startTimeReceived != null) {
// plausibility check for start time received, based on start mark passings; if no boat started within
// a grace period of MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS after the start time
// received then the startTimeReceived is believed to be wrong
TimePoint timeOfFirstMarkPassing = getFirstPassingTime(firstWaypoint);
if (timeOfFirstMarkPassing != null) {
long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassing.asMillis()
@@ -667,11 +671,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return timeOfFirstMarkPassing;
}
/**
* Determines the largest group of competitors that started within a one-minute time period and returns the time
* point of the earliest start mark passing within that group.
*/
private TimePoint calculateStartOfRaceFromMarkPassings(NavigableSet<MarkPassing> markPassings,
Iterable<Competitor> competitors) {
TimePoint startOfRace = null;
// Find the first mark passing within the largest cluster crossing the line within one minute.
final long ONE_MINUTE_IN_MILLIS = 60 * 1000;
lockForRead(markPassings);
try {
if (markPassings != null) {
@@ -692,8 +699,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
startOfLargestGroupSoFar = currentMarkPassing;
largestStartGroupWithinOneMinuteSize = 1;
} else {
if (currentMarkPassing.getTimePoint().asMillis()
- candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() <= ONE_MINUTE_IN_MILLIS) {
if (candidateForStartOfLargestGroupSoFar.getTimePoint().until(currentMarkPassing.getTimePoint()).compareTo(Duration.ONE_MINUTE) <= 0) {
// currentMarkPassing is within one minute of candidateForStartOfLargestGroupSoFar; extend
// candidate group...
candiateGroupSize++;
@@ -704,14 +710,10 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
} else {
// currentMarkPassing is more than a minute after candidateForStartOfLargestGroupSoFar;
// advance
// candidateForStartOfLargestGroupSoFar and reduce group size counter, until
// candidateForStartOfLargestGroupSoFar
// is again within the one-minute interval; may catch up all the way to currentMarkPassing
// if that was
// more than a minute after its predecessor
while (currentMarkPassing.getTimePoint().asMillis()
- candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() > ONE_MINUTE_IN_MILLIS) {
// advance candidateForStartOfLargestGroupSoFar and reduce group size counter, until
// candidateForStartOfLargestGroupSoFar is again within the one-minute interval; may catch
// up all the way to currentMarkPassing if that was more than a minute after its predecessor
while (candidateForStartOfLargestGroupSoFar.getTimePoint().until(currentMarkPassing.getTimePoint()).compareTo(Duration.ONE_MINUTE) > 0) {
candidateForStartOfLargestGroupSoFar = markPassings
.higher(candidateForStartOfLargestGroupSoFar);
candiateGroupSize--;
@@ -1041,8 +1043,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
try {
if (rankedCompetitors == null) {
rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the
// write
// lock after updating the cache
// write lock after updating the cache
if (rankedCompetitors == null) {
RaceRankComparator comparator = new RaceRankComparator(this, timePoint);
rankedCompetitors = new ArrayList<Competitor>();
@@ -36,7 +36,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
private final Map<RaceDefinition, TrackedRace> trackedRaces;
private final Map<BoatClass, Collection<TrackedRace>> trackedRacesByBoatClass;
private transient Set<RaceListener> raceListeners;
public TrackedRegattaImpl(Regatta regatta) {
super();
this.regatta = regatta;
@@ -118,7 +118,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
}
@Override
public Iterable<TrackedRace> getTrackedRaces() {
public Iterable<? extends TrackedRace> getTrackedRaces() {
return trackedRaces.values();
}
@@ -13,6 +13,7 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
/**
@@ -26,11 +27,13 @@ public class WindwardToGoComparator implements Comparator<TrackedLegOfCompetitor
private final TrackedLeg trackedLeg;
private final TimePoint timePoint;
private final Map<TrackedLegOfCompetitor, Distance> wwdtgCache;
private final WindLegTypeAndLegBearingCache windAndLegTypeCache;
public WindwardToGoComparator(TrackedLeg trackedLeg, TimePoint timePoint) {
public WindwardToGoComparator(TrackedLeg trackedLeg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
this.trackedLeg = trackedLeg;
this.timePoint = timePoint;
wwdtgCache = new HashMap<TrackedLegOfCompetitor, Distance>();
windAndLegTypeCache = cache;
}
@Override
@@ -80,7 +83,7 @@ public class WindwardToGoComparator implements Comparator<TrackedLegOfCompetitor
private Distance getWindwardDistanceToGo(TrackedLegOfCompetitor o1) throws NoWindException {
Distance result = wwdtgCache.get(o1);
if (result == null) {
result = o1.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE);
result = o1.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE, windAndLegTypeCache);
wwdtgCache.put(o1, result);
}
return result;
@@ -36,6 +36,7 @@
<import plugin="org.apache.felix.gogo.command"/>
<import plugin="org.apache.felix.gogo.runtime"/>
<import plugin="org.apache.felix.gogo.shell"/>
<import plugin="org.eclipse.jetty.jmx"/>
</requires>
<plugin
@@ -76,7 +76,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase {
public void onSuccess(Util.Pair<String, List<TracTracRaceRecordDTO>> result) {
System.out.println("Listed races.");
for (TracTracRaceRecordDTO rr : result.getB()){
if (rr.name.equals(TRACKED_RACE)){
if (rr.getName().equals(TRACKED_RACE)){
rrDao = rr;
}
}
@@ -8,7 +8,6 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceColumnListener;
@@ -32,6 +31,7 @@ import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
@@ -45,6 +45,7 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
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.RaceAbortedListener;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.RaceListener;
import com.sap.sailing.domain.tracking.StartTimeChangedListener;
@@ -53,8 +54,8 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
@@ -369,12 +370,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public String getBaseName() {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<? extends Series> getSeries() {
// TODO Auto-generated method stub
@@ -454,9 +449,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public Event getEvent() {
public boolean useStartTimeInference() {
// TODO Auto-generated method stub
return null;
return false;
}
@Override
@@ -464,11 +459,17 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
}
@Override
public void setUseStartTimeInference(boolean useStartTimeInference) {
// TODO Auto-generated method stub
}
};
}
@Override
public Iterable<TrackedRace> getTrackedRaces() {
public Iterable<DynamicTrackedRace> getTrackedRaces() {
// TODO Auto-generated method stub
return null;
}
@@ -994,4 +995,16 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public void addRaceAbortedListener(RaceAbortedListener listener) {
// TODO Auto-generated method stub
}
@Override
public void onAbortedByRaceCommittee(Flags flag) {
// TODO Auto-generated method stub
}
}
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
@@ -34,8 +35,8 @@ public class PingMarkViaRaceLogTest {
service.getMongoObjectFactory().getDatabase().dropDatabase();
Series series = new SeriesImpl("series", false, Collections.singletonList(fleet),
Collections.singletonList(columnName), service);
Regatta regatta = service.createRegatta("regatta", "Laser", UUID.randomUUID(), Collections.<Series>singletonList(series),
false, new HighPoint(), UUID.randomUUID());
Regatta regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", UUID.randomUUID(), Collections.<Series>singletonList(series),
false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true);
RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {});
MarkDTO mark = new MarkDTO("mark", "mark");
@@ -1,4 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
@@ -29,6 +29,10 @@
margin: 9px 12px 0 0;
}
.viewerToolbar-settingsButton:last-child {
margin: 0 0px 0 1px;
}
.viewerToolbar-settingsButton {
background: url(images/settings_white.png), url(images/btn-cta-orangeleft.png);
background-position: center, center 0;
@@ -3,8 +3,14 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ListBox;
import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
@@ -12,7 +18,9 @@ import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceRecordDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sse.common.Util;
public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel {
protected final TrackedRacesListComposite trackedRacesListComposite;
@@ -64,6 +72,9 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel
return availableRegattasListBox;
}
/**
* @return <code>null</code> in case of the "Default Regatta" choice
*/
public RegattaDTO getSelectedRegatta() {
RegattaDTO result = null;
int selIndex = this.availableRegattasListBox.getSelectedIndex();
@@ -80,4 +91,78 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel
return result;
}
protected boolean checkBoatClassOK(RegattaDTO selectedRegatta, Iterable<? extends RaceRecordDTO> selectedRaces) {
final boolean result;
if (selectedRegatta != null) {
List<RaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<>();
// show an error and don't load anything if the boat class of any race doesn't match the boat class of the selected regatta;
// show the user the names of the offending races
for (RaceRecordDTO race : selectedRaces) {
if (!checkBoatClassMatch(race.getBoatClassNames(), selectedRegatta)) {
racesWithNotMatchingBoatClasses.add(race);
}
}
if (!racesWithNotMatchingBoatClasses.isEmpty()) {
StringBuilder builder = new StringBuilder(100 + racesWithNotMatchingBoatClasses.size() * 30);
builder.append(stringMessages
.boatClassDoesNotMatchSelectedRegatta(selectedRegatta.boatClass == null ? ""
: selectedRegatta.boatClass.getName()));
builder.append("\n\n");
builder.append(stringMessages.races());
builder.append("\n");
for (RaceRecordDTO record: racesWithNotMatchingBoatClasses) {
builder.append(record.getName());
builder.append(" (");
builder.append(record.getBoatClassNames());
builder.append(")");
builder.append("\n");
}
Window.alert(builder.toString());
result = false;
} else {
result = true;
}
} else {
// "Default Regatta" selected; check if for races for which no previous regatta assignment is known there is
// another regatta with the same boat class into which the user may want to load
Map<RaceRecordDTO, Set<RegattaDTO>> existingRegattasWithMatchingBoatClassPerRace = new HashMap<>();
for (RaceRecordDTO race : selectedRaces) {
if (!race.hasRememberedRegatta()) {
Set<RegattaDTO> existingRegattasWithMatchingBoatClass = new LinkedHashSet<>();
for (RegattaDTO regatta : getAvailableRegattas()) {
if (checkBoatClassMatch(race.getBoatClassNames(), regatta)) {
existingRegattasWithMatchingBoatClass.add(regatta);
}
}
if (!existingRegattasWithMatchingBoatClass.isEmpty()) {
existingRegattasWithMatchingBoatClassPerRace.put(race, existingRegattasWithMatchingBoatClass);
}
}
}
if (!existingRegattasWithMatchingBoatClassPerRace.isEmpty()) {
StringBuilder builder = new StringBuilder(100 + existingRegattasWithMatchingBoatClassPerRace.size() * 30);
builder.append(stringMessages.regattaExistForSelectedBoatClass());
builder.append("\n\n");
builder.append(stringMessages.races());
builder.append("\n");
for (Entry<RaceRecordDTO, Set<RegattaDTO>> e : existingRegattasWithMatchingBoatClassPerRace.entrySet()) {
builder.append(e.getKey().getName());
builder.append(" (");
builder.append(e.getKey().getBoatClassNames());
builder.append("): ");
builder.append(e.getValue());
builder.append("\n");
}
result = Window.confirm(builder.toString());
} else {
result = true;
}
}
return result;
}
private boolean checkBoatClassMatch(Iterable<String> boatClassNames, RegattaDTO selectedRegatta) {
return Util.contains(boatClassNames, selectedRegatta.boatClass.getName());
}
}
@@ -115,7 +115,7 @@ public class AdminConsoleEntryPoint extends AbstractEntryPoint implements Regatt
addToTabPanel(tabPanel, user, regattaManagementPanel, stringMessages.regattas(), AdminConsoleFeatures.MANAGE_REGATTAS);
regattasDisplayers.add(regattaManagementPanel);
TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService,
AbstractEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService,
this, this, stringMessages);
tractracEventManagementPanel.ensureDebugId("TracTracEventManagement");
addToTabPanel(tabPanel, user, tractracEventManagementPanel, stringMessages.tracTracEvents(), AdminConsoleFeatures.MANAGE_TRACKED_RACES);
@@ -1,18 +1,18 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.user.datepicker.client.DateBox;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
public class EventCreateDialog extends EventDialog {
public EventCreateDialog(Collection<EventDTO> existingEvents, StringMessages stringConstants, DialogCallback<EventDTO> callback) {
super(new EventParameterValidator(stringConstants, existingEvents), stringConstants, callback);
super(new EventParameterValidator(stringConstants, existingEvents), stringConstants,
/* leaderboardGroups */ Collections.<LeaderboardGroupDTO>emptyList(), callback);
nameEntryField = createTextBox(null);
nameEntryField.setVisibleLength(50);
descriptionEntryField = createTextArea(null);
@@ -21,10 +21,11 @@ public class EventCreateDialog extends EventDialog {
descriptionEntryField.getElement().getStyle().setProperty("resize", "none");
venueEntryField = createTextBox(null);
venueEntryField.setVisibleLength(35);
startDateBox = createDateBox(12);
startDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT)));
endDateBox = createDateBox(12);
endDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT)));
final Date now = new Date();
startDateBox = createDateTimeBox(now);
startDateBox.setFormat("dd/mm/yyyy hh:ii");
endDateBox = createDateTimeBox(now);
endDateBox.setFormat("dd/mm/yyyy hh:ii");
officialWebsiteURLEntryField = createTextBox(null);
officialWebsiteURLEntryField.setVisibleLength(50);
logoImageURLEntryField = createTextBox(null);
@@ -18,22 +18,23 @@ import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox;
import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.controls.listedit.StringListInlineEditorComposite;
import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public abstract class EventDialog extends DataEntryDialog<EventDTO> {
public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO> {
private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
protected StringMessages stringMessages;
protected TextBox nameEntryField;
protected TextArea descriptionEntryField;
protected TextBox venueEntryField;
protected DateBox startDateBox;
protected DateBox endDateBox;
protected BetterDateTimeBox startDateBox;
protected BetterDateTimeBox endDateBox;
protected CheckBox isPublicCheckBox;
protected UUID id;
protected TextBox officialWebsiteURLEntryField;
@@ -42,6 +43,12 @@ public abstract class EventDialog extends DataEntryDialog<EventDTO> {
protected StringListInlineEditorComposite imageURLList;
protected StringListInlineEditorComposite videoURLList;
protected StringListInlineEditorComposite sponsorImageURLList;
/**
* Allows subclasses to "park" a set of associated leaderboard groups for re-attachment to the new {@link EventDTO} produced
* by {@link #getResult()}.
*/
private final Iterable<LeaderboardGroupDTO> leaderboardGroups;
protected static class EventParameterValidator implements Validator<EventDTO> {
@@ -105,10 +112,15 @@ public abstract class EventDialog extends DataEntryDialog<EventDTO> {
}
public EventDialog(EventParameterValidator validator, StringMessages stringMessages,
/**
* @param leaderboardGroups even though not editable in this dialog, this parameter gives an editing subclass a chance to "park" the leaderboard group
* assignments for re-association with the new {@link EventDTO} created by the {@link #getResult} method.
*/
public EventDialog(EventParameterValidator validator, StringMessages stringMessages, Iterable<LeaderboardGroupDTO> leaderboardGroups,
DialogCallback<EventDTO> callback) {
super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), validator,
callback);
this.leaderboardGroups = leaderboardGroups;
this.stringMessages = stringMessages;
final ValueChangeHandler<List<String>> valueChangeHandler = new ValueChangeHandler<List<String>>() {
@Override
@@ -140,6 +152,9 @@ public abstract class EventDialog extends DataEntryDialog<EventDTO> {
@Override
protected EventDTO getResult() {
EventDTO result = new EventDTO();
for (LeaderboardGroupDTO lg : leaderboardGroups) {
result.addLeaderboardGroup(lg);
}
result.setName(nameEntryField.getText());
result.setDescription(descriptionEntryField.getText());
result.setOfficialWebsiteURL(officialWebsiteURLEntryField.getText().trim().isEmpty() ? null : officialWebsiteURLEntryField.getText().trim());
@@ -4,19 +4,14 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.user.datepicker.client.DateBox;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sse.common.Util;
public class EventEditDialog extends EventDialog {
public EventEditDialog(EventDTO event, Collection<EventDTO> otherExistingEvents, StringMessages stringConstants, DialogCallback<EventDTO> callback) {
super(new EventParameterValidator(stringConstants, otherExistingEvents), stringConstants, callback);
public EventEditDialog(EventDTO event, Collection<EventDTO> otherExistingEvents, StringMessages stringMessages, DialogCallback<EventDTO> callback) {
super(new EventParameterValidator(stringMessages, otherExistingEvents), stringMessages, event.getLeaderboardGroups(), callback);
nameEntryField = createTextBox(event.getName());
nameEntryField.setVisibleLength(50);
descriptionEntryField = createTextArea(event.getDescription());
@@ -25,10 +20,10 @@ public class EventEditDialog extends EventDialog {
descriptionEntryField.getElement().getStyle().setProperty("resize", "none");
venueEntryField = createTextBox(event.venue.getName());
venueEntryField.setVisibleLength(35);
startDateBox = createDateBox(event.startDate, 12);
startDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT)));
endDateBox = createDateBox(event.endDate, 12);
endDateBox.setFormat(new DateBox.DefaultFormat(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT)));
startDateBox = createDateTimeBox(event.startDate);
startDateBox.setFormat("dd/mm/yyyy hh:ii");
endDateBox = createDateTimeBox(event.endDate);
endDateBox.setFormat("dd/mm/yyyy hh:ii");
isPublicCheckBox = createCheckbox("");
isPublicCheckBox.setValue(event.isPublic);
id = event.id;
@@ -348,9 +348,9 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB());
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_REFRESH_RACELOG.equals(value)) {
refreshRaceLog(object.getA(), object.getB(), true);
} else if(LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) {
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) {
setStartTime(object.getA(), object.getB());
} else if(LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) {
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) {
showRaceLog(object.getA(), object.getB());
}
}
@@ -50,6 +50,7 @@ public class RegattaDetailsComposite extends Composite {
private final Label boatClassName;
private final Label scoringSystem;
private final Label defaultCourseArea;
private final Label useStartTimeInference;
private final Label configuration;
private final SelectionModel<SeriesDTO> seriesSelectionModel;
@@ -68,7 +69,7 @@ public class RegattaDetailsComposite extends Composite {
mainPanel = new CaptionPanel(stringMessages.regatta());
VerticalPanel vPanel = new VerticalPanel();
mainPanel.add(vPanel);
Grid grid = new Grid(5, 2);
Grid grid = new Grid(6, 2);
vPanel.add(grid);
regattaName = new Label();
@@ -86,15 +87,20 @@ public class RegattaDetailsComposite extends Composite {
grid.setWidget(2 , 0, new Label(stringMessages.courseArea() + ":"));
grid.setWidget(2 , 1, defaultCourseArea);
useStartTimeInference = new Label();
useStartTimeInference.ensureDebugId("UseStartTimeInferenceLabel");
grid.setWidget(3 , 0, new Label(stringMessages.useStartTimeInference() + ":"));
grid.setWidget(3 , 1, useStartTimeInference);
configuration = new Label();
configuration.ensureDebugId("RacingProcedureLabel");
grid.setWidget(3, 0, new Label(stringMessages.racingProcedureConfiguration() + ":"));
grid.setWidget(3, 1, configuration);
grid.setWidget(4, 0, new Label(stringMessages.racingProcedureConfiguration() + ":"));
grid.setWidget(4, 1, configuration);
scoringSystem = new Label();
scoringSystem.ensureDebugId("ScoringSystemLabel");
grid.setWidget(4 , 0, new Label(stringMessages.scoringSystem() + ":"));
grid.setWidget(4 , 1, scoringSystem);
grid.setWidget(5 , 0, new Label(stringMessages.scoringSystem() + ":"));
grid.setWidget(5 , 1, scoringSystem);
seriesTable = createRegattaSeriesTable();
seriesTable.ensureDebugId("SeriesCellTable");
@@ -108,7 +114,6 @@ public class RegattaDetailsComposite extends Composite {
seriesListDataProvider = new ListDataProvider<SeriesDTO>();
seriesListDataProvider.addDataDisplay(seriesTable);
vPanel.add(seriesTable);
initWidget(mainPanel);
}
@@ -372,6 +377,7 @@ public class RegattaDetailsComposite extends Composite {
regattaName.setText(regatta.getName());
boatClassName.setText(regatta.boatClass != null ? regatta.boatClass.getName() : "");
defaultCourseArea.setText(regatta.defaultCourseAreaUuid == null ? "" : regatta.defaultCourseAreaName);
useStartTimeInference.setText(regatta.useStartTimeInference ? stringMessages.yes() : stringMessages.no());
if (regatta.configuration != null) {
configuration.setText(stringMessages.configured());
} else {
@@ -233,7 +233,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer
final RegattaIdentifier regattaName = new RegattaName(editedRegatta.getName());
sailingService.updateRegatta(regattaName, editedRegatta.defaultCourseAreaUuid,
editedRegatta.configuration, new MarkedAsyncCallback<Void>(
editedRegatta.configuration, editedRegatta.useStartTimeInference, new MarkedAsyncCallback<Void>(
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
@@ -172,7 +172,7 @@ public class RegattaStructureManagementPanel extends SimplePanel implements Rega
}
sailingService.createRegatta(newRegatta.getName(), newRegatta.boatClass==null?null:newRegatta.boatClass.getName(),
new RegattaCreationParametersDTO(seriesStructure), true,
newRegatta.scoringScheme, newRegatta.defaultCourseAreaUuid, new AsyncCallback<RegattaDTO>() {
newRegatta.scoringScheme, newRegatta.defaultCourseAreaUuid, newRegatta.useStartTimeInference, new AsyncCallback<RegattaDTO>() {
@Override
public void onFailure(Throwable t) {
errorReporter.reportError("Error trying to create new regatta " + newRegatta.getName() + ": " + t.getMessage());
@@ -98,7 +98,6 @@ public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAnd
List<EventDTO> existingEvents, StringMessages stringConstants, DialogCallback<RegattaDTO> callback) {
super(new RegattaDTO(), existingEvents, stringConstants.addRegatta(), stringConstants.ok(), stringConstants,
new RegattaParameterValidator(stringConstants, existingRegattas), callback);
this.seriesEditor = new SeriesWithFleetsListEditor(Collections.<SeriesDTO>emptyList(), stringMessages, resources.removeIcon(), /*enableFleetRemoval*/true);
}
@@ -5,6 +5,7 @@ import java.util.UUID;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
@@ -30,6 +31,7 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
protected ListBox scoringSchemeListBox;
protected ListBox courseAreaListBox;
protected ListBox sailingEventsListBox;
protected CheckBox useStartTimeInferenceCheckBox;
protected List<EventDTO> existingEvents;
@@ -39,24 +41,19 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
String okButton, StringMessages stringMessages, Validator<RegattaDTO> validator,
DialogCallback<RegattaDTO> callback) {
super(title, null, okButton, stringMessages.cancel(), validator, callback);
this.stringMessages = stringMessages;
this.regatta = regatta;
this.existingEvents = existingEvents;
nameEntryField = createTextBox(null);
nameEntryField.ensureDebugId("NameTextBox");
nameEntryField.setVisibleLength(40);
nameEntryField.setText(regatta.getName());
boatClassEntryField = createTextBox(null);
boatClassEntryField.ensureDebugId("BoatClassTextBox");
boatClassEntryField.setVisibleLength(20);
if (regatta.boatClass != null) {
boatClassEntryField.setText(regatta.boatClass.getName());
}
scoringSchemeListBox = createListBox(false);
scoringSchemeListBox.ensureDebugId("ScoringSchemeListBox");
for (ScoringSchemeType scoringSchemeType : ScoringSchemeType.values()) {
@@ -66,14 +63,14 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
scoringSchemeListBox.setSelectedIndex(scoringSchemeListBox.getItemCount() - 1);
}
}
sailingEventsListBox = createListBox(false);
sailingEventsListBox.ensureDebugId("EventListBox");
useStartTimeInferenceCheckBox = createCheckbox(stringMessages.useStartTimeInference());
useStartTimeInferenceCheckBox.ensureDebugId("UseStartTimeInferenceCheckBox");
useStartTimeInferenceCheckBox.setValue(regatta.useStartTimeInference);
courseAreaListBox = createListBox(false);
courseAreaListBox.ensureDebugId("CourseAreaListBox");
courseAreaListBox.setEnabled(false);
setupEventAndCourseAreaListBoxes(stringMessages);
}
@@ -84,7 +81,7 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
if (additionalWidget != null) {
panel.add(additionalWidget);
}
Grid formGrid = new Grid(5, 2);
Grid formGrid = new Grid(6, 2);
panel.add(formGrid);
formGrid.setWidget(0, 0, new Label(stringMessages.name() + ":"));
formGrid.setWidget(0, 1, nameEntryField);
@@ -96,6 +93,8 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
formGrid.setWidget(3, 1, sailingEventsListBox);
formGrid.setWidget(4, 0, new Label(stringMessages.courseArea() + ":"));
formGrid.setWidget(4, 1, courseAreaListBox);
formGrid.setWidget(5, 0, new Label(stringMessages.useStartTimeInference() + ":"));
formGrid.setWidget(5, 1, useStartTimeInferenceCheckBox);
setupAdditionalWidgetsOnPanel(panel);
return panel;
}
@@ -105,6 +104,7 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
regatta.setName(nameEntryField.getText());
regatta.boatClass = new BoatClassDTO(boatClassEntryField.getText(), 0.0);
regatta.scoringScheme = getSelectedScoringSchemeType();
regatta.useStartTimeInference = useStartTimeInferenceCheckBox.getValue();
setCourseAreaInRegatta(regatta);
return regatta;
}
@@ -139,8 +139,8 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog<R
return false;
}
private void setupEventAndCourseAreaListBoxes(StringMessages stringConstants) {
sailingEventsListBox.addItem(stringConstants.selectSailingEvent());
private void setupEventAndCourseAreaListBoxes(StringMessages stringMessages) {
sailingEventsListBox.addItem(stringMessages.selectSailingEvent());
for (EventDTO event : existingEvents) {
sailingEventsListBox.addItem(event.getName());
if (isCourseAreaInEvent(event, regatta.defaultCourseAreaUuid)) {
@@ -35,6 +35,7 @@ public class RegattaWithSeriesAndFleetsEditDialog extends RegattaWithSeriesAndFl
List<EventDTO> existingEvents, final StringMessages stringMessages, DialogCallback<RegattaDTO> callback) {
super(regatta, existingEvents, stringMessages.editRegatta(), stringMessages.ok(), stringMessages,
null, callback);
ensureDebugId("RegattaWithSeriesAndFleetsEditDialog");
currentRegattaConfiguration = regatta.configuration;
nameEntryField.setEnabled(false);
@@ -2,14 +2,9 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.github.gwtbootstrap.datetimepicker.client.ui.base.HasViewMode.ViewMode;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CaptionPanel;
@@ -25,6 +20,7 @@ import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap;
import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
@@ -32,7 +28,7 @@ import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class SetStartTimeDialog extends DataEntryDialog<RaceLogSetStartTimeAndProcedureDTO> {
public class SetStartTimeDialog extends DataEntryDialogWithBootstrap<RaceLogSetStartTimeAndProcedureDTO> {
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
@@ -103,23 +99,7 @@ public class SetStartTimeDialog extends DataEntryDialog<RaceLogSetStartTimeAndPr
private Widget createInputPanel() {
Grid content = new Grid(5, 2);
timeBox = new BetterDateTimeBox();
timeBox.addValueChangeHandler(new ValueChangeHandler<Date>() {
@Override
public void onValueChange(ValueChangeEvent<Date> event) {
validate();
}
});
timeBox.addAttachHandler(new Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
if (event.isAttached()) {
addAutoHidePartner(timeBox.getPicker());
}
}
});
timeBox.setAutoClose(true);
timeBox.setStartView(ViewMode.HOUR);
timeBox = createDateTimeBox(new Date());
timeBox.setFormat("dd/mm/yyyy hh:ii");
content.setWidget(0, 0, createLabel(stringMessages.startTime()));
content.setWidget(0, 1, timeBox);
@@ -4,7 +4,7 @@ import java.util.Arrays;
import java.util.List;
public interface SuggestedCourseAreaNames {
static final List<String> suggestedCourseAreaNames = Arrays.asList("Alpha", "Bravo", "Charly", "Delta", "Echo",
static final List<String> suggestedCourseAreaNames = Arrays.asList("Alpha", "Bravo", "Charlie", "Delta", "Echo",
"Foxtrott", "Golf", "Hotel", "India", "Juliett", "Stadium", "Offshore");
}
@@ -17,7 +17,6 @@ import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
@@ -131,7 +130,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
TextColumn<SwissTimingRaceRecordDTO> raceNameColumn = new TextColumn<SwissTimingRaceRecordDTO>() {
@Override
public String getValue(SwissTimingRaceRecordDTO object) {
return object.raceName;
return object.getName();
}
};
@@ -272,7 +271,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
List<String> strings = new ArrayList<>();
strings.add(t.regattaName);
strings.add(t.seriesName);
strings.add(t.raceName);
strings.add(t.getName());
strings.add(t.raceStatus);
strings.add(t.boatClass);
strings.add(t.gender);
@@ -319,7 +318,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
result.setComparator(nameColumn, new Comparator<SwissTimingRaceRecordDTO>() {
@Override
public int compare(SwissTimingRaceRecordDTO o1, SwissTimingRaceRecordDTO o2) {
return new NaturalComparator().compare(o1.raceName, o2.raceName);
return new NaturalComparator().compare(o1.getName(), o2.getName());
}
});
result.setComparator(trackingStartColumn, new Comparator<SwissTimingRaceRecordDTO>() {
@@ -446,34 +445,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
}
// Check if the assigned regatta makes sense
List<SwissTimingRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<SwissTimingRaceRecordDTO>();
for (SwissTimingRaceRecordDTO race : selectedRaces) {
if (!checkBoatClassMatch(race, selectedRegatta)) {
racesWithNotMatchingBoatClasses.add(race);
}
}
if (racesWithNotMatchingBoatClasses.size() > 0) {
StringBuilder builder = new StringBuilder(100 + racesWithNotMatchingBoatClasses.size() * 30);
builder.append("WARNING\n");
if (selectedRegatta != null) {
builder.append(stringMessages.boatClassDoesNotMatchSelectedRegatta(
selectedRegatta.boatClass==null?"":selectedRegatta.boatClass.getName()));
} else {
builder.append(stringMessages.regattaExistForSelectedBoatClass());
}
builder.append("\n\n");
builder.append(stringMessages.races());
builder.append("\n");
for (SwissTimingRaceRecordDTO record: racesWithNotMatchingBoatClasses) {
builder.append(record.raceName);
builder.append("\n");
}
Window.alert(builder.toString());
return;
}
sailingService.trackWithSwissTiming(
if (checkBoatClassOK(selectedRegatta, selectedRaces)) {
sailingService.trackWithSwissTiming(
/* regattaToAddTo */ regattaIdentifier,
selectedRaces, hostname, port, trackWind, correctWindByDeclination,
new AsyncCallback<Void>() {
@@ -488,30 +461,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
regattaRefresher.fillRegattas();
}
});
}
private boolean checkBoatClassMatch(SwissTimingRaceRecordDTO swissTimingRecord, RegattaDTO selectedRegatta) {
String boatClassName = swissTimingRecord.boatClass;
if (boatClassName != null) {
if (selectedRegatta == null) {
if (swissTimingRecord.hasRememberedRegatta) {
return true;
}
// in case no regatta has been selected we check if there would be a matching regatta
for (RegattaDTO regatta : getAvailableRegattas()) {
if ((boatClassName == null && regatta.boatClass == null) ||
(regatta.boatClass != null && boatClassName.equals(regatta.boatClass.getName()))) {
return false;
}
}
} else {
if (!boatClassName.equals(selectedRegatta.boatClass.getName())) {
return false;
}
}
}
return true;
}
private void updatePanelFromSelectedStoredConfiguration() {
@@ -18,7 +18,6 @@ import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
@@ -119,7 +118,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
TextColumn<SwissTimingReplayRaceDTO> raceNameColumn = new TextColumn<SwissTimingReplayRaceDTO>() {
@Override
public String getValue(SwissTimingReplayRaceDTO object) {
return object.name;
return object.getName();
}
};
TextColumn<SwissTimingReplayRaceDTO> raceStartTrackingColumn = new TextColumn<SwissTimingReplayRaceDTO>() {
@@ -205,7 +204,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
@Override
public List<String> getSearchableStrings(SwissTimingReplayRaceDTO t) {
List<String> strings = new ArrayList<String>();
strings.addAll(Arrays.asList(t.boat_class, t.flight_number, t.name, t.race_id, t.rsc));
strings.addAll(Arrays.asList(t.boat_class, t.flight_number, t.getName(), t.race_id, t.rsc));
return strings;
}
};
@@ -258,7 +257,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
result.setComparator(nameColumn, new Comparator<SwissTimingReplayRaceDTO>() {
@Override
public int compare(SwissTimingReplayRaceDTO o1, SwissTimingReplayRaceDTO o2) {
return new NaturalComparator().compare(o1.name, o2.name);
return new NaturalComparator().compare(o1.getName(), o2.getName());
}
});
result.setComparator(boatClassColumn, new Comparator<SwissTimingReplayRaceDTO>() {
@@ -338,30 +337,6 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
});
}
private boolean checkBoatClassMatch(SwissTimingReplayRaceDTO swissTimingRaceRecord, RegattaDTO selectedRegatta) {
String boatClassName = swissTimingRaceRecord.boat_class;
if (boatClassName != null) {
if (selectedRegatta == null) {
if (swissTimingRaceRecord.hasRememberedRegatta) {
return true;
}
// in case no regatta has been selected we check if there would be a matching regatta
for (RegattaDTO regatta : getAvailableRegattas()) {
if ((boatClassName == null && regatta.boatClass == null) ||
(regatta.boatClass != null && boatClassName.equals(regatta.boatClass.getName()))) {
return false;
}
}
} else {
if (!boatClassName.equals(selectedRegatta.boatClass.getName())) {
return false;
}
}
}
return true;
}
private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, final boolean simulateWithStartTimeNow) {
RegattaDTO selectedRegatta = getSelectedRegatta();
RegattaIdentifier regattaIdentifier = null;
@@ -370,40 +345,14 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
}
// Check if the assigned regatta makes sense
List<SwissTimingReplayRaceDTO> racesWithNotMatchingBoatClasses = new ArrayList<SwissTimingReplayRaceDTO>();
final List<SwissTimingReplayRaceDTO> selectedRaces = new ArrayList<>();
for (SwissTimingReplayRaceDTO replayRace : raceList.getList()) {
if (raceTable.getSelectionModel().isSelected(replayRace)) {
if (!checkBoatClassMatch(replayRace, selectedRegatta)) {
racesWithNotMatchingBoatClasses.add(replayRace);
}
selectedRaces.add(replayRace);
}
}
if (racesWithNotMatchingBoatClasses.size() > 0) {
String warningText = "";
if (selectedRegatta != null) {
warningText += stringMessages.boatClassDoesNotMatchSelectedRegatta(
selectedRegatta.boatClass==null?"":selectedRegatta.boatClass.getName());
} else {
warningText += stringMessages.regattaExistForSelectedBoatClass();
}
warningText += "\n\n";
warningText += stringMessages.races() + "\n";
for (SwissTimingReplayRaceDTO record : racesWithNotMatchingBoatClasses) {
warningText += record.name + "\n";
}
Window.alert(warningText);
return;
}
final List<SwissTimingReplayRaceDTO> selectedRaces = new ArrayList<SwissTimingReplayRaceDTO>();
for (final SwissTimingReplayRaceDTO race : this.raceList.getList()) {
if (raceTable.getSelectionModel().isSelected(race)) {
selectedRaces.add(race);
}
}
sailingService.replaySwissTimingRace(regattaIdentifier, selectedRaces, trackWind, correctWindByDeclination,
if (checkBoatClassOK(selectedRegatta, selectedRaces)) {
sailingService.replaySwissTimingRace(regattaIdentifier, selectedRaces, trackWind, correctWindByDeclination,
simulateWithStartTimeNow, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
@@ -416,6 +365,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane
regattaRefresher.fillRegattas();
}
});
}
}
private void updateJsonUrlFromSelectedPreviousConfiguration() {
@@ -3,8 +3,10 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -15,7 +17,6 @@ import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
@@ -44,7 +45,6 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
@@ -294,7 +294,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
@Override
public List<String> getSearchableStrings(TracTracRaceRecordDTO t) {
List<String> strings = new ArrayList<String>();
strings.add(t.name);
strings.add(t.getName());
strings.add(t.regattaName);
if (t.boatClassNames != null) {
for (String boatClassName : t.boatClassNames) {
@@ -326,7 +326,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
TextColumn<TracTracRaceRecordDTO> raceNameColumn = new TextColumn<TracTracRaceRecordDTO>() {
@Override
public String getValue(TracTracRaceRecordDTO object) {
return object.name;
return object.getName();
}
};
raceNameColumn.setSortable(true);
@@ -399,7 +399,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
result.setComparator(nameColumn, new Comparator<TracTracRaceRecordDTO>() {
@Override
public int compare(TracTracRaceRecordDTO o1, TracTracRaceRecordDTO o2) {
return new NaturalComparator().compare(o1.name, o2.name);
return new NaturalComparator().compare(o1.getName(), o2.getName());
}
});
result.setComparator(boatClassColumn, new Comparator<TracTracRaceRecordDTO>() {
@@ -520,95 +520,48 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
}));
}
private boolean checkBoatClassMatch(TracTracRaceRecordDTO tracTracRecord, RegattaDTO selectedRegatta) {
Iterable<String> boatClassNames = tracTracRecord.boatClassNames;
if (boatClassNames != null && Util.size(boatClassNames) > 0) {
String tracTracBoatClassName = boatClassNames.iterator().next();
if (selectedRegatta == null) {
if (tracTracRecord.hasRememberedRegatta) {
return true;
}
// in case no regatta has been selected we check if there would be a matching regatta
for (RegattaDTO regatta : getAvailableRegattas()) {
if ((tracTracBoatClassName == null && regatta.boatClass == null) ||
(regatta.boatClass != null && tracTracBoatClassName.equalsIgnoreCase(regatta.boatClass.getName()))) {
return false;
}
}
} else {
if (!tracTracBoatClassName.equals(selectedRegatta.boatClass.getName())) {
return false;
}
}
}
return true;
}
private void trackSelectedRaces(boolean trackWind, boolean correctWind, final boolean simulateWithStartTimeNow) {
String liveURI = liveURITextBox.getValue();
String storedURI = storedURITextBox.getValue();
String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
String tractracUsername = tractracUsernameTextBox.getValue();
String tractracPassword = tractracPasswordTextBox.getValue();
RegattaDTO selectedRegatta = getSelectedRegatta();
RegattaDTO selectedRegatta = getSelectedRegatta(); // null meaning "Default Regatta" selection
RegattaIdentifier regattaIdentifier = null;
if (selectedRegatta != null) {
regattaIdentifier = new RegattaName(selectedRegatta.getName());
}
// Check if the assigned regatta makes sense
// Check if the assigned regatta makes sense; the following cases need to be distinguished:
// - non-default regatta explicitly selected: check that boat class matches; disallow loading when there is a mismatch
// - "Default Regatta" selected: if race was assigned to a regatta before, use that without further checks;
// otherwise, warn user if a "persistent" regatta with the same boat class already exists
// because it may be an accidental omission to select that regatta for loading
List<TracTracRaceRecordDTO> allRaces = raceList.getList();
List<TracTracRaceRecordDTO> racesWithNotMatchingBoatClasses = new ArrayList<TracTracRaceRecordDTO>();
SelectionModel<? super TracTracRaceRecordDTO> selectionModel = racesTable.getSelectionModel();
final Set<TracTracRaceRecordDTO> selectedRaces = new LinkedHashSet<>();
for (TracTracRaceRecordDTO race : allRaces) {
if (selectionModel.isSelected(race)) {
if (!checkBoatClassMatch(race, selectedRegatta))
racesWithNotMatchingBoatClasses.add(race);
}
}
if (racesWithNotMatchingBoatClasses.size() > 0) {
StringBuilder builder = new StringBuilder(100 + racesWithNotMatchingBoatClasses.size() * 30);
if (selectedRegatta != null) {
builder.append(stringMessages.boatClassDoesNotMatchSelectedRegatta(
selectedRegatta.boatClass==null?"":selectedRegatta.boatClass.getName()));
} else {
builder.append(stringMessages.regattaExistForSelectedBoatClass());
}
builder.append("\n\n");
builder.append(stringMessages.races());
builder.append("\n");
for (TracTracRaceRecordDTO record: racesWithNotMatchingBoatClasses) {
builder.append(record.name);
builder.append("\n");
}
Window.alert(builder.toString());
return;
}
final List<TracTracRaceRecordDTO> selectedRaces = new ArrayList<TracTracRaceRecordDTO>();
for (final TracTracRaceRecordDTO race : raceList.getList()) {
if (selectionModel.isSelected(race)) {
selectedRaces.add(race);
}
}
sailingService.trackWithTracTrac(regattaIdentifier, selectedRaces, liveURI, storedURI, courseDesignUpdateURI, trackWind, correctWind,
simulateWithStartTimeNow, tractracUsername, tractracPassword, new MarkedAsyncCallback<Void>(
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
reportError("Error trying to register races " + selectedRaces + " for tracking: "
+ caught.getMessage() + ". Check live/stored URI syntax.");
}
@Override
public void onSuccess(Void result) {
TracTracEventManagementPanel.this.regattaRefresher.fillRegattas();
}
}));
}
if (checkBoatClassOK(selectedRegatta, selectedRaces)) {
sailingService.trackWithTracTrac(regattaIdentifier, selectedRaces, liveURI, storedURI,
courseDesignUpdateURI, trackWind, correctWind, simulateWithStartTimeNow, tractracUsername,
tractracPassword, new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
reportError("Error trying to register races " + selectedRaces + " for tracking: "
+ caught.getMessage() + ". Check live/stored URI syntax.");
}
@Override
public void onSuccess(Void result) {
TracTracEventManagementPanel.this.regattaRefresher.fillRegattas();
}
}));
}
}
private void updatePanelFromSelectedStoredConfiguration() {
int index = connectionsHistoryListBox.getSelectedIndex();
@@ -96,19 +96,21 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
} else {
raceDataGrid.setText(2, 1, "");
}
if(selectedRaceDTO.trackedRace.startOfTracking != null) {
if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.startOfTracking != null) {
raceDataGrid.setText(3, 1, dateFormatter.render(selectedRaceDTO.trackedRace.startOfTracking) + " "
+ timeFormatter.render(selectedRaceDTO.trackedRace.startOfTracking));
} else {
raceDataGrid.setText(3, 1, "");
}
if(selectedRaceDTO.trackedRace.endOfTracking != null) {
if (selectedRaceDTO.trackedRace != null && selectedRaceDTO.trackedRace.endOfTracking != null) {
raceDataGrid.setText(4, 1, dateFormatter.render(selectedRaceDTO.trackedRace.endOfTracking) + " "
+ timeFormatter.render(selectedRaceDTO.trackedRace.endOfTracking));
} else {
raceDataGrid.setText(4, 1, "");
}
raceDataGrid.setText(5, 1, "" + selectedRaceDTO.trackedRace.delayToLiveInMs);
if (selectedRaceDTO.trackedRace != null) {
raceDataGrid.setText(5, 1, "" + selectedRaceDTO.trackedRace.delayToLiveInMs);
}
}
}
}
@@ -0,0 +1,55 @@
package com.sap.sailing.gwt.ui.client;
import java.util.Date;
import com.github.gwtbootstrap.datetimepicker.client.ui.base.HasViewMode.ViewMode;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DialogUtils;
public abstract class DataEntryDialogWithBootstrap<T> extends DataEntryDialog<T> {
public DataEntryDialogWithBootstrap(String title, String message, String okButtonName, String cancelButtonName,
com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator<T> validator, boolean animationEnabled,
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<T> callback) {
super(title, message, okButtonName, cancelButtonName, validator, animationEnabled, callback);
}
protected DataEntryDialogWithBootstrap(String title, String message, String okButtonName, String cancelButtonName,
com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator<T> validator,
com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback<T> callback) {
super(title, message, okButtonName, cancelButtonName, validator, callback);
}
/**
* Call something like <code>setFormat("dd/mm/yyyy hh:ii")</code> on the result to set the date / time entry format.
*/
public BetterDateTimeBox createDateTimeBox(Date initialValue) {
final BetterDateTimeBox result = new BetterDateTimeBox();
result.setValue(initialValue);
result.addValueChangeHandler(new ValueChangeHandler<Date>() {
@Override
public void onValueChange(ValueChangeEvent<Date> event) {
validate();
}
});
result.addAttachHandler(new Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
if (event.isAttached()) {
addAutoHidePartner(result.getPicker());
}
}
});
result.setAutoClose(true);
result.setStartView(ViewMode.HOUR);
DialogUtils.linkEnterToButton(getOkButton(), result.getBox());
DialogUtils.linkEscapeToButton(getCancelButton(), result.getBox());
return result;
}
}
@@ -182,7 +182,7 @@ public interface SailingService extends RemoteService {
RegattaDTO createRegatta(String regattaName, String boatClassName,
RegattaCreationParametersDTO seriesNamesWithFleetNamesAndFleetOrderingAndMedal, boolean persistent,
ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId);
ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, boolean useStartTimeInference);
void removeRegatta(RegattaIdentifier regattaIdentifier);
@@ -190,7 +190,7 @@ public interface SailingService extends RemoteService {
void removeRegattas(Collection<RegattaIdentifier> regattas);
void updateRegatta(RegattaIdentifier regattaIdentifier, UUID defaultCourseAreaUuid, RegattaConfigurationDTO regattaConfiguration);
void updateRegatta(RegattaIdentifier regattaIdentifier, UUID defaultCourseAreaUuid, RegattaConfigurationDTO regattaConfiguration, boolean useStartTimeInference);
List<RaceColumnInSeriesDTO> addRaceColumnsToSeries(RegattaIdentifier regattaIdentifier, String seriesName, List<String> columnNames);
@@ -383,7 +383,7 @@ public interface SailingServiceAsync {
void createRegatta(String regattaName, String boatClassName,
RegattaCreationParametersDTO seriesNamesWithFleetNamesAndFleetOrderingAndMedal, boolean persistent,
ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, AsyncCallback<RegattaDTO> callback);
ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, boolean useStartTimeInference, AsyncCallback<RegattaDTO> callback);
void addRaceColumnsToSeries(RegattaIdentifier regattaIdentifier, String seriesName, List<String> columnNames,
AsyncCallback<List<RaceColumnInSeriesDTO>> callback);
@@ -457,7 +457,8 @@ public interface SailingServiceAsync {
PolarSheetGenerationSettings settings, String name, AsyncCallback<PolarSheetGenerationResponse> asyncCallback);
void updateRegatta(RegattaIdentifier regattaIdentifier, UUID defaultCourseAreaUuid, RegattaConfigurationDTO regattaConfiguration, AsyncCallback<Void> callback);
void updateRegatta(RegattaIdentifier regattaIdentifier, UUID defaultCourseAreaUuid,
RegattaConfigurationDTO regattaConfiguration, boolean useStartTimeInference, AsyncCallback<Void> callback);
/**
* @param detailType
@@ -1056,4 +1056,5 @@ public interface StringMessages extends Messages {
String lastScoreCorrectionsTime();
String lastScoreCorrectionsComment();
String setTimeToNow();
String useStartTimeInference();
}
@@ -363,8 +363,8 @@ successfullyUpdatedScores=Successfully updated scores
errorUpdatingScoresForLeaderboard=Error updating scores for leaderboard {0}: {1}
maneuverTypesToShowWhenCompetitorIsClicked=Maneuver types to show when competitor is clicked
simulateWithStartTimeNow=Simulate with "now" as start time
boatClassDoesNotMatchSelectedRegatta=The selected races contain boat classes which are not the same as the boat class ''{0}'' of the selected regatta.
regattaExistForSelectedBoatClass=There is at least one regatta for the selected boat classes.
boatClassDoesNotMatchSelectedRegatta=The selected races contain boat classes which are not the same as the boat class ''{0}'' of the selected regatta. No races will be loaded.
regattaExistForSelectedBoatClass=There is at least one regatta for the selected boat classes. Really create default regatta(s) for those race(s)?
reload=Reload
addRegatta=Add Regatta...
exchangeName=Exchange Name
@@ -1039,4 +1039,5 @@ setStartTimeReceived=Set start time received
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace that isn''t persistent. This means that the new value will be forgotten after the server has been restarted.
lastScoreCorrectionsTime=Last score correction time
lastScoreCorrectionsComment=Last score correction comment
setTimeToNow=Set time to ''now''
setTimeToNow=Set time to ''now''
useStartTimeInference=Use start time inference from start mark passings
Loaded 100 of 185 files, more files were not shown because too many files have changed in this diff. Show more