diff --git a/.gitignore b/.gitignore index ffed4b19862..536b93de871 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ out/ local.properties git.xml mobile/github-advrecyclerview_lib/bin +captures \ No newline at end of file diff --git a/Charleston-Race-Week-2016.md b/Charleston-Race-Week-2016.md new file mode 100644 index 00000000000..714703026a3 --- /dev/null +++ b/Charleston-Race-Week-2016.md @@ -0,0 +1,75 @@ +## Server Landscape + +### Interconnect AWS regions + +For being able to serve low latency connection to the american continent we run several replica server in front of an ELB in AWS region US-East (N.Virginia). + +Our Master and eventual further replicas needed in EU region are deployed in AWS region EU-West (Ireland) for low latency connections towards RabbitMQ and MongoDB. + +Two interconnect the two AWS regions and subnets, I established a site2site VPN tunnel between us-east-1c and eu-west-1c region. The global VPC routes are used to route the traffic for the private subnets (172.16.0.0 in EU and 172.30.0.0 in US-East) towards the according VPN gateway instance, which then tunnels it to the other region. The result is that both regions can communicate internally with each other, so we don't need to tunnel via reverse SSH tunnels or even more worse, open ports (like MongoDB) to the public. + +See [here](http://aws.amazon.com/articles/0639686206802544) for the basic tunnel setup, which is established. + +Simply make sure here, to use the according availibility zones to the instances between the regions can interact with each other. All required internal subnets are configured to the "SAP Sailing analytics" and the "DB/Messaging" Security Group in both regions. + +The VPN setup can be easily expanded to further regions like APJ. + +### Load balancers and Route53 + +This setup requires two ELB's; one in each region. Put instances behind the according ELB in your region as usual. +The only difference here is, that Route53 entries differ a little bit from the usual standard. There are event subdomain entries (crw2016) for different geo-based locations set, which will handle and forward requests to the "best" load balancer which is set up in each region. See [here](https://wiki.sapsailing.com/wiki/amazon-ec2#amazon-ec2-for-sap-sailing-analytics_howto_using-latency-based-dns-across-regions) for the basic setup of latency-based or geo-based Route53 entries. + +### Technical event meta information +``` +MONGODB_NAME=crw2016 +SERVER_NAME=CRW2016 +REPLICATION_CHANNEL=crw2016 +EVENT=56f62045-de57-4c5e-be6c-a2339d9c9ece +``` + +### Instances used + +**EU-West Ireland** + +- ELB Load Balancer + - External DNS: `CRW2016-EU-554516968.eu-west-1.elb.amazonaws.com` + +- Master 1 (Live) + - Internal DNS: `ip-172-31-21-180.eu-west-1.compute.internal` + - External DNS: `ec2-52-50-179-44.eu-west-1.compute.amazonaws.com` + +**US-East N. Virginia** + +- ELB Load Balancer + - External DNS: `CRW2016-US-EAST-1076258956.us-east-1.elb.amazonaws.com` + +- Replica 1 + - Internal DNS: `ip-172-30-2-176.ec2.internal` + - External DNS: `ec2-52-90-229-141.compute-1.amazonaws.com` + +### Starting up further masters + +``` +MONGODB_NAME=crw2016 +REPLICATION_CHANNEL=crw2016 +SERVER_NAME=CRW2016 +USE_ENVIRONMENT=live-master-server +MEMORY=25600m +INSTALL_FROM_RELEASE=build-201604090936 +SERVER_STARTUP_NOTIFY=steffen.tobias.wagner@sap.com +EVENT=56f62045-de57-4c5e-be6c-a2339d9c9ece +``` + +### Starting up further replicas + +``` +INSTALL_FROM_RELEASE=build-201604090936 +USE_ENVIRONMENT=live-replica-server +REPLICATE_MASTER_SERVLET_HOST=172.31.21.180 +REPLICATE_MASTER_EXCHANGE_NAME=crw2016 +REPLICATE_ON_START=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl,com.sap.sse.mail.impl.MailServiceImpl,com.sap.sailing.polars.impl.PolarDataServiceImpl +SERVER_NAME=CRW2016 +MONGODB_NAME=crw2016-replica +EVENT_ID=56f62045-de57-4c5e-be6c-a2339d9c9ece +SERVER_STARTUP_NOTIFY=steffen.tobias.wagner@sap.com +``` \ No newline at end of file diff --git a/home.md b/home.md index 364757f799f..d8231764684 100644 --- a/home.md +++ b/home.md @@ -72,6 +72,7 @@ SAP is at the center of today’s technology revolution, developing innovations * [[Travemünder Woche 2014 event page|wiki/tw2014]] * [[505 worlds Kiel 2014 event page|wiki/505-worlds-kiel-2014]] * [[Kieler Woche event page|wiki/kieler-woche-2015]] + * [[Charleston Race Week 2016|wiki/Charleston-Race-Week-2016]] * Lean DI Support * [[Setting up internal Jenkins on SAP Monsoon|wiki/setting-up-jenkins-on-sap-monsoon]] diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index 6f422454edf..d6b57f62e8d 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -26,6 +26,7 @@ public enum BoatClassMasterdata { CANOE_IC ("International Canoe", true, 5.20, 1.01, BoatHullType.MONOHULL, true, "Canoe IC", "Canoe-IC", "IC", "Kanu IC", "International Canoe", "International 10 Sq.m. Sailing Canoe"), CANOE_TAIFUN ("Canoe Taifun", true, 5.20, 1.32, BoatHullType.MONOHULL, false, "Taifun", "Taifun Kanu", "Kanu Taifun"), CONTENDER ("Contender", true, 4.88, 1.42, BoatHullType.MONOHULL, false), + CC_30 ("C&C 30", true, 9.12, 3.25, BoatHullType.MONOHULL, true), D_ONE ("D-One", true, 4.23, 2.31, BoatHullType.MONOHULL, true, "Devoti D-One", "DOne", "D_One"), DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, true, "Drachen", "Dragon"), DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true), @@ -35,6 +36,7 @@ public enum BoatClassMasterdata { EUROPE_INT ("Europe Int.", true, 3.35, 1.35, BoatHullType.MONOHULL, false, "Europe"), F_18 ("Formula 18", true, 6.85, 2.25, BoatHullType.CATAMARAN, true, "F18", "F-18"), FARR_30 ("Farr 30", true, 9.42, 3.08, BoatHullType.MONOHULL, true, "F30", "F-30", "Farr-30"), + FARR_280 ("Farr 280", true, 8.72, 2.87, BoatHullType.MONOHULL, true, "F280", "F-280", "Farr-280"), FINN ("Finn", true, 4.50, 1.51, BoatHullType.MONOHULL, false), FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true), FLYING_PHANTOM ("Flying Phantom", true, 5.52, 3.00, BoatHullType.CATAMARAN, true), @@ -42,6 +44,7 @@ public enum BoatClassMasterdata { FUN ("FUN", true, 7.20, 2.45, BoatHullType.MONOHULL, true, "FUN O.D.", "FUN OD", "Open FUN"), F_16 ("Formula 16", true, 5.00, 2.50, BoatHullType.CATAMARAN, true, "F16", "F-16"), GC_32 ("GC 32", false, 10.0, 6.00, BoatHullType.CATAMARAN, true, "GC32", "GC-32"), + GP_26 ("GP 26", true, 7.90, 2.55, BoatHullType.MONOHULL, true, "GP26", "GP-26"), HOBIE_16 ("Hobie 16", true, 5.05, 2.41, BoatHullType.CATAMARAN, false, "H16"), H_BOAT ("H-Boat", true, 8.28, 2.18, BoatHullType.MONOHULL, true, "HB"), HOBIE_TIGER ("Hobie Tiger", true, 5.51, 2.60, BoatHullType.CATAMARAN, true), @@ -51,6 +54,9 @@ public enum BoatClassMasterdata { J24 ("J/24", true, 7.32, 2.67, BoatHullType.MONOHULL, true, "J24", "J-24"), J70 ("J/70", true, 6.93, 2.25, BoatHullType.MONOHULL, true, "J70", "J-70"), J80 ("J/80", true, 8.0, 2.51, BoatHullType.MONOHULL, true, "J80", "J-80"), + J88 ("J/88", true, 8.9, 1.89, BoatHullType.MONOHULL, true, "J88", "J-88"), + J105 ("J/105", true, 10.52, 3.35, BoatHullType.MONOHULL, true, "J105", "J-105"), + J111 ("J/111", true, 11.1, 3.29, BoatHullType.MONOHULL, true, "J111", "J-111"), KIELZUGVOGEL ("Kielzugvogel", true, 5.80, 1.88, BoatHullType.MONOHULL, false, "KZV"), KITE ("Kite", true, 3.35, 1.52, BoatHullType.MONOHULL, false), LASER_2 ("Laser 2", true, 4.39, 1.42, BoatHullType.MONOHULL, false, "Laser II", "Laser2", "Laser-2", "Laser-II"), @@ -59,6 +65,7 @@ public enum BoatClassMasterdata { LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, false, "Laser", "LSR"), LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, false, "LSB3", "SB20"), LAGO_26 ("Lago 26", true, 7.95, 2.50, BoatHullType.MONOHULL, true, "Lago26"), + MELGES_20 ("Melges 20", true, 6.10, 2.13, BoatHullType.MONOHULL, true, "Melges-20", "M20"), MELGES_24 ("Melges 24", true, 7.32, 2.50, BoatHullType.MONOHULL, true, "Melges-24", "M24"), MINI_TRANSAT ("Mini Transat 6.50", true, 6.50, 3.00, BoatHullType.MONOHULL, true, "Mini Transat"), MUSTO_SKIFF ("Musto Skiff", true, 4.55, 1.35, BoatHullType.MONOHULL, true, "Musto Performance Skiff", "MPS", "Musto"), @@ -78,14 +85,20 @@ public enum BoatClassMasterdata { STAR ("Star", true, 6.92, 1.74, BoatHullType.MONOHULL, false, "STR"), STREAMLINE ("Streamline", true, 7.15, 2.55, BoatHullType.MONOHULL, true), SWAN_45 ("Swan 45", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "Swan", "Swan-45"), + TARTAN_10 ("Tartan 10", true, 10.10, 2.82, BoatHullType.MONOHULL, true), TORNADO ("Tornado Catamaran", true, 6.10, 3.02, BoatHullType.CATAMARAN, true, "Tornado", "Tornado Cat"), TRIAS ("Trias", true, 9.20, 2.12, BoatHullType.MONOHULL, true), + VIPER_640 ("Viper 640", true, 6.43, 2.49, BoatHullType.MONOHULL, true), + VX_ONE ("VX ONE", true, 5.79, 2.19, BoatHullType.MONOHULL, true, "VX-ONE"), X_99 ("X-99", true, 9.96, 2.95, BoatHullType.MONOHULL, true, "X99"), // multi-class "boat classes"; to be replaced at some later point in time by something like a RegattaClass ORC ("ORC", true, 13.83, 3.91, BoatHullType.MONOHULL, true), ORC_CLUB ("ORC Club", true, 13.83, 3.91, BoatHullType.MONOHULL, true), - ORC_INTERNATIONAL ("ORC International", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "ORC Int."); + ORC_INTERNATIONAL ("ORC International", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "ORC Int."), + + // Performance Handicap Racing Fleet (PHRF) is a handicap system used for racing in North America + PHRF ("PHRF", true, 13.83, 3.91, BoatHullType.MONOHULL, true); private final String displayName; private final String[] alternativeNames; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/abstractlog/TimePointSpecificationFoundInLog.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/abstractlog/TimePointSpecificationFoundInLog.java new file mode 100644 index 00000000000..a1e18090c9d --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/abstractlog/TimePointSpecificationFoundInLog.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.common.abstractlog; + +import java.io.Serializable; + +import com.sap.sse.common.TimePoint; + +/** + * Events from {@link RaceLog}s and {@link RegattaLog}s may be used to specify timing aspects for a race. + * When searching for such events, three types of outcome are possible: a valid time point specification + * may be found; or an explicit specification of {@code null} may be found (such as forcing a tracking + * interval to be open at one end); or no specification is found.

+ * + * If only a {@link TimePoint} were used, the last two cases could not be disambiguated when specifying + * {@code null} as this {@link TimePoint}. This interface makes the distinction possible. If the reference + * to an object of this type is {@code null} it means that no information regarding the time point was found + * in the log. If a valid {@link TimePointSpecificationFoundInLog} object is presented, information was found in the log, + * but the {@link #getTimePoint} could still be {@code null}, meaning that a log entry explicitly set this + * time point to {@code null}. + * + * @author Axel Uhl (d043530) + * + */ +public interface TimePointSpecificationFoundInLog extends Serializable { + TimePoint getTimePoint(); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/abstractlog/TimePointSpecificationFoundInLogImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/abstractlog/TimePointSpecificationFoundInLogImpl.java new file mode 100644 index 00000000000..f14d3650a26 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/abstractlog/TimePointSpecificationFoundInLogImpl.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.common.abstractlog; + +import com.sap.sse.common.TimePoint; + +public class TimePointSpecificationFoundInLogImpl implements TimePointSpecificationFoundInLog { + private static final long serialVersionUID = -5499989656571658254L; + private TimePoint timePoint; + + TimePointSpecificationFoundInLogImpl() {} // for GWT serialization + + public TimePointSpecificationFoundInLogImpl(TimePoint timePoint) { + super(); + this.timePoint = timePoint; + } + + @Override + public TimePoint getTimePoint() { + return timePoint; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/GPSFix.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/GPSFix.java index ea30c3f3cd6..6dffbcd46b8 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/GPSFix.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/GPSFix.java @@ -1,10 +1,19 @@ package com.sap.sailing.domain.common.tracking; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Positioned; import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sse.common.Timed; public interface GPSFix extends Positioned, Timed, WithValidityCache, WithEstimatedSpeedCache { + /** + * A typical horizontal dilution of precision, or in other words, an error that is quite likely and may occur. + * For example, when trying to tell whether an object was on the one or the other side of some other object, + * this typical level of accuracy can be used as a default margin of error. + */ + Distance TYPICAL_HDOP = new MeterDistance(5); + /** * Reaches to at to's {@link GPSFix#getTimePoint() time point} starting at this fix, * traveling on a straight great circle segment. diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java new file mode 100644 index 00000000000..27c8ea5529c --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java @@ -0,0 +1,133 @@ +package com.sap.sailing.domain.racelogtracking.test.impl; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +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.TrackedRaceImpl; +import com.sap.sse.common.Duration; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest { + private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er"); + + /** + * tests the precedence order described in {@link TrackedRaceImpl#updateStartAndEndOfTracking()} + */ + @Test + public void testStartTimeInferencePrecedenceOrder() throws TransformationException, + NoCorrespondingServiceRegisteredException, InterruptedException { + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null, + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null); + Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark)); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2)); + Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark), + new WaypointImpl(mark2) })); + RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 })); + + TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, + EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ + null, /* endDate */null, null, null, "a", null)); + final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false, + OneDesignRankingMetric::new, mock(RaceLogResolver.class)); + + setStartAndEndOfRaceInRaceLog(10000, 20000); + trackedRace.attachRaceLog(raceLog); + trackedRace.attachRegattaLog(regattaLog); + + //test inference via race start/end time in racelog + trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog); + MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(10000); + TimePoint expectedStartOfTracking = startOfRaceInRaceLog.minus(Duration.ONE_MINUTE.times(TrackedRaceImpl.TRACKING_BUFFER_IN_MINUTES)); + MillisecondsTimePoint endOfRaceInRaceLog = new MillisecondsTimePoint(20000); + TimePoint expectedEndOfTracking = endOfRaceInRaceLog.plus(Duration.ONE_MINUTE.times(TrackedRaceImpl.TRACKING_BUFFER_IN_MINUTES)); + assertEquals(expectedStartOfTracking, trackedRace.getStartOfTracking()); + assertEquals(expectedEndOfTracking, trackedRace.getEndOfTracking()); + + + //test inference via manually set start/end of tracking + setManualTrackingTimesOnTrackedRace(trackedRace, 30000, 40000); + assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking()); + assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking()); + + //shouldn't change when setting time in raceLog again + setStartAndEndOfRaceInRaceLog(100000, 200000); + assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking()); + assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking()); + + //test inference via start/end of tracking in RaceLog + setStartAndEndOfTrackingInRaceLog(50000, 60000); + MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(50000); + MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(60000); + assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking()); + assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking()); + + //shouldn't change when setting time in raceLog again + setStartAndEndOfRaceInRaceLog(300000, 400000); + assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking()); + assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking()); + + //shouldn't change when setting start/end of tracking + setManualTrackingTimesOnTrackedRace(trackedRace, 500000, 600000); + assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking()); + assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking()); + } + + + public void setStartAndEndOfTrackingInRaceLog(int startTime, int endTime){ + MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(startTime); + MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(endTime); + raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTrackingInRacelog, author, 0)); + raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTrackingInRacelog, author, 0)); + } + + public void setStartAndEndOfRaceInRaceLog(int startTime, int endTime){ + MillisecondsTimePoint startTimeInRaceLog2 = new MillisecondsTimePoint(startTime); + MillisecondsTimePoint endTimeInRaceLog2 = new MillisecondsTimePoint(endTime); + raceLog.add(new RaceLogStartTimeEventImpl(startTimeInRaceLog2, author, 0, startTimeInRaceLog2)); + raceLog.add(new RaceLogRaceStatusEventImpl(endTimeInRaceLog2, endTimeInRaceLog2, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED)); + } + + public void setManualTrackingTimesOnTrackedRace(DynamicTrackedRace trackedRace, int startTime, int endTime){ + trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(startTime)); + trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(endTime)); + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF index 5133df9643f..4163fa4c010 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF @@ -21,6 +21,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.mail Import-Package: com.google.zxing;version="3.1.0", com.mongodb, + com.sap.sse.shared.media, javax.mail;version="1.4.0", javax.mail.internet;version="1.4.0", javax.mail.util;version="1.4.0", diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index d975334a6fb..0daa3a1bef8 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -19,7 +19,9 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder; @@ -57,6 +59,8 @@ import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; import com.sap.sailing.domain.common.racelog.tracking.RaceNotCreatedException; @@ -146,6 +150,18 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { public void visit(RaceLogEndOfTrackingEvent event) { RaceLogRaceTracker.this.onEndOfTrackingEvent(event); } + + @Override + public void visit(RaceLogStartTimeEvent event) { + trackedRace.updateStartAndEndOfTracking(); + } + + @Override + public void visit(RaceLogRaceStatusEvent event) { + if (event.getNextStatus().equals(RaceLogRaceStatus.FINISHED)){ + trackedRace.updateStartAndEndOfTracking(); + } + } }; visitors.put(log, visitor); ((RaceLog) log).addListener(visitor); @@ -183,9 +199,9 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { @Override public void stop(boolean preemptive) { RaceLog raceLog = params.getRaceLog(); - final Pair trackingTimes = new TrackingTimesFinder(raceLog).analyze(); - if (trackingTimes == null || trackingTimes.getB() == null) { - // seems the first time tracking for this race is started; enter "now" as start of tracking + final Pair trackingTimes = new TrackingTimesFinder(raceLog).analyze(); + if (trackingTimes == null || trackingTimes.getB() == null || trackingTimes.getB().getTimePoint() == null) { + // seems the first time tracking for this race is stopped; enter "now" as end of tracking // into the race log raceLog.add(new RaceLogEndOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0)); } @@ -408,13 +424,13 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { private void onStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) { if (trackedRace != null) { - trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint()); + trackedRace.updateStartAndEndOfTracking(); } } private void onEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) { if (trackedRace != null) { - trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint()); + trackedRace.updateStartAndEndOfTracking(); } } @@ -438,9 +454,9 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { RaceColumn raceColumn = params.getRaceColumn(); Fleet fleet = params.getFleet(); RaceLogDenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze(); - final Pair trackingTimes = new TrackingTimesFinder(raceLog).analyze(); - if (trackingTimes == null) { - // seems the first time tracking for this race is started; enter "now" as start of tracking + final Pair trackingTimes = new TrackingTimesFinder(raceLog).analyze(); + if (trackingTimes == null || trackingTimes.getA() == null || trackingTimes.getA().getTimePoint() == null) { + // the start of tracking interval is unset or set to null; enter "now" as start of tracking // into the race log raceLog.add(new RaceLogStartOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0)); } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index 66e8b734ebc..fd66057e442 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.racelogtracking.impl; import java.io.IOException; import java.io.InputStream; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.UUID; @@ -69,9 +70,11 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.mail.MailException; +import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.i18n.ResourceBundleStringMessages; import com.sap.sse.mail.MailService; import com.sap.sse.qrcode.QRCodeGenerationUtil; +import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.util.impl.NonGwtUrlHelper; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { @@ -200,7 +203,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { for (RaceLog toRaceLog : toRaceLogs) { if (new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) { if (course != null) { - CourseBase newCourse = new CourseDataImpl("Copy of \"" + course.getName() + "\""); + CourseBase newCourse = new CourseDataImpl(course.getName()); TimePoint now = MillisecondsTimePoint.now(); int i = 0; for (Waypoint oldWaypoint : course.getWaypoints()) { @@ -281,19 +284,22 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { String serverUrlWithoutTrailingSlash, Set competitors, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException { StringBuilder occuredExceptions = new StringBuilder(); - for (Competitor competitor : competitors) { final String toAddress = competitor.getEmail(); if (toAddress != null) { String leaderboardName = leaderboard.getName(); String competitorName = competitor.getName(); - String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, competitor.getId().toString(), NonGwtUrlHelper.INSTANCE); + String logoUrl = null; + final List imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO); + if (imagesWithTag != null && !imagesWithTag.isEmpty()) { + logoUrl = imagesWithTag.get(0).getURL().toString(); + } try { sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url, iOSAppUrl, - androidAppUrl); + androidAppUrl, logoUrl); } catch (MailException e) { occuredExceptions.append(e.getMessage() + "\r\n"); } @@ -305,7 +311,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { } private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String invitee, - String url, String iOSAppUrl, String androidAppUrl) throws MailException { + String url, String iOSAppUrl, String androidAppUrl, String logoUrl) throws MailException { final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES; String subject = String.format("%s %s", B.get(locale, "trackingInvitationFor"), invitee); @@ -314,21 +320,25 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { boolean hasIOSAppUrl = iOSAppUrl != null && !iOSAppUrl.isEmpty(); boolean hasAndroidAppUrl = androidAppUrl != null && !androidAppUrl.isEmpty(); + boolean hasLogoUrl = logoUrl != null && !logoUrl.isEmpty(); StringBuilder htmlText = new StringBuilder(); htmlText.append(""); htmlText.append(""); htmlText.append(""); htmlText.append(""); htmlText.append(""); + if (hasLogoUrl) { + htmlText.append("

"); + } htmlText.append("

").append(B.get(locale, "welcomeTo")).append(" ").append(leaderboardName).append("

"); htmlText.append("

").append(B.get(locale, "scanQRCodeOrVisitUrlToRegisterAs")).append(" ").append(invitee) .append("

"); - htmlText.append("

"); + htmlText.append("

"); htmlText.append("

").append(B.get(locale, "alternativelyVisitThisLink")).append("

"); if (hasIOSAppUrl) { htmlText.append("") @@ -386,13 +396,19 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { String eventId = event.getId().toString(); + String logoUrl = null; + List imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO); + if (imagesWithTag != null && !imagesWithTag.isEmpty()) { + logoUrl = imagesWithTag.get(0).getURL().toString(); + } // http:///buoy-tender/checkin?event_id=&leaderboard_name= String url = DeviceMappingConstants.getBuoyTenderInvitationUrl(serverUrlWithoutTrailingSlash, leaderboardName, eventId, NonGwtUrlHelper.INSTANCE); for (String toAddress : emailArray) { try { sendInvitationEmail(locale, toAddress, leaderboardName, - RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, iOSAppUrl, androidAppUrl); + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, iOSAppUrl, androidAppUrl, + logoUrl); } catch (MailException e) { occuredExceptions.append(e.getMessage() + "\r\n"); } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/DependentStartTimeFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/DependentStartTimeFinderTest.java index bb89960120a..73ce0a85b33 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/DependentStartTimeFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/DependentStartTimeFinderTest.java @@ -83,7 +83,7 @@ public class DependentStartTimeFinderTest { expectedDependingOnRaces.add(new SimpleRaceLogIdentifierImpl("A", "", "")); assertNull(result.getStartTime()); assertEquals(bAfterA, result.getStartTimeDiff()); - assertEquals(expectedDependingOnRaces, result.getRacesDependingOn()); + assertEquals(expectedDependingOnRaces, result.getDependingOnRaces()); // assertEquals(5000, result.getStartTimeDiff().asMillis()); finder = new StartTimeFinder(raceLogResolver, raceLogC); @@ -96,7 +96,7 @@ public class DependentStartTimeFinderTest { result = finder.analyze(); assertNull(result.getStartTime()); assertEquals(cAfterB, result.getStartTimeDiff()); - assertEquals(expectedDependingOnRaces, result.getRacesDependingOn()); + assertEquals(expectedDependingOnRaces, result.getDependingOnRaces()); TimePoint now = MillisecondsTimePoint.now(); raceLogA.add(new RaceLogStartTimeEventImpl(now, now, author, "12", 12, new MillisecondsTimePoint(5000), @@ -175,7 +175,7 @@ public class DependentStartTimeFinderTest { StartTimeFinderResult result = finder.analyze(); assertEquals(19000, result.getStartTime().asMillis()); assertEquals(8000, result.getStartTimeDiff().asMillis()); - assertEquals(expectedDependingOnRaces, result.getRacesDependingOn()); + assertEquals(expectedDependingOnRaces, result.getDependingOnRaces()); // now C -> B finder = new StartTimeFinder(raceLogResolver, raceLogC); @@ -187,7 +187,7 @@ public class DependentStartTimeFinderTest { result = finder.analyze(); assertEquals(11000, result.getStartTime().asMillis()); assertEquals(6000, result.getStartTimeDiff().asMillis()); - assertEquals(expectedDependingOnRaces, result.getRacesDependingOn()); + assertEquals(expectedDependingOnRaces, result.getDependingOnRaces()); // now B finder = new StartTimeFinder(raceLogResolver, raceLogB); @@ -198,6 +198,6 @@ public class DependentStartTimeFinderTest { result = finder.analyze(); assertEquals(5000, result.getStartTime().asMillis()); assertNull(result.getStartTimeDiff()); - assertEquals(expectedDependingOnRaces, result.getRacesDependingOn()); + assertEquals(expectedDependingOnRaces, result.getDependingOnRaces()); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/DependentStartTimeResolver.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/DependentStartTimeResolver.java index c6b0f962674..48ab190f4e8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/DependentStartTimeResolver.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/DependentStartTimeResolver.java @@ -46,7 +46,7 @@ public class DependentStartTimeResolver { resultOfDependentRace.setStartTimeDiff(startTimeDifference); result = resultOfDependentRace; } else { - result = new StartTimeFinderResult(resultOfDependentRace.getRacesDependingOn(), + result = new StartTimeFinderResult(resultOfDependentRace.getDependingOnRaces(), resultOfDependentRace.getStartTime().plus(startTimeDifference), startTimeDifference); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/StartTimeFinderResult.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/StartTimeFinderResult.java index ea1a5c12b8f..7371e1ccc23 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/StartTimeFinderResult.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/StartTimeFinderResult.java @@ -10,7 +10,7 @@ import com.sap.sse.common.Util; public class StartTimeFinderResult { public static enum ResolutionFailed { RACE_LOG_UNRESOLVED, CYCLIC_DEPENDENCY, NO_START_TIME_SET }; - private final Iterable racesDependingOn; + private final Iterable dependingOnRaces; private final TimePoint startTime; /** @@ -28,11 +28,11 @@ public class StartTimeFinderResult { this(racesDependingOn, /* startTime */ null, startTimeDiff, resolutionFailed); } - public StartTimeFinderResult(Iterable racesDependingOn, TimePoint startTime, Duration startTimeDiff, + public StartTimeFinderResult(Iterable dependingOnRaces, TimePoint startTime, Duration startTimeDiff, ResolutionFailed resolutionFailed) { this.startTime = startTime; this.startTimeDiff = startTimeDiff; - this.racesDependingOn = racesDependingOn; + this.dependingOnRaces = dependingOnRaces; this.resolutionFailed = resolutionFailed; } @@ -44,8 +44,12 @@ public class StartTimeFinderResult { return resolutionFailed; } - public Iterable getRacesDependingOn() { - return racesDependingOn; + /** + * @return the chain of races on which this race's start time depends; the last entry will have an absolute start + * time; the first one is the one on which this race's start time depends immediately + */ + public Iterable getDependingOnRaces() { + return dependingOnRaces; } public TimePoint getStartTime() { @@ -61,14 +65,14 @@ public class StartTimeFinderResult { } public boolean isDependentStartTime() { - return racesDependingOn != null && !Util.isEmpty(racesDependingOn); + return dependingOnRaces != null && !Util.isEmpty(dependingOnRaces); } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((racesDependingOn == null) ? 0 : racesDependingOn.hashCode()); + result = prime * result + ((dependingOnRaces == null) ? 0 : dependingOnRaces.hashCode()); result = prime * result + ((resolutionFailed == null) ? 0 : resolutionFailed.hashCode()); result = prime * result + ((startTime == null) ? 0 : startTime.hashCode()); result = prime * result + ((startTimeDiff == null) ? 0 : startTimeDiff.hashCode()); @@ -84,10 +88,10 @@ public class StartTimeFinderResult { if (getClass() != obj.getClass()) return false; StartTimeFinderResult other = (StartTimeFinderResult) obj; - if (racesDependingOn == null) { - if (other.racesDependingOn != null) + if (dependingOnRaces == null) { + if (other.dependingOnRaces != null) return false; - } else if (!racesDependingOn.equals(other.racesDependingOn)) + } else if (!dependingOnRaces.equals(other.dependingOnRaces)) return false; if (resolutionFailed != other.resolutionFailed) return false; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TrackingTimesFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TrackingTimesFinder.java index 00bf8467cfe..bf5e5c1b6dc 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TrackingTimesFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TrackingTimesFinder.java @@ -4,39 +4,40 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; -import com.sap.sse.common.TimePoint; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLogImpl; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; -public class TrackingTimesFinder extends RaceLogAnalyzer> { +public class TrackingTimesFinder extends RaceLogAnalyzer> { public TrackingTimesFinder(RaceLog raceLog) { super(raceLog); } @Override - protected Util.Pair performAnalysis() { + protected Util.Pair performAnalysis() { boolean startOfTrackingFound = false; boolean endOfTrackingFound = false; - TimePoint start = null; - TimePoint end = null; + TimePointSpecificationFoundInLog start = null; + TimePointSpecificationFoundInLog end = null; final RaceLog raceLog = getLog(); for (RaceLogEvent event : raceLog.getUnrevokedEventsDescending()) { if (!startOfTrackingFound && event instanceof RaceLogStartOfTrackingEvent) { - start = event.getLogicalTimePoint(); + start = new TimePointSpecificationFoundInLogImpl(event.getLogicalTimePoint()); startOfTrackingFound = true; } if (!endOfTrackingFound && event instanceof RaceLogEndOfTrackingEvent) { - end = event.getLogicalTimePoint(); + end = new TimePointSpecificationFoundInLogImpl(event.getLogicalTimePoint()); endOfTrackingFound = true; } if (startOfTrackingFound && endOfTrackingFound) { break; } } - final Pair result; + final Pair result; if (startOfTrackingFound || endOfTrackingFound) { - result = new Util.Pair(start, end); + result = new Util.Pair<>(start, end); } else { result = null; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java index f50c3b936ca..18b39e00a41 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ReadonlyRacingProcedure.java @@ -63,6 +63,11 @@ public interface ReadonlyRacingProcedure extends RaceStateEventProcessor, RaceSt */ boolean hasIndividualRecall(); + /** + * Returns {@code true} if this racing procedure allows users to enter scores in the app + */ + boolean isResultEntryEnabled(); + /** * Returns true if there is an individual recall signaled in the {@link RaceLog} right now. */ diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java index 88c96b8b79b..ffea26c734a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java @@ -60,6 +60,11 @@ public class BasicRacingProcedureImpl extends BaseRacingProcedure { return false; } + @Override + protected Boolean isResultEntryEnabledByDefault() { + return true; + } + @Override protected Collection createStartStateEvents(TimePoint startTime) { return Arrays.asList(new RaceStateEventImpl(startTime, RaceStateEvents.START)); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java index e1a4fc5a1f6..ea009a6554f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java @@ -158,6 +158,11 @@ public class ESSRacingProcedureImpl extends BaseRacingProcedure implements ESSRa return true; } + @Override + protected Boolean isResultEntryEnabledByDefault() { + return true; + } + @Override public ESSConfiguration getConfiguration() { return (ESSConfiguration) super.getConfiguration(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java index 276e7d0ee42..6005febdf11 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java @@ -76,6 +76,11 @@ public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements return false; } + @Override + protected Boolean isResultEntryEnabledByDefault() { + return false; + } + @Override public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, FulfillmentFunction function) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java index 7e848baba4f..1bccfd7b31b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/impl/BaseRacingProcedure.java @@ -213,10 +213,18 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i @Override public boolean hasIndividualRecall() { - return configuration.hasInidividualRecall() == null ? hasIndividualRecallByDefault() : configuration - .hasInidividualRecall(); + return configuration.hasIndividualRecall() == null ? hasIndividualRecallByDefault() : configuration + .hasIndividualRecall(); } + @Override + public boolean isResultEntryEnabled() { + return configuration.isResultEntryEnabled() == null ? isResultEntryEnabledByDefault() : configuration + .isResultEntryEnabled(); + } + + protected abstract Boolean isResultEntryEnabledByDefault(); + protected abstract boolean hasIndividualRecallByDefault(); @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java index 4e78ca10eb1..a7098a8d0e5 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/league/impl/LeagueRacingProcedureImpl.java @@ -43,21 +43,16 @@ public class LeagueRacingProcedureImpl extends BaseRacingProcedure implements Le return RacingProcedureType.LEAGUE; } - @Override - public boolean hasIndividualRecall() { - boolean hasRecall = super.hasIndividualRecall(); - if (!hasRecall) { - return false; - } else { - return hasRecall; - } - } - @Override protected boolean hasIndividualRecallByDefault() { return true; } + @Override + protected Boolean isResultEntryEnabledByDefault() { + return true; + } + @Override public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, FulfillmentFunction function) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java index 11ec1eb18a9..8b3cb5e7f98 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java @@ -78,6 +78,11 @@ public class RRS26RacingProcedureImpl extends BaseRacingProcedure implements RRS return true; } + @Override + protected Boolean isResultEntryEnabledByDefault() { + return false; + } + @Override public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, FulfillmentFunction function) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java index 9508bc968a8..70147c8b9f2 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java @@ -18,7 +18,7 @@ public interface RacingProcedureConfiguration extends Serializable { /** * Indicates whether the {@link RacingProcedure} should allow individual recall or not. */ - Boolean hasInidividualRecall(); + Boolean hasIndividualRecall(); /** * Merges this {@link RacingProcedureConfiguration} with the passed configuration, returning a copy with all @@ -28,4 +28,12 @@ public interface RacingProcedureConfiguration extends Serializable { */ RacingProcedureConfiguration merge(RacingProcedureConfiguration update); + /** + * May/shall the result entry control be used to capture results? If not, only the + * photo feature for the hardcopy will be availble. Otherwise, a rank editor is offered + * in the app which submits the score updates which then will be applied to the leaderboard + * immediately. + */ + Boolean isResultEntryEnabled(); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java index 21977ed851d..555d501bea6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java @@ -8,8 +8,21 @@ public class RacingProcedureConfigurationImpl implements RacingProcedureConfigur private static final long serialVersionUID = 8501755084811977792L; private Flags classFlag; - private Boolean hasInidividualRecall;@Override + /** + * Can a user trigger an individual recall by using the X-Ray flag? + */ + private Boolean hasIndividualRecall; + + /** + * May/shall the result entry control be used to capture results? If not, only the + * photo feature for the hardcopy will be availble. Otherwise, a rank editor is offered + * in the app which submits the score updates which then will be applied to the leaderboard + * immediately. + */ + private Boolean isResultEntryEnabled; + + @Override public Flags getClassFlag() { return classFlag; } @@ -19,12 +32,21 @@ public class RacingProcedureConfigurationImpl implements RacingProcedureConfigur } @Override - public Boolean hasInidividualRecall() { - return hasInidividualRecall; + public Boolean hasIndividualRecall() { + return hasIndividualRecall; } - public void setHasInidividualRecall(Boolean hasInidividualRecall) { - this.hasInidividualRecall = hasInidividualRecall; + public void setHasIndividualRecall(Boolean hasInidividualRecall) { + this.hasIndividualRecall = hasInidividualRecall; + } + + @Override + public Boolean isResultEntryEnabled() { + return isResultEntryEnabled; + } + + public void setResultEntryEnabled(Boolean isResultEntryEnabled) { + this.isResultEntryEnabled = isResultEntryEnabled; } protected RacingProcedureConfiguration copy() { @@ -33,7 +55,8 @@ public class RacingProcedureConfigurationImpl implements RacingProcedureConfigur protected RacingProcedureConfiguration copy(RacingProcedureConfigurationImpl target) { target.setClassFlag(this.getClassFlag()); - target.setHasInidividualRecall(this.hasInidividualRecall()); + target.setHasIndividualRecall(this.hasIndividualRecall()); + target.setResultEntryEnabled(this.isResultEntryEnabled()); return target; } @@ -43,8 +66,11 @@ public class RacingProcedureConfigurationImpl implements RacingProcedureConfigur if (update.getClassFlag() != null) { target.setClassFlag(update.getClassFlag()); } - if (update.hasInidividualRecall() != null) { - target.setHasInidividualRecall(update.hasInidividualRecall()); + if (update.hasIndividualRecall() != null) { + target.setHasIndividualRecall(update.hasIndividualRecall()); + } + if (update.isResultEntryEnabled() != null) { + target.setResultEntryEnabled(update.isResultEntryEnabled()); } return target; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java index 67359e4b3c8..b9a5c66198f 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java @@ -26,6 +26,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.base.impl.CourseAreaImpl; import com.sap.sailing.domain.base.impl.FleetImpl; @@ -88,6 +89,32 @@ public class CompetitorProviderCacheInvalidationTest extends AbstractLeaderboard competitorProviderRegattaLeaderboard = new CompetitorProviderFromRaceColumnsAndRegattaLike(regattaLeaderboard); } + /** + * Asserts that a regatta leaderboard provides the regatta log-registered competitors even if there + * is not a single race column in the regatta yet. + */ + @Test + public void testCompetitorsFromRegattaLogForEmptyRegatta() { + for (final Series seriesToRemove : regatta.getSeries()) { + regatta.removeSeries(seriesToRemove); + } + RegattaLog regattaLog = regattaLeaderboard.getRegatta().getRegattaLog(); + final Map competitorOnRegattaLogRegistrationEvents = new HashMap<>(); + final LogEventAuthorImpl author = new LogEventAuthorImpl("Me", 0); + for (Competitor c : compLists[0]) { + final RegattaLogRegisterCompetitorEventImpl registerCompetitorEvent = new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), author, UUID.randomUUID(), c); + regattaLog.add(registerCompetitorEvent); + competitorOnRegattaLogRegistrationEvents.put(c, registerCompetitorEvent); + } + Set expected = new HashSet<>(compLists[0]); + Set actual = new HashSet<>(); + Util.addAll(competitorProviderRegattaLeaderboard.getAllCompetitors(), actual); + assertEquals(expected, actual); + Set actual2 = new HashSet<>(); + Util.addAll(regatta.getAllCompetitors(), actual2); + assertEquals(expected, actual2); + } + @Test public void testSimpleCompetitorListOnOneRaceInFlexibleLeaderboard() { TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(MillisecondsTimePoint.now(), compLists[0]); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 02cf02d1673..882bea8c3f1 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -205,7 +205,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void recordFix(Competitor competitor, GPSFixMoving fix) { + public void recordFix(Competitor competitor, GPSFixMoving fix, boolean onlyWhenInTrackingTimesInterval) { } @Override @@ -723,7 +723,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void recordFix(Mark mark, GPSFix fix) { + public void recordFix(Mark mark, GPSFix fix, boolean onlyWhenInTrackingTimesInterval) { } @Override @@ -1034,4 +1034,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public void updateMarkPassingsAfterRaceLogChanges() { } + + @Override + public void updateStartAndEndOfTracking() { + } + + @Override + public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index b13c4e7d4d8..a70725ef5c5 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -654,4 +654,8 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { public IsManagedByCache resolve(SharedDomainFactory domainFactory) { return this; } + + @Override + public void updateStartAndEndOfTracking() { + } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracConnectionConstants.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracConnectionConstants.java index cdfcc8187b0..5daa13e7978 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracConnectionConstants.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracConnectionConstants.java @@ -8,7 +8,7 @@ public interface TracTracConnectionConstants { final int PORT_TUNNEL_STORED = PORT_TUNNEL_LIVE + 1; //For official usage use 4413; final int PORT_LIVE = 1520; //For official usage use 4400; for SAP-dedicated server use 1520 - final int PORT_STORED = PORT_LIVE + 1; //For official usage use 4401; + final int PORT_STORED = PORT_LIVE + 1; //For official usage use 4401; for SAP-dedicated server use 1521 /* * "Status" is the one that we used originally and is an enumerate with the values: diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java index 52a6d395f56..560b4daa821 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java @@ -17,6 +17,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; import com.sap.sailing.domain.abstractlog.regatta.impl.BaseRegattaLogEventVisitor; +import com.sap.sailing.domain.abstractlog.shared.analyzing.CompetitorsInLogAnalyzer; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; @@ -157,15 +158,23 @@ public class CompetitorProviderFromRaceColumnsAndRegattaLike { public Iterable getAllCompetitors() { if (allCompetitorsCache == null) { final Set result = new HashSet<>(); + boolean hasRaceColumns = false; for (RaceColumn rc : provider.getRaceColumns()) { + hasRaceColumns = true; Util.addAll(rc.getAllCompetitors(), result); for (final Fleet fleet : rc.getFleets()) { rc.getRaceLog(fleet).addListener(raceLogCompetitorsCacheInvalidationListener); } } final RegattaLog regattaLog = provider.getRegattaLike().getRegattaLog(); - // Don't add regatta log competitors; if no race exists, the leaderboard is not considered to have any - // competitors. The competitors are collected from the races. Those, however, will be the regatta log + if (!hasRaceColumns) { + // If no race exists, the regatta log-provided competitor registrations will not have + // been considered yet; add them: + final Set regattaLogProvidedCompetitors = new CompetitorsInLogAnalyzer<>(regattaLog).analyze(); + result.addAll(regattaLogProvidedCompetitors); + } + // else, don't add regatta log competitors because they have been added in each column already. + // The competitors are collected from the races. Those, however, will be the regatta log // competitors if the race does not define its own. // Note: adding listeners is idempotent; at most one occurrence of this listener exists in the race/regatta log's // listeners set @@ -187,7 +196,7 @@ public class CompetitorProviderFromRaceColumnsAndRegattaLike { Iterable result = allCompetitorsCacheByRace.get(key); if (result == null) { final Set resultSet = new HashSet<>(); - //raceColumn already considers trackedRace, RaceLog and RegattaLog + // raceColumn already considers trackedRace, RaceLog and RegattaLog Util.addAll(raceColumn.getAllCompetitors(fleet), resultSet); // note: adding listeners is idempotent; at most one occurrence of this listener exists in the race/regatta log's // listeners set diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index 6f356dd53a1..bb58ce6b5dc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -72,7 +72,7 @@ public class MarkPassingCalculator { chooser.calculateMarkPassDeltas(c, allCandidates.getA(), allCandidates.getB()); } if (listen) { - final Thread listenerThread = new Thread(new Listen(), "MarkPassingCalculator for race " + race.getRace().getName()); + final Thread listenerThread = new Thread(new Listen(race.getRace().getName()), "MarkPassingCalculator for race " + race.getRace().getName()); listenerThread.setDaemon(true); listenerThread.start(); } @@ -88,10 +88,16 @@ public class MarkPassingCalculator { * */ private class Listen implements Runnable { + private final String raceName; + + public Listen(String raceName) { + this.raceName = raceName; + } + @Override public void run() { try { - logger.fine("MarkPassingCalculator is listening"); + logger.info("MarkPassingCalculator is listening on race "+raceName); boolean finished = false; Map> competitorFixes = new HashMap<>(); Map> markFixes = new HashMap<>(); @@ -104,64 +110,65 @@ public class MarkPassingCalculator { List unsuppressedMarkPassings = new ArrayList<>(); while (!finished) { try { - logger.finer("MPC is checking the queue"); - List allNewFixInsertions = new ArrayList<>(); - try { - allNewFixInsertions.add(listener.getQueue().take()); - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "MarkPassingCalculator threw exception " + e.getMessage() - + " while waiting for new GPSFixes"); - } - listener.getQueue().drainTo(allNewFixInsertions); - logger.finer("MPC recieved "+ allNewFixInsertions.size()+" new updates."); - for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) { - if (listener.isEndMarker(fixInsertion)) { - logger.info("Stopping "+MarkPassingCalculator.this+"'s listener"); - finished = true; - } else { - fixInsertion.storePositionUpdate(competitorFixes, markFixes, addedWaypoints, removedWaypoints, - smallestChangedWaypointIndex, fixedMarkPassings, removedFixedMarkPassings, - suppressedMarkPassings, unsuppressedMarkPassings); + logger.finer("MPC for "+raceName+" is checking the queue"); + List allNewFixInsertions = new ArrayList<>(); + try { + allNewFixInsertions.add(listener.getQueue().take()); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "MarkPassingCalculator for "+raceName+" threw exception " + e.getMessage() + + " while waiting for new GPSFixes"); } - } - if (!suspended) { - if (smallestChangedWaypointIndex != null) { - Map, List>> candidateDeltas = finder - .updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex); - chooser.removeWaypoints(removedWaypoints); - chooser.addWaypoints(addedWaypoints); - for (Entry, List>> entry : candidateDeltas - .entrySet()) { - Util.Pair, List> pair = entry.getValue(); - chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + listener.getQueue().drainTo(allNewFixInsertions); + logger.finer("MPC for "+raceName+" recieved "+ allNewFixInsertions.size()+" new updates."); + for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) { + if (listener.isEndMarker(fixInsertion)) { + logger.info("Stopping "+MarkPassingCalculator.this+"'s listener for race "+raceName); + finished = true; + break; + } else { + fixInsertion.storePositionUpdate(competitorFixes, markFixes, addedWaypoints, removedWaypoints, + smallestChangedWaypointIndex, fixedMarkPassings, removedFixedMarkPassings, + suppressedMarkPassings, unsuppressedMarkPassings); } } - updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, - unsuppressedMarkPassings); - computeMarkPasses(competitorFixes, markFixes); - competitorFixes.clear(); - markFixes.clear(); - addedWaypoints.clear(); - removedWaypoints.clear(); - smallestChangedWaypointIndex = null; - fixedMarkPassings.clear(); - removedFixedMarkPassings.clear(); - suppressedMarkPassings.clear(); + if (!finished && !suspended) { + if (smallestChangedWaypointIndex != null) { + Map, List>> candidateDeltas = finder + .updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex); + chooser.removeWaypoints(removedWaypoints); + chooser.addWaypoints(addedWaypoints); + for (Entry, List>> entry : candidateDeltas + .entrySet()) { + Util.Pair, List> pair = entry.getValue(); + chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + } + } + updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, + unsuppressedMarkPassings); + computeMarkPasses(competitorFixes, markFixes); + competitorFixes.clear(); + markFixes.clear(); + addedWaypoints.clear(); + removedWaypoints.clear(); + smallestChangedWaypointIndex = null; + fixedMarkPassings.clear(); + removedFixedMarkPassings.clear(); + suppressedMarkPassings.clear(); unsuppressedMarkPassings.clear(); } } catch (Exception e) { - logger.severe("Error while calculating markpassings: " + e.getMessage()); + logger.log(Level.SEVERE, "Error while calculating markpassings for race "+raceName+": " + e.getMessage(), e); } } } finally { - logger.info("MarkPassingCalculator Listen thread terminating"); + logger.info("MarkPassingCalculator Listen thread terminating for race "+raceName); } } private void updateManuallySetMarkPassings(List> fixedMarkPassings, List> removedMarkPassings, List> suppressedMarkPassings, List unsuppressedMarkPassings) { - logger.finest("Updating manually edited MarkPassings"); + logger.finest("Updating manually edited MarkPassings for race "+raceName); for (Pair pair : suppressedMarkPassings) { chooser.suppressMarkPassings(pair.getA(), pair.getB()); } @@ -187,7 +194,7 @@ public class MarkPassingCalculator { */ private void computeMarkPasses(Map> newCompetitorFixes, Map> newMarkFixes) { - logger.finer("Calculating markpassings with " + newCompetitorFixes.size() + " new competitor Fixes and " + logger.finer("Calculating markpassings for race "+raceName+" with " + newCompetitorFixes.size() + " new competitor Fixes and " + newMarkFixes.size() + "new mark fixes."); Map> combinedCompetitorFixes = new HashMap<>(); @@ -230,9 +237,10 @@ public class MarkPassingCalculator { @Override public void run() { - logger.finer("Calculating MarkPassings for " + c + " (" + Util.size(fixes) + " new fixes)"); + logger.finer("Calculating MarkPassings for race "+raceName+", competitor " + c + " (" + Util.size(fixes) + " new fixes)"); Util.Pair, Iterable> candidateDeltas = finder.getCandidateDeltas(c, fixes); - logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates and will remove " + logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates for race "+raceName+ + " and competitor "+c+" and will remove " + Util.size(candidateDeltas.getB()) + " old Candidates for " + c); chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java index 093f167be45..3b2435361bf 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java @@ -20,6 +20,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.markpassingcalculation.Candidate; import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -46,12 +47,24 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; * */ public class CandidateChooserImpl implements CandidateChooser { + /** + * Earlier finish mark passings are to be preferred over later ones if they otherwise seem equally likely. While the + * {@link #getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance, Distance, double)} method should usually + * assign an equal probability of 1.0 for edges whose distance is in the range of 1.0 and + * {@link #MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH} times the leg length, for the finishing + * leg the candidates that require more distance than the minimum distance required receive an increasing penalty. + * The maximum penalty for finishing candidates that have required + * {@link #MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH} times the leg distance is expressed by + * this consttant. + */ + private static final double PENALTY_FOR_LATEST_FINISH_PASSING = 0.95; + /** * Distance ratios of actual distance traveled and leg length above this threshold will receive * penalties on their probability. Ratios below 1.0 receive the ratio as the penalty. * See {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate)}. */ - final double MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH = 2.0; + private static final double MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH = 2.0; /** * Start mark passings will be considered this much before the actual race start. The race start @@ -67,7 +80,7 @@ public class CandidateChooserImpl implements CandidateChooser { */ private static final Duration DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES = Duration.ONE_MINUTE; - private final static double MINIMUM_PROBABILITY = Edge.getPenaltyForSkipping(); + private static final double MINIMUM_PROBABILITY = Edge.getPenaltyForSkipping(); private static final Logger logger = Logger.getLogger(CandidateChooserImpl.class.getName()); @@ -308,7 +321,7 @@ public class CandidateChooserImpl implements CandidateChooser { edgeSet = new HashSet<>(); edges.put(e.getStart(), edgeSet); } - edgeSet.add(e); + edgeSet.add(e); // FIXME what about edges that should replace an edge between the same two candidates? Will those edges somehow be removed? } /** @@ -437,9 +450,16 @@ public class CandidateChooserImpl implements CandidateChooser { first = c1.getWaypoint(); } final Waypoint second = c2.getWaypoint(); - final Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2); - final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint()); - result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled); + final Distance totalGreatCircleDistance = getMinimumTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2); + if (totalGreatCircleDistance == null) { + result = 0; // no distance known; cannot tell; low probability + } else { + final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint()); + final double probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength = + c2.getWaypoint() == race.getRace().getCourse().getLastWaypoint() ? PENALTY_FOR_LATEST_FINISH_PASSING : 1.0; + result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled, + probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength); + } return result; } @@ -450,31 +470,44 @@ public class CandidateChooserImpl implements CandidateChooser { * mark on a great circle segment, distances sailed will exceed the great line circle distances. *

* - * A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance estimated - * and double that is likely and anything greater than that gradually becomes unlikely. + * A smaller distance than great circle from mark to mark is getting the more unlikely the shorter the distance is, + * somewhere between the distance estimated and twice that is likely, and anything greater than that gradually + * becomes unlikely. + *

+ * + * Finishing legs are a special case. Here, we'd like to prefer an earlier candidate over a later one as long as the + * earlier one still leads to a "reasonable" distance sailed, particularly if two such candidates are otherwise + * equally highly likely. Therefore, this method accepts a parameter + * {@code probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength} that configures a slight "slope" in + * the interval that for non-finishing legs receives a constant probability of 1.0. This slope will give 1.0 for the + * shortest possible distance and slightly less for the longest distance that for non-finishing legs would still + * result in 1.0. The probabilities of even greater distances then starts contiguously at the end value of that + * slope. * * @return a number between 0 and 1 with 1 representing a "fair chance" that the actual distance sailed could have * been sailed for the given great circle distance; 1 is returned for actual distances being in the range of * 1..2 times the great circle distance. Actual distances outside this interval reduce probability linearly - * for smaller distances (gradient 0.5) and varies with the one over the ratio for distances that exceed twice the - * great circle distance. + * for smaller distances (gradient 0.5) and varies with the one over the ratio for distances that exceed + * twice the great circle distance. */ - private double getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance totalGreatCircleDistance, - Distance actualDistanceTraveled) { + private double getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance totalGreatCircleDistance, Distance actualDistanceTraveled, + double probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength) { final double result; final double ratio = actualDistanceTraveled.getMeters() / totalGreatCircleDistance.getMeters(); // A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance // estimated and double that is likely and anything greater than that gradually becomes unlikely - if (ratio <= MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH) { - result = Math.min(1.0, ratio); // maximum probability is 1; use for the interval between 1 and MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH + if (ratio <= 1) { + result = ratio; + } else if (ratio <= MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH) { + result = 1 - (1-probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength)*(ratio-1)/(MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH-1); } else { - result = 1./(ratio-1.); // start at probability 1 for ratio==2 + // start at probability probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength for ratio==MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH + result = probabilityForMaxReasonableRatioBetweenDistanceTraveledAndLegLength/(ratio-MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH + 1.); } return result; } - private Distance getApproximateTotalGreatCircleDistanceBetweenWaypoints(Waypoint first, final Waypoint second, - final TimePoint timePoint) { + private Distance getMinimumTotalGreatCircleDistanceBetweenWaypoints(Waypoint first, final Waypoint second, final TimePoint timePoint) { Distance totalGreatCircleDistance = new MeterDistance(0); boolean legsAreBetweenCandidates = false; for (TrackedLeg leg : race.getTrackedLegs()) { @@ -486,8 +519,15 @@ public class CandidateChooserImpl implements CandidateChooser { legsAreBetweenCandidates = true; } if (legsAreBetweenCandidates) { - totalGreatCircleDistance = totalGreatCircleDistance.add( - waypointPositionAndDistanceCache.getApproximateDistance(from, leg.getLeg().getTo(), timePoint)); + final Distance minimumDistanceToNextWaypoint = waypointPositionAndDistanceCache.getMinimumDistance(from, leg.getLeg().getTo(), timePoint); + if (minimumDistanceToNextWaypoint == null) { + totalGreatCircleDistance = null; + break; + } else { + // subtract twice the typical error margin of the position fixes of the marks, assuming that the leg could have been + // a little shorter in fact: + totalGreatCircleDistance = totalGreatCircleDistance.add(minimumDistanceToNextWaypoint).add(GPSFix.TYPICAL_HDOP.scale(-2)); + } } } return totalGreatCircleDistance; @@ -502,14 +542,16 @@ public class CandidateChooserImpl implements CandidateChooser { private void removeCandidates(Competitor c, Iterable wrongCandidates) { for (Candidate can : wrongCandidates) { - logger.finest("Removing all edges containing " + can + "of "+ c); + if (logger.isLoggable(Level.FINEST)) { + logger.finest("Removing all edges containing " + can + "of "+ c); + } candidates.get(c).remove(can); Map> edges = allEdges.get(c); edges.remove(can); for (Set set : edges.values()) { for (Iterator i = set.iterator(); i.hasNext();) { final Edge e = i.next(); - if (e.getStart().equals(can) || e.getEnd().equals(can)) { + if (e.getStart() == can || e.getEnd() == can) { i.remove(); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java index cf4d3fad43b..5260fbce609 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.markpassingcalculation.impl; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -57,13 +58,30 @@ public class WaypointPositionAndDistanceCache { private final ConcurrentWeakHashMap> waypointPositionCache; /** - * Caches distances between the positions cached in {@link #waypointPositionCache}. As soon as an entry + * Weak keys are the marks; values are time-ordered maps mapping the center of a time range + * to the mark position at that time. + */ + private final ConcurrentWeakHashMap> markPositionCache; + + /** + * Caches distances between the approximate positions cached in {@link #waypointPositionCache}. For two-mark + * control points those are the positions in the middle between the two marks. As soon as an entry * is invalidated in {@link #waypointPositionCache}, it is also invalidated here. Keys always occur * symmetrically under the map's monitor being held, i.e., if (w1, w2) is a cache key then so is * (w2, w1), with equal value. */ private final ConcurrentHashMap, SortedMap> distanceCache; + /** + * Caches minimum distances between the positions cached in {@link #waypointPositionCache}. For two-mark control + * points those distances are the shortest distance between any two mark pairs from the two waypoints. Note that + * this is not the absolute minimum distance to be sailed, e.g., if the two waypoints are lines, where their closest + * approximation point may lie somewhere on the middle of at least one line. As soon as an entry is invalidated in + * {@link #waypointPositionCache}, it is also invalidated here. Keys always occur symmetrically under the map's + * monitor being held, i.e., if (w1, w2) is a cache key then so is (w2, w1), with equal value. + */ + private final ConcurrentHashMap, SortedMap> minimumDistanceCache; + /** * The duration of the time ranges whose center time points serve as keys for the {@link NavigableMap}s used as * values in the {@link #waypointPositionCache} structure. Each cache entry extends half this duration into the past and into the @@ -84,7 +102,9 @@ public class WaypointPositionAndDistanceCache { public WaypointPositionAndDistanceCache(TrackedRace race, Duration timeRangeResolution) { this.trackedRace = race; this.waypointPositionCache = new ConcurrentWeakHashMap<>(); + this.markPositionCache = new ConcurrentWeakHashMap<>(); this.distanceCache = new ConcurrentHashMap<>(); + this.minimumDistanceCache = new ConcurrentHashMap<>(); this.timeRangeResolution = timeRangeResolution; this.waypoints = Collections.synchronizedList(new ArrayList<>()); final Course course = this.trackedRace.getRace().getCourse(); @@ -119,6 +139,12 @@ public class WaypointPositionAndDistanceCache { /* alternateKeySupplier */ null); } + public Position getApproximatePosition(Mark mark, TimePoint timePoint) { + return getApproximateResult(mark, timePoint, markPositionCache, + roundedToTimeRangeCenter->trackedRace.getOrCreateTrack(mark).getEstimatedPosition(roundedToTimeRangeCenter, /* extrapolate */ false), + /* alternateKeySupplier */ null); + } + /** * @param resultCalculator * function that computes the result to be cached, assuming the caller knows the key which it just passed @@ -174,6 +200,27 @@ public class WaypointPositionAndDistanceCache { /* alternate key supplier */ ()->new Pair<>(w2.getControlPoint(), w1.getControlPoint())); } + public Distance getMinimumDistance(Waypoint w1, Waypoint w2, TimePoint timePoint) { + return getApproximateResult(new Pair<>(w1.getControlPoint(), w2.getControlPoint()), timePoint, minimumDistanceCache, + roundedToTimeRangeCenter->{ + Distance minDist = null; + for (final Mark w1m : w1.getMarks()) { + for (final Mark w2m : w2.getMarks()) { + final Position approximatePositionW1m = getApproximatePosition(w1m, roundedToTimeRangeCenter); + final Position approximatePositionW2m = getApproximatePosition(w2m, roundedToTimeRangeCenter); + if (approximatePositionW1m != null && approximatePositionW2m != null) { + final Distance w1mToW2m = approximatePositionW1m.getDistance(approximatePositionW2m); + if (minDist == null || minDist.compareTo(w1mToW2m) > 0) { + minDist = w1mToW2m; + } + } + } + } + return minDist; + }, + /* alternate key supplier */ ()->new Pair<>(w2.getControlPoint(), w1.getControlPoint())); + } + /** * Invalidates those elements of the cache affected by the position fix for the mark. This considers for which time * range the {@link GPSFixTrack#getEstimatedPosition(TimePoint, boolean)} result will be affected by the fix and checks @@ -181,6 +228,10 @@ public class WaypointPositionAndDistanceCache { */ private void invalidate(Mark mark, GPSFix fix) { TimeRange affectedTimeRange = trackedRace.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix); + final SortedMap map = markPositionCache.get(mark); + if (map != null) { + removeAffectedEntries(mark, affectedTimeRange, map, /* entryRemovedCallback */ null); + } synchronized (waypoints) { // it's a synchronized list, so grabbing the list's monitor will grant us exclusive access here for (Waypoint waypoint : waypoints) { if (Util.contains(waypoint.getMarks(), mark)) { @@ -190,34 +241,50 @@ public class WaypointPositionAndDistanceCache { } } - private void invalidate(ControlPoint controlPoint, TimeRange affectedTimeRange) { - SortedMap map = waypointPositionCache.get(controlPoint); + private void invalidate(final ControlPoint controlPoint, TimeRange affectedTimeRange) { + final SortedMap map = waypointPositionCache.get(controlPoint); if (map != null) { - final SortedMap tailMap = map.tailMap(roundToResolution(affectedTimeRange.from())); - synchronized (map) { - for (Iterator> i=tailMap.entrySet().iterator(); i.hasNext(); ) { - final Entry e = i.next(); - final TimePoint timePoint = e.getKey(); - if (timePoint.after(affectedTimeRange.to())) { - break; - } - assert timePoint.equals(roundToResolution(timePoint)); - i.remove(); - synchronized (waypoints) { - for (Waypoint otherWaypoint : waypoints) { - ControlPoint otherControlPoint = otherWaypoint.getControlPoint(); - if (otherControlPoint != controlPoint) { - final Map distanceMap = distanceCache.get(new Pair<>(controlPoint, - otherControlPoint)); - if (distanceMap != null) { - distanceMap.remove(timePoint); - } - final Map otherDistanceMap = distanceCache.get(new Pair<>( - otherControlPoint, controlPoint)); - if (otherDistanceMap != null) { - otherDistanceMap.remove(timePoint); - } - } + removeAffectedEntries(controlPoint, affectedTimeRange, map, this::controlPointCacheMapEntryRemoved); + } + } + + private void removeAffectedEntries(T controlPoint, TimeRange affectedTimeRange, + SortedMap map, EntryRemovedCallback entryRemovedCallback) { + final SortedMap tailMap = map.tailMap(roundToResolution(affectedTimeRange.from())); + synchronized (map) { + for (Iterator> i=tailMap.entrySet().iterator(); i.hasNext(); ) { + final Entry e = i.next(); + final TimePoint timePoint = e.getKey(); + if (timePoint.after(affectedTimeRange.to())) { + break; + } + assert timePoint.equals(roundToResolution(timePoint)); + i.remove(); + if (entryRemovedCallback != null) { + entryRemovedCallback.cacheMapEntryRemoved(controlPoint, timePoint); + } + } + } + } + + @FunctionalInterface + private interface EntryRemovedCallback { + void cacheMapEntryRemoved(T controlPoint, final TimePoint timePoint); + } + + private void controlPointCacheMapEntryRemoved(ControlPoint controlPoint, final TimePoint timePoint) { + synchronized (waypoints) { + for (Waypoint otherWaypoint : waypoints) { + ControlPoint otherControlPoint = otherWaypoint.getControlPoint(); + if (otherControlPoint != controlPoint) { + for (ConcurrentHashMap, SortedMap> cache : Arrays.asList(distanceCache, minimumDistanceCache)) { + final Map distanceMap = cache.get(new Pair<>(controlPoint, otherControlPoint)); + if (distanceMap != null) { + distanceMap.remove(timePoint); + } + final Map otherDistanceMap = cache.get(new Pair<>(otherControlPoint, controlPoint)); + if (otherDistanceMap != null) { + otherDistanceMap.remove(timePoint); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java index 3b30d768dbc..5514307549f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java @@ -475,10 +475,12 @@ public abstract class AbstractRankingMetric implements RankingMetric { TrackedLegOfCompetitor currentLegWho = getTrackedRace().getCurrentLeg(who, timePoint); if (currentLegWho == null) { // already finished or not yet started; if already finished, use last leg final Waypoint lastWaypoint = getTrackedRace().getRace().getCourse().getLastWaypoint(); - final TrackedLeg lastTrackedLeg = getTrackedRace().getTrackedLegFinishingAt(lastWaypoint); - TrackedLegOfCompetitor whosLastTrackedLeg = lastTrackedLeg.getTrackedLeg(who); - if (whosLastTrackedLeg.hasFinishedLeg(timePoint)) { - currentLegWho = whosLastTrackedLeg; + if (lastWaypoint != null) { // could be an empty course + final TrackedLeg lastTrackedLeg = getTrackedRace().getTrackedLegFinishingAt(lastWaypoint); + TrackedLegOfCompetitor whosLastTrackedLeg = lastTrackedLeg.getTrackedLeg(who); + if (whosLastTrackedLeg.hasFinishedLeg(timePoint)) { + currentLegWho = whosLastTrackedLeg; + } } } return currentLegWho; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java index c5015b70cf9..019c61b47b1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java @@ -88,12 +88,16 @@ public class OneDesignRankingMetric extends AbstractRankingMetric { } else { // after passing the next mark, who still needs to travel about the same time that to // traveled after passing the next mark until finishing the race - timeToTookToFinishRaceStartingAtNextMark = - getTrackedRace().getMarkPassing(to, whosNextMark).getTimePoint(). - until(tosLegFinishingTime); + final MarkPassing tosMarkPassingForWhosNextMark = getTrackedRace().getMarkPassing(to, whosNextMark); + timeToTookToFinishRaceStartingAtNextMark = tosMarkPassingForWhosNextMark == null ? null : + tosMarkPassingForWhosNextMark.getTimePoint().until(tosLegFinishingTime); + } + if (timeToTookToFinishRaceStartingAtNextMark == null) { + result = null; + } else { + final TimePoint etaAtFinish = rankingInfo.getTimePoint().plus(etaNextMark.plus(timeToTookToFinishRaceStartingAtNextMark)); + result = tosLegFinishingTime.until(etaAtFinish); } - final TimePoint etaAtFinish = rankingInfo.getTimePoint().plus(etaNextMark.plus(timeToTookToFinishRaceStartingAtNextMark)); - result = tosLegFinishingTime.until(etaAtFinish); } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/regattalike/HasRegattaLike.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/regattalike/HasRegattaLike.java index deb887970fb..9a73f72b3d6 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/regattalike/HasRegattaLike.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/regattalike/HasRegattaLike.java @@ -26,7 +26,8 @@ public interface HasRegattaLike { /** * Determines the combined set of all competitors from all race columns that this object's {@link IsRegattaLike} - * has. + * has, as well as those coming from registrations on the {@link #getRegattaLike()}'s + * {@link IsRegattaLike#getRegattaLog() regatta log}. * * @see #getRegattaLike() * @see IsRegattaLike#getRaceColumnByName(String) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index 3c4177fce24..49f07eb9da6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -14,10 +14,39 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sse.common.TimePoint; public interface DynamicTrackedRace extends TrackedRace { - void recordFix(Competitor competitor, GPSFixMoving fix); + /** + * Records a position and speed and course over ground fix for a competitor, but only if the fix's {@link GPSFixMoving#getTimePoint()} + * is within this race's {@link #getStartOfTracking() start} and {@link #getEndOfTracking() end} of tracking time interval. + */ + default void recordFix(Competitor competitor, GPSFixMoving fix) { + recordFix(competitor, fix, /* onlyWhenInTrackingTimeInterval */ true); + } + + /** + * Records a position and speed and course over ground fix for a competitor. If + * {@code onlyWhenInTrackingTimeInterval} is {@code true}, the fix is recorded only if the fix's + * {@link GPSFixMoving#getTimePoint()} is within this race's {@link #getStartOfTracking() start} and + * {@link #getEndOfTracking() end} of tracking time interval. If {@code onlyWhenInTrackingTimeInterval} is + * {@code false}, the fix is recorded regardless of this race's tracking times interval. + */ + void recordFix(Competitor competitor, GPSFixMoving fix, boolean onlyWhenInTrackingTimeInterval); - void recordFix(Mark mark, GPSFix fix); + /** + * Records a position fix for a mark, but only if the fix's {@link GPSFixMoving#getTimePoint()} is within this + * race's {@link #getStartOfTracking() start} and {@link #getEndOfTracking() end} of tracking time interval. + */ + default void recordFix(Mark mark, GPSFix fix) { + recordFix(mark, fix, /* onlyWhenInTrackingTimeInterval */ true); + } + /** + * Records a position fix for a mark. If {@code onlyWhenInTrackingTimeInterval} is {@code true}, the fix is recorded + * only if the fix's {@link GPSFixMoving#getTimePoint()} is within this race's {@link #getStartOfTracking() start} + * and {@link #getEndOfTracking() end} of tracking time interval. If {@code onlyWhenInTrackingTimeInterval} is + * {@code false}, the fix is recorded regardless of this race's tracking times interval. + */ + void recordFix(Mark mark, GPSFix fix, boolean onlyWhenInTrackingTimeInterval); + /** * Inserts a wind fix into a {@link WindTrack} for the windSource if the current filtering * rules accept the wind fix. Filtering applies based upon timing considerations, assuming that wind fixes are not @@ -128,10 +157,12 @@ public interface DynamicTrackedRace extends TrackedRace { void onCourseDesignChangedByRaceCommittee(CourseBase courseDesign); void onStartTimeChangedByRaceCommittee(TimePoint newStartTime); + void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime); void onAbortedByRaceCommittee(Flags flag); void invalidateStartTime(); void invalidateEndTime(); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index a9ec7959111..77cd1df350c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -36,6 +36,7 @@ import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RacingProcedureType; @@ -870,7 +871,11 @@ public interface TrackedRace extends Serializable, IsManagedByCache getTrackingTimesFromRaceLogs() { + default Pair getTrackingTimesFromRaceLogs() { + return null; + } + + default Pair getStartAndFinishedTimeFromRaceLogs() { return null; } @@ -883,4 +888,15 @@ public interface TrackedRace extends Serializable, IsManagedByCache getMarksFromRegattaLogs() { return getMarks(); } + + /** + * Updates the start and end of tracking in the following precedence order: + * + *

    + *
  1. start/end of tracking in Racelog
  2. + *
  3. manually set start/end of tracking via {@link #setStartOfTrackingReceived(TimePoint, boolean)} and {@link #setEndOfTrackingReceived(TimePoint, boolean)}
  4. + *
  5. start/end of race in Racelog +/- TRACKING_BUFFER_IN_MINUTES
  6. + *
+ */ + public void updateStartAndEndOfTracking(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index c6b2997f97c..adb90c43932 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -156,8 +156,8 @@ DynamicTrackedRace, GPSTrackListener { } @Override - public void recordFix(Competitor competitor, GPSFixMoving fix) { - if (isFixWithinStartAndEndOfTracking(fix.getTimePoint())) { + public void recordFix(Competitor competitor, GPSFixMoving fix, boolean onlyWhenInTrackingTimesInterval) { + if (!onlyWhenInTrackingTimesInterval || isFixWithinStartAndEndOfTracking(fix.getTimePoint())) { DynamicGPSFixTrack track = getTrack(competitor); if (track != null) { if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { @@ -176,9 +176,9 @@ DynamicTrackedRace, GPSTrackListener { } @Override - public void recordFix(Mark mark, GPSFix fix) { + public void recordFix(Mark mark, GPSFix fix, boolean onlyWhenInTrackingTimeInterval) { final TimePoint fixTimePoint = fix.getTimePoint(); - if (isFixWithinStartAndEndOfTracking(fixTimePoint)) { + if (!onlyWhenInTrackingTimeInterval || isFixWithinStartAndEndOfTracking(fixTimePoint)) { getOrCreateTrack(mark).addGPSFix(fix); } } @@ -308,6 +308,7 @@ DynamicTrackedRace, GPSTrackListener { if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); } + updateStartAndEndOfTracking(); } @Override @@ -978,6 +979,13 @@ DynamicTrackedRace, GPSTrackListener { } catch (IOException e) { logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); } + updateStartAndEndOfTracking(); + } + + @Override + public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + logger.info("Finished time of race "+getRace().getName()+" updated by race committee to "+newFinishedTime); + updateStartAndEndOfTracking(); } @Override @@ -1010,5 +1018,4 @@ DynamicTrackedRace, GPSTrackListener { public DynamicGPSFixTrack getTrack(Mark mark) { return (DynamicGPSFixTrack) super.getTrack(mark); } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index 2c27235f626..cc546138856 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmed import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; @@ -22,7 +23,6 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.MarkPassingDataFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.WindFixesFinder; import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor; import com.sap.sailing.domain.base.Competitor; @@ -31,12 +31,12 @@ import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.markpassingcalculation.MarkPassingUpdateListener; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.RaceLogWindFixDeclinationHelper; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; /** @@ -78,10 +78,6 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { return new LastPublishedCourseDesignFinder(raceLog); } - private TrackingTimesFinder createTrackingTimesFinder(RaceLog raceLog) { - return new TrackingTimesFinder(raceLog); - } - private AbortingFlagFinder createAbortingFlagFinder(RaceLog raceLog) { return new AbortingFlagFinder(raceLog); } @@ -154,7 +150,6 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { private void analyze(RaceLog raceLog) { analyzeCourseDesign(null); initializeWindTrack(raceLog); - analyseTrackingTimes(raceLog); if (markPassingUpdateListener != null) { markPassingDataFinder = new MarkPassingDataFinder(raceLog); analyzeMarkPassings(); @@ -162,14 +157,6 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { trackedRace.updateMarkPassingsAfterRaceLogChanges(); } - private void analyseTrackingTimes(RaceLog raceLog) { - Pair times = createTrackingTimesFinder(raceLog).analyze(); - if (times != null) { - trackedRace.setStartOfTrackingReceived(times.getA()); - trackedRace.setEndOfTrackingReceived(times.getB()); - } - } - private void analyzeCourseDesign(CourseBase courseBaseProvidedByEvent) { CourseBase courseDesign = null; for (RaceLog raceLog : raceLogs) { @@ -319,11 +306,18 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { @Override public void visit(RaceLogStartOfTrackingEvent event) { - trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint()); + trackedRace.updateStartAndEndOfTracking(); } @Override public void visit(RaceLogEndOfTrackingEvent event) { - trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint()); + trackedRace.updateStartAndEndOfTracking(); + } + + @Override + public void visit(RaceLogRaceStatusEvent event) { + if (event.getNextStatus().equals(RaceLogRaceStatus.FINISHED)){ + trackedRace.invalidateEndTime(); + } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 63bb88be797..487c6081ce4 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -40,6 +40,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder; @@ -83,6 +84,7 @@ import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.confidence.BearingWithConfidence; import com.sap.sailing.domain.common.confidence.BearingWithConfidenceCluster; import com.sap.sailing.domain.common.confidence.HasConfidence; @@ -156,6 +158,8 @@ import difflib.Patch; import difflib.PatchFailedException; public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials implements CourseListener { + public static final int TRACKING_BUFFER_IN_MINUTES = 5; + private static final long serialVersionUID = -4825546964220003507L; private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName()); @@ -193,6 +197,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl */ private TimePoint endOfTrackingReceived; + /** + * The start and end of tracking inferred via RaceLog, the received timepoint (see above) and mapping intervals. + * For the precedence order see {@link #updateStartAndEndOfTracking()}. + */ + private TimePoint startOfTracking; + private TimePoint endOfTracking; + /** * Race start time as announced by the tracking infrastructure */ @@ -728,27 +739,85 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public TimePoint getStartOfTracking() { - return startOfTrackingReceived; + return startOfTracking; + } + + /** + * Updates the start and end of tracking in the following precedence order: + * + *
    + *
  1. start/end of tracking in Racelog
  2. + *
  3. manually set start/end of tracking via {@link #setStartOfTrackingReceived(TimePoint, boolean)} and {@link #setEndOfTrackingReceived(TimePoint, boolean)}
  4. + *
  5. start/end of race in Racelog +/- TRACKING_BUFFER_IN_MINUTES
  6. + *
+ */ + public void updateStartAndEndOfTracking() { + final Pair trackingTimesFromRaceLog = this.getTrackingTimesFromRaceLogs(); + final Pair startAndFinishedTimesFromRaceLog = this.getStartAndFinishedTimeFromRaceLogs(); + TimePoint oldStartOfTracking = getStartOfTracking(); + TimePoint oldEndOfTracking = getEndOfTracking(); + boolean startOfTrackingFound = false; + boolean endOfTrackingFound = false; + // check race log + if (trackingTimesFromRaceLog != null){ + if (trackingTimesFromRaceLog.getA() != null) { + startOfTracking = trackingTimesFromRaceLog.getA().getTimePoint(); + startOfTrackingFound = true; + } + if (trackingTimesFromRaceLog.getB() != null) { + endOfTracking = trackingTimesFromRaceLog.getB().getTimePoint(); + endOfTrackingFound = true; + } + } + // check "received" variants coming from a connector directly + if (!startOfTrackingFound || !endOfTrackingFound) { + if (startOfTrackingReceived != null && !startOfTrackingFound) { + startOfTrackingFound = true; + startOfTracking = startOfTrackingReceived; + } + if (endOfTrackingReceived != null && !endOfTrackingFound) { + endOfTrackingFound = true; + endOfTracking = endOfTrackingReceived; + } + } + // check for start/finished times in race log and add a few minutes on the ends + if (!startOfTrackingFound || !endOfTrackingFound) { + if (startAndFinishedTimesFromRaceLog != null) { + if (!startOfTrackingFound && startAndFinishedTimesFromRaceLog.getA() != null){ + startOfTracking = startAndFinishedTimesFromRaceLog.getA().minus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)); + startOfTrackingFound = true; + } + if (!endOfTrackingFound && startAndFinishedTimesFromRaceLog.getB() != null){ + endOfTracking = startAndFinishedTimesFromRaceLog.getB().plus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)); + endOfTrackingFound = true; + } + } + } + startOfTrackingChanged(oldStartOfTracking, false); + endOfTrackingChanged(oldEndOfTracking, false); } @Override public TimePoint getEndOfTracking() { - return endOfTrackingReceived; + return endOfTracking; } public void invalidateStartTime() { startTime = null; startTimeWithoutInferenceFromStartMarkPassings = null; + updateStartAndEndOfTracking(); } public void invalidateEndTime() { endTime = null; + updateStartAndEndOfTracking(); } protected void invalidateMarkPassingTimes() { synchronized (markPassingsTimes) { markPassingsTimes.clear(); } + updateStartAndEndOfTracking(); } /** @@ -757,18 +826,34 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl * tracking specification (see bug 3196). This method uses the {@link TrackingTimesFinder} to * analyze all {@link #attachedRaceLogs race logs attached} to find tracking times specifications. * If no tracking times specification is found at all, null is returned. Note that - * even when a valid pair is returned, the components may be null. + * even when a valid pair is returned, the components may be null. This may either + * indicate that no event for that part of the tracking interval was found, or that an event + * was found that explicitly specified {@code null} to force an open interval on that end. */ @Override - public Pair getTrackingTimesFromRaceLogs() { + public Pair getTrackingTimesFromRaceLogs() { for (final RaceLog raceLog : attachedRaceLogs.values()) { - Pair result = new TrackingTimesFinder(raceLog).analyze(); + Pair result = new TrackingTimesFinder(raceLog).analyze(); if (result != null) { return result; } } return null; } + + @Override + public Pair getStartAndFinishedTimeFromRaceLogs() { + //Only one of the RaceLogs should have valid data, so one doesn't have to compare all the + //start/finished time values in order to find the earliest startTime/latestFinishedTime + for (final RaceLog raceLog : attachedRaceLogs.values()) { + TimePoint startTime = new StartTimeFinder(raceLogResolver, raceLog).analyze().getStartTime(); + TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); + if (startTime != null || finishedTime != null){ + return new Util.Pair(startTime, finishedTime); + } + } + return null; + } /** * Calculates the start time of the race from various sources. The highest precedence take the {@link #attachedRaceLogs race logs}, @@ -1842,12 +1927,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } protected void setStartOfTrackingReceived(final TimePoint startOfTracking, final boolean waitForGPSFixesToLoad) { - final TimePoint oldStartOfTracking = this.startOfTrackingReceived; + final TimePoint oldStartOfTracking = getStartOfTracking(); this.startOfTrackingReceived = startOfTracking; - if (!Util.equalsWithNull(oldStartOfTracking, startOfTracking) && - (startOfTracking == null || (oldStartOfTracking != null && startOfTracking.before(oldStartOfTracking)))) { - logger.info("Loading fixes after start of tracking for "+getRace()+" has been extended from "+oldStartOfTracking+" to "+startOfTracking); - loadGPSFixesForExtendedTimeRange(startOfTracking, oldStartOfTracking, waitForGPSFixesToLoad); + updateStartAndEndOfTracking(); + startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad); + } + + private void startOfTrackingChanged(final TimePoint oldStartOfTracking, boolean waitForGPSFixesToLoad){ + TimePoint inferedStartOfTracking = getStartOfTracking(); + if (!Util.equalsWithNull(oldStartOfTracking, inferedStartOfTracking) && + (inferedStartOfTracking == null || (oldStartOfTracking != null && inferedStartOfTracking.before(oldStartOfTracking)))) { + logger.info("Loading fixes after start of tracking for "+getRace()+" has been extended from "+oldStartOfTracking+" to "+inferedStartOfTracking); + loadGPSFixesForExtendedTimeRange(inferedStartOfTracking, oldStartOfTracking, waitForGPSFixesToLoad); } } @@ -1861,11 +1952,19 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl protected void setEndOfTrackingReceived(final TimePoint endOfTracking, final boolean waitForGPSFixesToLoad) { final TimePoint oldEndOfTracking = this.endOfTrackingReceived; this.endOfTrackingReceived = endOfTracking; - if (!Util.equalsWithNull(oldEndOfTracking, endOfTracking) && - (endOfTracking == null || (oldEndOfTracking != null && endOfTracking.after(oldEndOfTracking)))) { - logger.info("Loading fixes after end of tracking for "+getRace()+" has been extended from "+oldEndOfTracking+" to "+endOfTracking); - loadGPSFixesForExtendedTimeRange(oldEndOfTracking, endOfTracking, waitForGPSFixesToLoad); + updateStartAndEndOfTracking(); + endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad); + } + + private void endOfTrackingChanged(final TimePoint oldEndOfTracking, boolean waitForGPSFixesToLoad){ + TimePoint inferedEndOfTracking = getEndOfTracking(); + + if (!Util.equalsWithNull(oldEndOfTracking, inferedEndOfTracking) && + (inferedEndOfTracking == null || (oldEndOfTracking != null && inferedEndOfTracking.after(oldEndOfTracking)))) { + logger.info("Loading fixes after end of tracking for "+getRace()+" has been extended from "+oldEndOfTracking+" to "+inferedEndOfTracking); + loadGPSFixesForExtendedTimeRange(oldEndOfTracking, inferedEndOfTracking, waitForGPSFixesToLoad); } + } /** @@ -3341,6 +3440,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl synchronized (TrackedRaceImpl.this) { attachedRaceLogs.put(raceLog.getId(), raceLog); notifyAll(); + updateStartAndEndOfTracking(); } } @@ -3394,6 +3494,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } catch (InterruptedException e) { logger.log(Level.WARNING, "Interrupted while waiting for race log being attached", e); } + updateStartAndEndOfTracking(); } @Override diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index dd6184140a1..12e6cbee047 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -247,6 +247,13 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> + + boatClassIconsMap; - private static BoatClassImageResources imageResources = BoatClassImageResources.INSTANCE; - - static { - boatClassIconsMap = new HashMap(); - - // handicap sailing icons - boatClassIconsMap.put(BoatClassMasterdata.ORC.getDisplayName(), imageResources.ORCIcon()); - boatClassIconsMap.put(BoatClassMasterdata.ORC_CLUB.getDisplayName(), imageResources.ORCIcon()); - boatClassIconsMap.put(BoatClassMasterdata.ORC_INTERNATIONAL.getDisplayName(), imageResources.ORCIcon()); - - boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon()); - boatClassIconsMap.put(BoatClassMasterdata._18Footer.getDisplayName(), imageResources._18FooterIcon()); - boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon()); - boatClassIconsMap.put(BoatClassMasterdata._29ER.getDisplayName(), imageResources._29erIcon()); - boatClassIconsMap.put(BoatClassMasterdata._420.getDisplayName(), imageResources._420Icon()); - boatClassIconsMap.put(BoatClassMasterdata._470.getDisplayName(), imageResources._470Icon()); - boatClassIconsMap.put(BoatClassMasterdata._49ERFX.getDisplayName(), imageResources._49erFXIcon()); - boatClassIconsMap.put(BoatClassMasterdata._49ER.getDisplayName(), imageResources._49erIcon()); - boatClassIconsMap.put(BoatClassMasterdata._5O5.getDisplayName(), imageResources._505mIcon()); - boatClassIconsMap.put(BoatClassMasterdata.A_CAT.getDisplayName(), imageResources.A_CatIcon()); - boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon()); - boatClassIconsMap.put(BoatClassMasterdata.B_ONE.getDisplayName(), imageResources.BOne()); - boatClassIconsMap.put(BoatClassMasterdata.CANOE_IC.getDisplayName(), imageResources.CanoeICIcon()); - boatClassIconsMap.put(BoatClassMasterdata.CANOE_TAIFUN.getDisplayName(), imageResources.CanoeTaifunIcon()); - boatClassIconsMap.put(BoatClassMasterdata.CONTENDER.getDisplayName(), imageResources.ContenderIcon()); - boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon()); - boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon()); - boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon()); - boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon()); - boatClassIconsMap.put(BoatClassMasterdata.D_ONE.getDisplayName(), imageResources.DOneIcon()); - boatClassIconsMap.put(BoatClassMasterdata.D_35.getDisplayName(), imageResources.D35Icon()); - boatClassIconsMap.put(BoatClassMasterdata.F_16.getDisplayName(), imageResources.F16Icon()); - boatClassIconsMap.put(BoatClassMasterdata.F_18.getDisplayName(), imageResources.F18Icon()); - boatClassIconsMap.put(BoatClassMasterdata.FARR_30.getDisplayName(), imageResources.Farr30Icon()); - boatClassIconsMap.put(BoatClassMasterdata.FINN.getDisplayName(), imageResources.FinnIcon()); - boatClassIconsMap.put(BoatClassMasterdata.FLYING_DUTCHMAN.getDisplayName(), imageResources.FlyingDutchmanIcon()); - boatClassIconsMap.put(BoatClassMasterdata.FOLKBOAT.getDisplayName(), imageResources.FolkBoatIcon()); - boatClassIconsMap.put(BoatClassMasterdata.FUN.getDisplayName(), imageResources.FUNIcon()); - boatClassIconsMap.put(BoatClassMasterdata.GC_32.getDisplayName(), imageResources.GC32Icon()); - boatClassIconsMap.put(BoatClassMasterdata.H_BOAT.getDisplayName(), imageResources.H_BoatIcon()); - boatClassIconsMap.put(BoatClassMasterdata.HOBIE_16.getDisplayName(), imageResources.HobieIcon()); - boatClassIconsMap.put(BoatClassMasterdata.HOBIE_TIGER.getDisplayName(), imageResources.HobieIcon()); - boatClassIconsMap.put(BoatClassMasterdata.HOBIE_WILD_CAT.getDisplayName(), imageResources.HobieIcon()); - boatClassIconsMap.put(BoatClassMasterdata.INTERNATIONAL_14.getDisplayName(), imageResources.International14Icon()); - boatClassIconsMap.put(BoatClassMasterdata.J22.getDisplayName(), imageResources.J22Icon()); - boatClassIconsMap.put(BoatClassMasterdata.J24.getDisplayName(), imageResources.J24Icon()); - boatClassIconsMap.put(BoatClassMasterdata.J70.getDisplayName(), imageResources.J70Icon()); - boatClassIconsMap.put(BoatClassMasterdata.J80.getDisplayName(), imageResources.J80Icon()); - boatClassIconsMap.put(BoatClassMasterdata.KIELZUGVOGEL.getDisplayName(), imageResources.KielzugvogelIcon()); - boatClassIconsMap.put(BoatClassMasterdata.LASER_2.getDisplayName(), imageResources.Laser2Icon()); - boatClassIconsMap.put(BoatClassMasterdata.LASER_SB3.getDisplayName(), imageResources.LaserSB3Icon()); - boatClassIconsMap.put(BoatClassMasterdata.LASER_INT.getDisplayName(), imageResources.LaserIcon()); - boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.LaserIcon()); - boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserIcon()); - boatClassIconsMap.put(BoatClassMasterdata.LAGO_26.getDisplayName(), imageResources.Lago26Icon()); - boatClassIconsMap.put(BoatClassMasterdata.MELGES_24.getDisplayName(), imageResources.Melges24Icon()); - boatClassIconsMap.put(BoatClassMasterdata.MINI_TRANSAT.getDisplayName(), imageResources.MiniTransatIcon()); - boatClassIconsMap.put(BoatClassMasterdata.MUSTO_SKIFF.getDisplayName(), imageResources.MustoSkiffIcon()); - boatClassIconsMap.put(BoatClassMasterdata.NACRA_17.getDisplayName(), imageResources.Nacra17Icon()); - boatClassIconsMap.put(BoatClassMasterdata.O_JOLLE.getDisplayName(), imageResources.OJolleIcon()); - boatClassIconsMap.put(BoatClassMasterdata.OK.getDisplayName(), imageResources.OKIcon()); - boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon()); - boatClassIconsMap.put(BoatClassMasterdata.OPTIMIST.getDisplayName(), imageResources.OptimistIcon()); - boatClassIconsMap.put(BoatClassMasterdata.PIRATE.getDisplayName(), imageResources.PiratIcon()); - boatClassIconsMap.put(BoatClassMasterdata.PLATU_25.getDisplayName(), imageResources.Platu25Icon()); - boatClassIconsMap.put(BoatClassMasterdata.PWA.getDisplayName(), imageResources.PWAIcon()); - boatClassIconsMap.put(BoatClassMasterdata.RS800.getDisplayName(), imageResources.RS800Icon()); - boatClassIconsMap.put(BoatClassMasterdata.RS_X.getDisplayName(), imageResources.RSXIcon()); - boatClassIconsMap.put(BoatClassMasterdata.RS_FEVA.getDisplayName(), imageResources.RSFevaIcon()); - boatClassIconsMap.put(BoatClassMasterdata.SONAR.getDisplayName(), imageResources.SonarIcon()); - boatClassIconsMap.put(BoatClassMasterdata.SOLING.getDisplayName(), imageResources.SolingIcon()); - boatClassIconsMap.put(BoatClassMasterdata.STAR.getDisplayName(), imageResources.StarIcon()); - boatClassIconsMap.put(BoatClassMasterdata.STREAMLINE.getDisplayName(), imageResources.StreamlineIcon()); - boatClassIconsMap.put(BoatClassMasterdata.SWAN_45.getDisplayName(), imageResources.Swan45Icon()); - boatClassIconsMap.put(BoatClassMasterdata.TORNADO.getDisplayName(), imageResources.TornadoIcon()); - boatClassIconsMap.put(BoatClassMasterdata.TRIAS.getDisplayName(), imageResources.TriasIcon()); - boatClassIconsMap.put(BoatClassMasterdata.X_99.getDisplayName(), imageResources.X99Icon()); - } - - public static ImageResource getBoatClassIconResource(String displayName) { - ImageResource imageResource = boatClassIconsMap.get(displayName); - if(imageResource == null) { - imageResource = BoatClassImageResources.INSTANCE.genericBoatClass(); - } - return imageResource; - } -} +package com.sap.sailing.gwt.common.client; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.resources.client.ImageResource; +import com.sap.sailing.domain.common.BoatClassMasterdata; + +/** + * All images are expected to be 140x140px with transparent background. + * + * @author Steffen Tobias Wagner, Axel Uhl (D043530) + * + */ +public class BoatClassImageResolver { + private static Map boatClassIconsMap; + private static BoatClassImageResources imageResources = BoatClassImageResources.INSTANCE; + + static { + boatClassIconsMap = new HashMap(); + + // handicap sailing icons + boatClassIconsMap.put(BoatClassMasterdata.ORC.getDisplayName(), imageResources.ORCIcon()); + boatClassIconsMap.put(BoatClassMasterdata.ORC_CLUB.getDisplayName(), imageResources.ORCIcon()); + boatClassIconsMap.put(BoatClassMasterdata.ORC_INTERNATIONAL.getDisplayName(), imageResources.ORCIcon()); + + boatClassIconsMap.put(BoatClassMasterdata.PHRF.getDisplayName(), imageResources.PHRFIcon()); + + boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon()); + boatClassIconsMap.put(BoatClassMasterdata._18Footer.getDisplayName(), imageResources._18FooterIcon()); + boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon()); + boatClassIconsMap.put(BoatClassMasterdata._29ER.getDisplayName(), imageResources._29erIcon()); + boatClassIconsMap.put(BoatClassMasterdata._420.getDisplayName(), imageResources._420Icon()); + boatClassIconsMap.put(BoatClassMasterdata._470.getDisplayName(), imageResources._470Icon()); + boatClassIconsMap.put(BoatClassMasterdata._49ERFX.getDisplayName(), imageResources._49erFXIcon()); + boatClassIconsMap.put(BoatClassMasterdata._49ER.getDisplayName(), imageResources._49erIcon()); + boatClassIconsMap.put(BoatClassMasterdata._5O5.getDisplayName(), imageResources._505mIcon()); + boatClassIconsMap.put(BoatClassMasterdata.A_CAT.getDisplayName(), imageResources.A_CatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon()); + boatClassIconsMap.put(BoatClassMasterdata.B_ONE.getDisplayName(), imageResources.BOne()); + boatClassIconsMap.put(BoatClassMasterdata.CANOE_IC.getDisplayName(), imageResources.CanoeICIcon()); + boatClassIconsMap.put(BoatClassMasterdata.CANOE_TAIFUN.getDisplayName(), imageResources.CanoeTaifunIcon()); + boatClassIconsMap.put(BoatClassMasterdata.CONTENDER.getDisplayName(), imageResources.ContenderIcon()); + boatClassIconsMap.put(BoatClassMasterdata.CC_30.getDisplayName(), imageResources.CC30Icon()); + boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon()); + boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon()); + boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon()); + boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon()); + boatClassIconsMap.put(BoatClassMasterdata.D_ONE.getDisplayName(), imageResources.DOneIcon()); + boatClassIconsMap.put(BoatClassMasterdata.D_35.getDisplayName(), imageResources.D35Icon()); + boatClassIconsMap.put(BoatClassMasterdata.F_16.getDisplayName(), imageResources.F16Icon()); + boatClassIconsMap.put(BoatClassMasterdata.F_18.getDisplayName(), imageResources.F18Icon()); + boatClassIconsMap.put(BoatClassMasterdata.FARR_30.getDisplayName(), imageResources.Farr30Icon()); + boatClassIconsMap.put(BoatClassMasterdata.FARR_280.getDisplayName(), imageResources.Farr280Icon()); + boatClassIconsMap.put(BoatClassMasterdata.FINN.getDisplayName(), imageResources.FinnIcon()); + boatClassIconsMap.put(BoatClassMasterdata.FLYING_DUTCHMAN.getDisplayName(), imageResources.FlyingDutchmanIcon()); + boatClassIconsMap.put(BoatClassMasterdata.FOLKBOAT.getDisplayName(), imageResources.FolkBoatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.FUN.getDisplayName(), imageResources.FUNIcon()); + boatClassIconsMap.put(BoatClassMasterdata.GC_32.getDisplayName(), imageResources.GC32Icon()); + boatClassIconsMap.put(BoatClassMasterdata.GP_26.getDisplayName(), imageResources.GP26Icon()); + boatClassIconsMap.put(BoatClassMasterdata.H_BOAT.getDisplayName(), imageResources.H_BoatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.HOBIE_16.getDisplayName(), imageResources.HobieIcon()); + boatClassIconsMap.put(BoatClassMasterdata.HOBIE_TIGER.getDisplayName(), imageResources.HobieIcon()); + boatClassIconsMap.put(BoatClassMasterdata.HOBIE_WILD_CAT.getDisplayName(), imageResources.HobieIcon()); + boatClassIconsMap.put(BoatClassMasterdata.INTERNATIONAL_14.getDisplayName(), imageResources.International14Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J22.getDisplayName(), imageResources.J22Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J24.getDisplayName(), imageResources.J24Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J70.getDisplayName(), imageResources.J70Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J80.getDisplayName(), imageResources.J80Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J88.getDisplayName(), imageResources.J88Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J105.getDisplayName(), imageResources.J105Icon()); + boatClassIconsMap.put(BoatClassMasterdata.J111.getDisplayName(), imageResources.J111Icon()); + boatClassIconsMap.put(BoatClassMasterdata.KIELZUGVOGEL.getDisplayName(), imageResources.KielzugvogelIcon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_2.getDisplayName(), imageResources.Laser2Icon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_SB3.getDisplayName(), imageResources.LaserSB3Icon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_INT.getDisplayName(), imageResources.LaserIcon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.LaserIcon()); + boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserIcon()); + boatClassIconsMap.put(BoatClassMasterdata.LAGO_26.getDisplayName(), imageResources.Lago26Icon()); + boatClassIconsMap.put(BoatClassMasterdata.MELGES_20.getDisplayName(), imageResources.Melges20Icon()); + boatClassIconsMap.put(BoatClassMasterdata.MELGES_24.getDisplayName(), imageResources.Melges24Icon()); + boatClassIconsMap.put(BoatClassMasterdata.MINI_TRANSAT.getDisplayName(), imageResources.MiniTransatIcon()); + boatClassIconsMap.put(BoatClassMasterdata.MUSTO_SKIFF.getDisplayName(), imageResources.MustoSkiffIcon()); + boatClassIconsMap.put(BoatClassMasterdata.NACRA_17.getDisplayName(), imageResources.Nacra17Icon()); + boatClassIconsMap.put(BoatClassMasterdata.O_JOLLE.getDisplayName(), imageResources.OJolleIcon()); + boatClassIconsMap.put(BoatClassMasterdata.OK.getDisplayName(), imageResources.OKIcon()); + boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon()); + boatClassIconsMap.put(BoatClassMasterdata.OPTIMIST.getDisplayName(), imageResources.OptimistIcon()); + boatClassIconsMap.put(BoatClassMasterdata.PIRATE.getDisplayName(), imageResources.PiratIcon()); + boatClassIconsMap.put(BoatClassMasterdata.PLATU_25.getDisplayName(), imageResources.Platu25Icon()); + boatClassIconsMap.put(BoatClassMasterdata.PWA.getDisplayName(), imageResources.PWAIcon()); + boatClassIconsMap.put(BoatClassMasterdata.RS800.getDisplayName(), imageResources.RS800Icon()); + boatClassIconsMap.put(BoatClassMasterdata.RS_X.getDisplayName(), imageResources.RSXIcon()); + boatClassIconsMap.put(BoatClassMasterdata.RS_FEVA.getDisplayName(), imageResources.RSFevaIcon()); + boatClassIconsMap.put(BoatClassMasterdata.SONAR.getDisplayName(), imageResources.SonarIcon()); + boatClassIconsMap.put(BoatClassMasterdata.SOLING.getDisplayName(), imageResources.SolingIcon()); + boatClassIconsMap.put(BoatClassMasterdata.STAR.getDisplayName(), imageResources.StarIcon()); + boatClassIconsMap.put(BoatClassMasterdata.STREAMLINE.getDisplayName(), imageResources.StreamlineIcon()); + boatClassIconsMap.put(BoatClassMasterdata.SWAN_45.getDisplayName(), imageResources.Swan45Icon()); + boatClassIconsMap.put(BoatClassMasterdata.TARTAN_10.getDisplayName(), imageResources.Tartan10Icon()); + boatClassIconsMap.put(BoatClassMasterdata.TORNADO.getDisplayName(), imageResources.TornadoIcon()); + boatClassIconsMap.put(BoatClassMasterdata.TRIAS.getDisplayName(), imageResources.TriasIcon()); + boatClassIconsMap.put(BoatClassMasterdata.VIPER_640.getDisplayName(), imageResources.Viper640Icon()); + boatClassIconsMap.put(BoatClassMasterdata.VX_ONE.getDisplayName(), imageResources.VXOneIcon()); + boatClassIconsMap.put(BoatClassMasterdata.X_99.getDisplayName(), imageResources.X99Icon()); + } + + public static ImageResource getBoatClassIconResource(String displayName) { + ImageResource imageResource = boatClassIconsMap.get(displayName); + if(imageResource == null) { + imageResource = BoatClassImageResources.INSTANCE.genericBoatClass(); + } + return imageResource; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java index f11d0e05012..a6e47667273 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java @@ -1,204 +1,239 @@ -package com.sap.sailing.gwt.common.client; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.ImageResource; - -public interface BoatClassImageResources extends ClientBundle { - public static final BoatClassImageResources INSTANCE = GWT.create(BoatClassImageResources.class); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GENERIC_BOATCLASS.png") - ImageResource genericBoatClass(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/12MR.png") - ImageResource _12mRIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/18FOOTER.png") - ImageResource _18FooterIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/2_4M.png") - ImageResource _2_4mIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/29ER.png") - ImageResource _29erIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/420.png") - ImageResource _420Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/470.png") - ImageResource _470Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png") - ImageResource _49erFXIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png") - ImageResource _49erIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/505.png") - ImageResource _505mIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png") - ImageResource A_CatIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ALBIN_EXPRESS.png") - ImageResource AlbinExpressIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/B_ONE.png") - ImageResource BOne(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CANOE_IC.png") - ImageResource CanoeICIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CANOE_TAIFUN.png") - ImageResource CanoeTaifunIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CONTENDER.png") - ImageResource ContenderIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/DRAGON.png") - ImageResource DragonIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE.png") - ImageResource EuropeIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png") - ImageResource Extreme40Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/D35.png") - ImageResource D35Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/F16.png") - ImageResource F16Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/F18.png") - ImageResource F18Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png") - ImageResource Farr30Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png") - ImageResource FinnIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png") - ImageResource FlyingDutchmanIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FOLKBOAT.png") - ImageResource FolkBoatIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FUN.png") - ImageResource FUNIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/H_BOAT.png") - ImageResource H_BoatIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/HOBIE.png") - ImageResource HobieIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J22.png") - ImageResource J22Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J24.png") - ImageResource J24Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J70.png") - ImageResource J70Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J80.png") - ImageResource J80Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER2.png") - ImageResource Laser2Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER_SB3.png") - ImageResource LaserSB3Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png") - ImageResource LaserIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LAGO_26.png") - ImageResource Lago26Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png") - ImageResource Melges24Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MINI_TRANSAT.png") - ImageResource MiniTransatIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MUSTO_SKIFF.png") - ImageResource MustoSkiffIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/NACRA_17.png") - ImageResource Nacra17Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OK.png") - ImageResource OKIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png") - ImageResource OptimistIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PIRAT.png") - ImageResource PiratIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PLATU_25.png") - ImageResource Platu25Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS_X.png") - ImageResource RSXIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS_FEVA.png") - ImageResource RSFevaIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SONAR.png") - ImageResource SonarIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SOLING.png") - ImageResource SolingIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/STAR.png") - ImageResource StarIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/STREAMLINE.png") - ImageResource StreamlineIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SWAN_45.png") - ImageResource Swan45Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TORNADO.png") - ImageResource TornadoIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/X_99.png") - ImageResource X99Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/DYAS.png") - ImageResource DyasIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/INTERNATIONAL_14.png") - ImageResource International14Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPENBIC.png") - ImageResource OpenBicIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS800.png") - ImageResource RS800Icon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png") - ImageResource ORCIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/KIELZUGVOGEL.png") - ImageResource KielzugvogelIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TRIAS.png") - ImageResource TriasIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/O_JOLLE.png") - ImageResource OJolleIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/D_ONE.png") - ImageResource DOneIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PWA.png") - ImageResource PWAIcon(); - - @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GC_32.png") - ImageResource GC32Icon(); -} +package com.sap.sailing.gwt.common.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; + +public interface BoatClassImageResources extends ClientBundle { + public static final BoatClassImageResources INSTANCE = GWT.create(BoatClassImageResources.class); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GENERIC_BOATCLASS.png") + ImageResource genericBoatClass(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/12MR.png") + ImageResource _12mRIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/18FOOTER.png") + ImageResource _18FooterIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/2_4M.png") + ImageResource _2_4mIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/29ER.png") + ImageResource _29erIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/420.png") + ImageResource _420Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/470.png") + ImageResource _470Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png") + ImageResource _49erFXIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png") + ImageResource _49erIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/505.png") + ImageResource _505mIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png") + ImageResource A_CatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ALBIN_EXPRESS.png") + ImageResource AlbinExpressIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/B_ONE.png") + ImageResource BOne(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CANOE_IC.png") + ImageResource CanoeICIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CANOE_TAIFUN.png") + ImageResource CanoeTaifunIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CONTENDER.png") + ImageResource ContenderIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/DRAGON.png") + ImageResource DragonIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE.png") + ImageResource EuropeIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/EXTREME40.png") + ImageResource Extreme40Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/D35.png") + ImageResource D35Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/F16.png") + ImageResource F16Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/F18.png") + ImageResource F18Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png") + ImageResource Farr30Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FARR_280.png") + ImageResource Farr280Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FINN.png") + ImageResource FinnIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png") + ImageResource FlyingDutchmanIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FOLKBOAT.png") + ImageResource FolkBoatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FUN.png") + ImageResource FUNIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/H_BOAT.png") + ImageResource H_BoatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/HOBIE.png") + ImageResource HobieIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J22.png") + ImageResource J22Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J24.png") + ImageResource J24Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J70.png") + ImageResource J70Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J80.png") + ImageResource J80Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J88.png") + ImageResource J88Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J105.png") + ImageResource J105Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/J111.png") + ImageResource J111Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER2.png") + ImageResource Laser2Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER_SB3.png") + ImageResource LaserSB3Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LASER.png") + ImageResource LaserIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LAGO_26.png") + ImageResource Lago26Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_20.png") + ImageResource Melges20Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_24.png") + ImageResource Melges24Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MINI_TRANSAT.png") + ImageResource MiniTransatIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MUSTO_SKIFF.png") + ImageResource MustoSkiffIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/NACRA_17.png") + ImageResource Nacra17Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OK.png") + ImageResource OKIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png") + ImageResource OptimistIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PIRAT.png") + ImageResource PiratIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PLATU_25.png") + ImageResource Platu25Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS_X.png") + ImageResource RSXIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS_FEVA.png") + ImageResource RSFevaIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SONAR.png") + ImageResource SonarIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SOLING.png") + ImageResource SolingIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/STAR.png") + ImageResource StarIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/STREAMLINE.png") + ImageResource StreamlineIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/SWAN_45.png") + ImageResource Swan45Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TORNADO.png") + ImageResource TornadoIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/X_99.png") + ImageResource X99Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/DYAS.png") + ImageResource DyasIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/INTERNATIONAL_14.png") + ImageResource International14Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPENBIC.png") + ImageResource OpenBicIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RS800.png") + ImageResource RS800Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ORC.png") + ImageResource ORCIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PHRF.png") + ImageResource PHRFIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/KIELZUGVOGEL.png") + ImageResource KielzugvogelIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TRIAS.png") + ImageResource TriasIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/O_JOLLE.png") + ImageResource OJolleIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/D_ONE.png") + ImageResource DOneIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/PWA.png") + ImageResource PWAIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GC_32.png") + ImageResource GC32Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CC_30.png") + ImageResource CC30Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GP_26.png") + ImageResource GP26Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/VX_ONE.png") + ImageResource VXOneIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TARTAN_10.png") + ImageResource Tartan10Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/VIPER_640.png") + ImageResource Viper640Icon(); + + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java index 44c10e1f1f9..d6170b2f01f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java @@ -15,6 +15,7 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.common.client.i18n.TextMessages; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; @@ -157,10 +158,12 @@ public class EventHeader extends Composite { if(!presenter.needsSelectionInHeader()) { eventName.setInnerText(nameToShow); LabelTypeUtil.renderLabelType(eventState, event.getState().getStateMarker()); + UIObject.ensureDebugId(eventState, "EventStateLabelDiv"); hide(dropdownTitle); } else { dropdownEventName.setInnerText(nameToShow); LabelTypeUtil.renderLabelType(dropdownEventState, presenter.showRegattaMetadata() ? presenter.getRegattaMetadata().getState().getStateMarker() : event.getState().getStateMarker()); + UIObject.ensureDebugId(dropdownEventState, "EventStateLabelDiv"); hide(staticTitle); initDropdown(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.ui.xml index 6f98e5881ac..bf98f60fd23 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.ui.xml @@ -4,7 +4,7 @@ - +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListItem.ui.xml index 425f41a9517..7f2a49818ee 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/multiregattalist/MultiRegattaListItem.ui.xml @@ -5,7 +5,7 @@ - +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeader.ui.xml index eea06aea487..f7f28c63d07 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeader.ui.xml @@ -4,7 +4,7 @@ - +
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeaderBody.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeaderBody.java index 0d474310362..59955828f33 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeaderBody.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/regattaheader/RegattaHeaderBody.java @@ -11,8 +11,8 @@ import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.UIObject; import com.sap.sailing.gwt.common.client.BoatClassImageResolver; import com.sap.sailing.gwt.home.communication.event.LabelType; -import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO; import com.sap.sailing.gwt.home.communication.eventview.HasRegattaMetadata.RegattaState; +import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO; import com.sap.sailing.gwt.home.shared.utils.LabelTypeUtil; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -44,6 +44,8 @@ public class RegattaHeaderBody extends UIObject { addDetailsItem(I18N.courseAreaName(defaultCourseAreaName)); } addDetailsItem(regattaMetadata.getBoatCategory()); + UIObject.ensureDebugId(nameUi, "RegattaNameSpan"); + UIObject.ensureDebugId(labelUi, "RegattaStateLabelDiv"); } private void addDetailsItem(int count, String text) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/app/PlaceNavigation.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/app/PlaceNavigation.java index db241b1e3cb..4f60e51c901 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/app/PlaceNavigation.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/app/PlaceNavigation.java @@ -59,11 +59,12 @@ public class PlaceNavigation { if (!GWT.isProdMode()) { url += "?gwt.codesvr=127.0.0.1:9997"; } - url += getPlaceToken(); - } else { - url = getPlaceToken(); + String localeValue = Window.Location.getParameter("locale"); + if (localeValue != null) { + url += "?locale=" + localeValue; + } } - return url; + return url + getPlaceToken(); } public boolean isRemotePlace() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java index d098d2517ce..d4938a0a812 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java @@ -73,8 +73,10 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn } startDateBox = createDateTimeBox(regatta.startDate); startDateBox.setFormat("dd/mm/yyyy hh:ii"); + startDateBox.ensureDebugId("StartDateTimeBox"); endDateBox = createDateTimeBox(regatta.endDate); endDateBox.setFormat("dd/mm/yyyy hh:ii"); + endDateBox.ensureDebugId("EndDateTimeBox"); scoringSchemeListBox = createListBox(false); scoringSchemeListBox.ensureDebugId("ScoringSchemeListBox"); for (ScoringSchemeType scoringSchemeType : ScoringSchemeType.values()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index b214a68dd41..983f752fb84 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -93,6 +93,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements leaderboardGroupsDisplayers = new HashSet<>(); final EventManagementPanel eventManagementPanel = new EventManagementPanel(sailingService, this, this, getStringMessages()); + eventManagementPanel.ensureDebugId("EventManagement"); panel.addToVerticalTabPanel(new DefaultRefreshableAdminConsolePanel(eventManagementPanel) { @Override public void refreshAfterBecomingVisible() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java index 9dbd7153c72..628bb577a3c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java @@ -156,7 +156,7 @@ public class CreateRegattaCallback implements DialogCallback{ public void cancel() { } }); - dialog.ensureDebugId("CreateDefaultRegattaDialog"); + dialog.ensureDebugId("CreateDefaultRegattaLeaderboardDialog"); dialog.show(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java index f472748b084..51c16288695 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java @@ -20,22 +20,28 @@ public class EventCreateDialog extends EventDialog { availableLeaderboardGroups, /* leaderboardGroups */ Collections.emptyList(), callback); nameEntryField = createTextBox(null); nameEntryField.setVisibleLength(50); + nameEntryField.ensureDebugId("NameTextBox"); descriptionEntryField = createTextArea(null); descriptionEntryField.setCharacterWidth(50); descriptionEntryField.setVisibleLines(2); descriptionEntryField.getElement().getStyle().setProperty("resize", "none"); + descriptionEntryField.ensureDebugId("DescriptionTextArea"); venueEntryField = createTextBox(null); venueEntryField.setVisibleLength(35); + venueEntryField.ensureDebugId("VenueTextBox"); final Date now = new Date(); startDateBox = createDateTimeBox(now); - startDateBox.setFormat("dd/mm/yyyy hh:ii"); + startDateBox.setFormat("dd/mm/yyyy hh:ii"); + startDateBox.ensureDebugId("StartDateTimeBox"); endDateBox = createDateTimeBox(now); endDateBox.setFormat("dd/mm/yyyy hh:ii"); + endDateBox.ensureDebugId("EndDateTimeBox"); officialWebsiteURLEntryField = createTextBox(null); officialWebsiteURLEntryField.setVisibleLength(50); sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(Collections. emptyMap()); isPublicCheckBox = createCheckbox(""); isPublicCheckBox.setValue(false); + isPublicCheckBox.ensureDebugId("IsPublicCheckBox"); imagesListComposite.fillImages(Collections.emptyList()); videosListComposite.fillVideos(Collections.emptyList()); // add default course area diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java index ea7b3bee298..1f4f6993874 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java @@ -54,6 +54,7 @@ import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -118,6 +119,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le panel.add(eventControlsPanel); Button refreshButton = new Button(stringMessages.refresh()); + refreshButton.ensureDebugId("RefreshEventsButton"); refreshButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -127,6 +129,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le eventControlsPanel.add(refreshButton); Button createEventBtn = new Button(stringMessages.actionAddEvent()); + createEventBtn.ensureDebugId("CreateEventButton"); createEventBtn.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -136,6 +139,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le eventControlsPanel.add(createEventBtn); removeEventsButton = new Button(stringMessages.remove()); + removeEventsButton.ensureDebugId("RemoveEventsButton"); removeEventsButton.setEnabled(false); removeEventsButton.addClickHandler(new ClickHandler() { @Override @@ -455,6 +459,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le createNewEvent(newEvent, existingLeaderboardGroups); } }); + dialog.ensureDebugId("EventCreateDialog"); dialog.show(); } @@ -557,6 +562,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le } }); leaderboardGroupCreateDialog.setFieldsBasedOnEventName(newEvent.getName()); + leaderboardGroupCreateDialog.ensureDebugId("LeaderboardGroupCreateDialog"); leaderboardGroupCreateDialog.show(); } @@ -678,7 +684,8 @@ public class EventListComposite extends Composite implements EventsRefresher, Le fillEvents(); if (newEvent.getLeaderboardGroups().isEmpty()) { // show simple Dialog - new CreateDefaultLeaderboardGroupDialog(sailingService, stringMessages, errorReporter, new DialogCallback() { + DataEntryDialog dialog = new CreateDefaultLeaderboardGroupDialog( + sailingService, stringMessages, errorReporter, new DialogCallback() { @Override public void ok(Void editedObject) { openLeaderboardGroupCreationDialog(existingLeaderboardGroups, newEvent); @@ -687,7 +694,9 @@ public class EventListComposite extends Composite implements EventsRefresher, Le @Override public void cancel() { } - }).show(); + }); + dialog.ensureDebugId("CreateDefaultLeaderboardGroupConfirmDialog"); + dialog.show(); } else { openCreateDefaultRegattaDialog(newEvent); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java index dabfa8597e5..28c370ab0bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java @@ -52,14 +52,22 @@ public final class ListBoxUtils { } } } - - public static void setupFlagsListBox(ListBox box, List selectedFlags) { + + private static void setupFlagsListBox(ListBox box, List selectedFlags, Flags... availableFlags) { box.clear(); - for (Flags flag : Flags.validValues()) { + for (Flags flag : availableFlags) { box.addItem(flag.toString(), flag.name()); if (selectedFlags.contains(flag)) { box.setItemSelected(box.getItemCount() - 1, true); } } } + + public static void setupFlagsListBox(ListBox box, List selectedFlags) { + setupFlagsListBox(box, selectedFlags, Flags.validValues()); + } + + public static void setupStartmodeFlagsListBox(ListBox box, List selectedFlags) { + setupFlagsListBox(box, selectedFlags, Flags.PAPA, Flags.BLACK, Flags.INDIA, Flags.INDIA_ZULU, Flags.UNIFORM); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java index 336dadbd42e..5e54ee20605 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementRaceImagesBarCell.java @@ -4,10 +4,10 @@ import java.util.ArrayList; import java.util.List; import com.google.gwt.core.client.GWT; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.gwt.ui.adminconsole.AbstractLeaderboardConfigPanel.RaceColumnDTOAndFleetDTOWithNameBasedEquality; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell; -import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; public class RaceLogTrackingEventManagementRaceImagesBarCell extends ImagesBarCell { @@ -53,11 +53,11 @@ public class RaceLogTrackingEventManagementRaceImagesBarCell extends ImagesBarCe result.add(new ImageSpec(ACTION_SHOW_RACELOG, stringMessages.raceLog(), makeImagePrototype(resources.flagIcon()))); result.add(new ImageSpec(ACTION_SET_TRACKING_TIMES, stringMessages.setTrackingTimes(), makeImagePrototype(resources.setTrackingTimes()))); - Pair startEndTrackingTime = smartphoneTrackingEventManagementPanel.getTrackingTimesFor(object); - if (startEndTrackingTime == null){ + Pair startEndTrackingTime = smartphoneTrackingEventManagementPanel.getTrackingTimesFor(object); + if (startEndTrackingTime == null) { result.add(new ImageSpec(ACTION_START_TRACKING, stringMessages.startTracking(), makeImagePrototype(resources.startRaceLogTracking()))); } else { - if (startEndTrackingTime.getB() == null){ + if (startEndTrackingTime.getB() == null || startEndTrackingTime.getB().getTimePoint() == null) { result.add(new ImageSpec(ACTION_STOP_TRACKING, stringMessages.stopTracking(), makeImagePrototype(resources.stopRaceLogTracking()))); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java index 609acfc6a51..49b69745aa6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java @@ -41,12 +41,14 @@ public class RegattaConfigurationDialog extends DataEntryDialog callback) { @@ -168,11 +173,24 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { + @Override + public void onValueChange(ValueChangeEvent event) { + } + }); + return box; + } + private void setupRRS26() { rrs26DisclosurePanel = new DisclosurePanel(stringMessages.rrs26Start()); VerticalPanel panel = new VerticalPanel(); - Grid grid = new Grid(3, 3); + Grid grid = new Grid(4, 3); rrs26EnabledBox = new CheckBox(stringMessages.setConfiguration()); rrs26EnabledBox.addValueChangeHandler(new ValueChangeHandler() { @Override @@ -180,6 +198,7 @@ public class RegattaConfigurationDialog extends DataEntryDialog selectedFlags = new ArrayList(); if (originalConfiguration != null && originalConfiguration.rrs26Configuration != null && originalConfiguration.rrs26Configuration.startModeFlags != null) { selectedFlags = originalConfiguration.rrs26Configuration.startModeFlags; } - ListBoxUtils.setupFlagsListBox(rrs26StartModeFlagsBox, selectedFlags); + ListBoxUtils.setupStartmodeFlagsListBox(rrs26StartModeFlagsBox, selectedFlags); rrs26StartModeFlagsBox.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { @@ -229,13 +250,14 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { @Override public void onValueChange(ValueChangeEvent event) { gateStartClassFlagListBox.setEnabled(event.getValue()); gateStartRecallBox.setEnabled(event.getValue()); + gateStartResultEntryBox.setEnabled(event.getValue()); gateStartPathfinderBox.setEnabled(event.getValue()); gateStartGolfDownBox.setEnabled(event.getValue()); } @@ -244,6 +266,7 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { @Override @@ -262,10 +285,12 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { @Override public void onValueChange(ValueChangeEvent event) { essClassFlagListBox.setEnabled(event.getValue()); essRecallBox.setEnabled(event.getValue()); + essResultEntryBox.setEnabled(event.getValue()); } }); essClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null : originalConfiguration.essConfiguration); essClassFlagListBox.setWidth("100%"); essRecallBox = setupRecallBox(originalConfiguration==null?null:originalConfiguration.essConfiguration); + essResultEntryBox = setupResultEntryBox(originalConfiguration==null?null:originalConfiguration.essConfiguration); grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); grid.setWidget(0, 1, essClassFlagListBox); grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("ESS"))); grid.setWidget(1, 0, essRecallBox); grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + grid.setWidget(2, 0, essResultEntryBox); + grid.setWidget(2, 2, createHelpImage(stringMessages.resultEntryHelpText())); essEnabledBox.setValue(originalConfiguration != null && originalConfiguration.essConfiguration != null); ValueChangeEvent.fire(essEnabledBox, essEnabledBox.getValue()); @@ -314,25 +343,29 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { @Override public void onValueChange(ValueChangeEvent event) { basicClassFlagListBox.setEnabled(event.getValue()); basicRecallBox.setEnabled(event.getValue()); + basicResultEntryBox.setEnabled(event.getValue()); } }); basicClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null : originalConfiguration.basicConfiguration); basicClassFlagListBox.setWidth("100%"); basicRecallBox = setupRecallBox(originalConfiguration==null?null:originalConfiguration.basicConfiguration); + basicResultEntryBox = setupResultEntryBox(originalConfiguration==null?null:originalConfiguration.basicConfiguration); grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); grid.setWidget(0, 1, basicClassFlagListBox); grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("basic start"))); grid.setWidget(1, 0, basicRecallBox); grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + grid.setWidget(2, 0, basicResultEntryBox); + grid.setWidget(2, 2, createHelpImage(stringMessages.resultEntryHelpText())); basicEnabledBox.setValue(originalConfiguration != null && originalConfiguration.basicConfiguration != null); ValueChangeEvent.fire(basicEnabledBox, basicEnabledBox.getValue()); @@ -347,25 +380,29 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { @Override public void onValueChange(ValueChangeEvent event) { leagueClassFlagListBox.setEnabled(event.getValue()); leagueRecallBox.setEnabled(event.getValue()); + leagueResultEntryBox.setEnabled(event.getValue()); } }); leagueClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null : originalConfiguration.leagueConfiguration); leagueClassFlagListBox.setWidth("100%"); leagueRecallBox = setupRecallBox(originalConfiguration==null?null:originalConfiguration.leagueConfiguration); + leagueResultEntryBox = setupResultEntryBox(originalConfiguration==null?null:originalConfiguration.leagueConfiguration); grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); grid.setWidget(0, 1, leagueClassFlagListBox); grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("league start"))); grid.setWidget(1, 0, leagueRecallBox); grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + grid.setWidget(2, 0, leagueResultEntryBox); + grid.setWidget(2, 2, createHelpImage(stringMessages.resultEntryHelpText())); leagueEnabledBox.setValue(originalConfiguration != null && originalConfiguration.leagueConfiguration != null); ValueChangeEvent.fire(leagueEnabledBox, leagueEnabledBox.getValue()); @@ -400,7 +437,7 @@ public class RegattaConfigurationDialog extends DataEntryDialog flags = new ArrayList(); for (int i = 0; i < rrs26StartModeFlagsBox.getItemCount(); i++) { if (rrs26StartModeFlagsBox.isItemSelected(i)) { @@ -412,21 +449,21 @@ public class RegattaConfigurationDialog extends DataEntryDialog= 0) { Flags flag = Flags.valueOf(classListBox.getValue(index)); target.classFlag = flag == Flags.NONE ? null : flag; } target.hasIndividualRecall = recallBox.getValue(); + target.isResultEntryEnabled = resultEntryBox.getValue(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java index 1aeada7dfa3..5635329d783 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java @@ -59,6 +59,7 @@ public class SetStartTimeDialog extends DataEntryDialogWithBootstrap>() { + new AsyncCallback>() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error retrieving tracking times: " + caught.getMessage()); } @Override - public void onSuccess(Pair result) { - currentStart = result == null ? null : result.getA(); - currentEnd = result == null ? null : result.getB(); + public void onSuccess(Pair result) { + currentStart = result == null || result.getA() == null ? null : result.getA().getTimePoint(); + currentEnd = result == null || result.getB() == null ? null : result.getB().getTimePoint(); updateDateTimeLabelAndTimeBoxFromDate(currentStart, currentStartLabel, startTimeBox); updateDateTimeLabelAndTimeBoxFromDate(currentEnd, currentEndLabel, endTimeBox); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java index 3069a566370..e6f83f32fa4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java @@ -28,6 +28,7 @@ import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; @@ -45,7 +46,6 @@ import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; -import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; @@ -64,7 +64,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC private CheckBox trackWind; private ImagesBarColumn raceActionColumn; protected boolean regattaHasCompetitors = false; - private Map, Pair> raceWithStartAndEndOfTrackingTime = new HashMap, Pair>(); + private Map, Pair> raceWithStartAndEndOfTrackingTime = new HashMap<>(); public SmartphoneTrackingEventManagementPanel(SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, LeaderboardsRefresher leaderboardsRefresher, @@ -477,14 +477,14 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC } sailingService.getTrackingTimes(raceColumnsAndFleets, - new AsyncCallback, Pair>>() { + new AsyncCallback, Pair>>() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error retrieving tracking times: " + caught.getMessage()); } @Override - public void onSuccess(Map, Pair> result) { + public void onSuccess(Map, Pair> result) { raceWithStartAndEndOfTrackingTime = result; raceColumnTable.getDataProvider().getList().clear(); @@ -510,7 +510,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC } } - public Pair getTrackingTimesFor(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnDTOAndFleet){ + public Pair getTrackingTimesFor(RaceColumnDTOAndFleetDTOWithNameBasedEquality raceColumnDTOAndFleet){ return raceWithStartAndEndOfTrackingTime.get(new Triple(getSelectedLeaderboard().name, raceColumnDTOAndFleet.getA().getName(), raceColumnDTOAndFleet.getB().getName())); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index a83a0223b81..923b0224751 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -29,6 +29,7 @@ import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.UnableToCloseDeviceMappingException; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.dto.BoatDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; @@ -93,7 +94,6 @@ import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sse.common.Duration; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; @@ -577,7 +577,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS RegattaAndRaceIdentifier getRaceIdentifier(String regattaLikeName, String raceColumnName, String fleetName); void setTrackingTimes(RaceLogSetTrackingTimesDTO dto) throws NotFoundException; - Pair getTrackingTimes(String leaderboardName, String raceColumnName, String fleetName) throws NotFoundException; + Pair getTrackingTimes(String leaderboardName, String raceColumnName, String fleetName) throws NotFoundException; /** * @param raceLogFrom identifies the race log to copy from by its leaderboard name, race column name and fleet name @@ -640,5 +640,5 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS void editMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString, GPSFixDTO oldFix, Position newPosition) throws NotRevokableException; - Map, Pair> getTrackingTimes(Collection> raceColumnsAndFleets); + Map, Pair> getTrackingTimes(Collection> raceColumnsAndFleets); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index d9112c428a9..65fb4725221 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -24,6 +24,7 @@ import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.dto.BoatDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; @@ -81,7 +82,6 @@ import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; @@ -726,7 +726,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void setTrackingTimes(RaceLogSetTrackingTimesDTO dto, AsyncCallback callback); void getTrackingTimes(String leaderboardName, String raceColumnName, String fleetName, - AsyncCallback> callback); + AsyncCallback> callback); /** * Copies one race's competitor set to other races. @@ -782,5 +782,5 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void getMarkTracks(String leaderboardName, String raceColumnName, String fleetName, AsyncCallback callback); void getTrackingTimes(Collection> raceColumnsAndFleets, - AsyncCallback, Pair>> asyncCallback); + AsyncCallback, Pair>> asyncCallback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index e7c4a9d6847..e6991021c58 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -966,11 +966,13 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String setRacingProcedureConfiguration(); String racingProcedureConfiguration(); String activateIndividualRecall(); + String activateResultEntry(); String rrs26Start(); String setConfiguration(); String classFlag(); String classFlagHelpText(String procedure); String individualRecallHelpText(); + String resultEntryHelpText(); String startmodeFlags(); String startmodeFlagsHelpText(); String gateStart(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index c15408406c4..018755ac805 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -938,11 +938,13 @@ dontoverwrite=Don''t overwrite setRacingProcedureConfiguration=Set regatta configuration racingProcedureConfiguration=Regatta configuration activateIndividualRecall=Activate individual recall +activateResultEntry=Activate result entry rrs26Start=RRS26 start setConfiguration=Activate configuration classFlag=Class flag classFlagHelpText=Sets the class flag used for {0}. Choose ''Don''t Overwrite'' to activate the procedure''s default flag. individualRecallHelpText=When activated the Race Officer is able to call for individual recall. +resultEntryHelpText=When activated the Race Officer is able to enter results during and after the finishing phase. startmodeFlags=Startmode flags startmodeFlagsHelpText=Sets the available start mode flags. Choose none to enable the procedure''s default flags. gateStart=Gate Start diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 7a5551fdd66..ca4c9d6eb77 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -930,11 +930,13 @@ dontoverwrite=Nicht überschreiben setRacingProcedureConfiguration=Konfiguriere Rennart racingProcedureConfiguration=Rennart-Konfiguration activateIndividualRecall=Aktiviere Einzelrückruf +activateResultEntry=Aktiviere Ergebniseingabe rrs26Start=RRS26 Start setConfiguration=Aktiviere Konfiguration classFlag=Klassenflagge classFlagHelpText=Setzt die Klassenflagge für {0}. Wähle 'Nicht überschreiben' um die Standardflagge der Rennart zu wählen. -individualRecallHelpText=Wenn aktiviert, ermöglicht dem Race Officer Einzelrückruf anzuwenden. +individualRecallHelpText=Wenn aktiviert, ermöglicht dies dem Race Officer, Einzelrückruf anzuwenden. +resultEntryHelpText=Wenn aktiviert, ermöglicht dies dem Race Officer, Ergebnisse während und nach der Beendigungsphase zu erfassen. startmodeFlags=Startmodus-Flaggen startmodeFlagsHelpText=Setzt die verfügbaren Startmodus-Flaggen. Wähle keine aus um die Standardflaggen zu wählen. gateStart=Gate Start diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java index 5a79f2e3003..c17fecacdc9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java @@ -188,17 +188,17 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements TimeLis protected void changeMinMaxAndExtremesInterval(Date minTimepoint, Date maxTimepoint, boolean redraw) { if (chart != null) { XAxis xAxis = chart.getXAxis(); + if (minTimepoint != null && maxTimepoint != null) { + xAxis.setExtremes(minTimepoint.getTime(), maxTimepoint.getTime(), /* redraw */ false, false); + long tickInterval = (maxTimepoint.getTime() - minTimepoint.getTime()) / TICKCOUNT; + xAxis.setTickInterval(tickInterval); + } if (minTimepoint != null) { xAxis.setMin(minTimepoint.getTime()); } if (maxTimepoint != null) { xAxis.setMax(maxTimepoint.getTime()); } - if (minTimepoint != null && maxTimepoint != null) { - xAxis.setExtremes(minTimepoint.getTime(), maxTimepoint.getTime(), /* redraw */ false, false); - long tickInterval = (maxTimepoint.getTime() - minTimepoint.getTime()) / TICKCOUNT; - xAxis.setTickInterval(tickInterval); - } if (redraw) { chart.redraw(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java index 508c4d4a0af..2206f0e2efe 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarksPanel.java @@ -38,6 +38,7 @@ public class MarksPanel extends SimplePanel implements Component(10000, tableResources); + markTable.setStyleName("EditMarkPositionMarkTable"); SortableColumn markNameColumn = new SortableColumn(new TextCell(), SortingOrder.NONE) { @Override public String getValue(MarkDTO object) { @@ -77,6 +78,7 @@ public class MarksPanel extends SimplePanel implements Componentcompetitor are set to -1. */ private void clearTail(CompetitorDTO competitor) { - Polyline tail = tails.get(competitor); + final Polyline tail = tails.get(competitor); if (tail != null) { tail.getPath().clear(); firstShownFix.put(competitor, -1); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java index 4c7e027c4c5..a2fdce54831 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java @@ -2,6 +2,8 @@ package com.sap.sailing.gwt.ui.client.shared.racemap; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.maps.client.events.click.ClickMapHandler; +import com.google.gwt.maps.client.events.mousemove.MouseMoveMapEvent; +import com.google.gwt.maps.client.events.mousemove.MouseMoveMapHandler; import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent; import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler; import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent; @@ -45,6 +47,12 @@ public class Hoverline { hoverline.setVisible(false); } }); + map.getMap().addMouseMoveHandler(new MouseMoveMapHandler() { + @Override + public void onEvent(MouseMoveMapEvent event) { + hoverline.setVisible(false); + } + }); } public HandlerRegistration addClickHandler(ClickMapHandler handler) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java index f1a0b2eb29c..b978eb8b278 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java @@ -2,6 +2,8 @@ package com.sap.sailing.gwt.ui.raceboard; import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -27,6 +29,8 @@ import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; import com.sap.sailing.gwt.ui.client.TimePanel; import com.sap.sailing.gwt.ui.client.TimePanelSettings; import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; @@ -38,10 +42,12 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes; +import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sse.common.Duration; import com.sap.sse.common.Util; import com.sap.sse.common.filter.Filter; import com.sap.sse.common.filter.FilterSet; +import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; @@ -151,6 +157,16 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint return true; } }; + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, /* errorReporter */ this, + Collections.singleton(selectedRaceIdentifier), 30000l /* requestInterval*/); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(new RaceTimesInfoProviderListener() { + @Override + public void raceTimesInfosReceived(Map raceTimesInfo, + long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, + long clientTimeWhenResponseWasReceived) { + timer.setLivePlayDelayInMillis(raceTimesInfo.get(selectedRaceIdentifier).delayToLiveInMs); + } + }); final Button backToLivePlayButton = timePanel.getBackToLiveButton(); final TimePanelSettings timePanelSettings = timePanel.getSettings(); timePanelSettings.setRefreshInterval(refreshInterval); @@ -192,6 +208,8 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint windChart = null; } createRaceBoardInOneScreenMode(raceMap, windChart); + timeRangeWithZoomProvider.setTimeRange(new MillisecondsTimePoint(timer.getTime()).minus(Duration.ONE_MINUTE.times(15)).asDate(), + new MillisecondsTimePoint(timer.getTime()).plus(Duration.ONE_MINUTE.times(3)).asDate()); timer.setTime(timer.getTime().getTime()-1000l); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index e0809fc808b..7c042a80084 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -179,6 +179,7 @@ import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.domain.common.dto.BoatDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; @@ -303,6 +304,7 @@ import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO.RacingProcedureConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO; import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; @@ -4814,30 +4816,35 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (configuration.getRRS26Configuration() != null) { dto.rrs26Configuration = new DeviceConfigurationDTO.RegattaConfigurationDTO.RRS26ConfigurationDTO(); dto.rrs26Configuration.classFlag = configuration.getRRS26Configuration().getClassFlag(); - dto.rrs26Configuration.hasIndividualRecall = configuration.getRRS26Configuration().hasInidividualRecall(); + dto.rrs26Configuration.hasIndividualRecall = configuration.getRRS26Configuration().hasIndividualRecall(); + dto.rrs26Configuration.isResultEntryEnabled = configuration.getRRS26Configuration().isResultEntryEnabled(); dto.rrs26Configuration.startModeFlags = configuration.getRRS26Configuration().getStartModeFlags(); } if (configuration.getGateStartConfiguration() != null) { dto.gateStartConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.GateStartConfigurationDTO(); dto.gateStartConfiguration.classFlag = configuration.getGateStartConfiguration().getClassFlag(); - dto.gateStartConfiguration.hasIndividualRecall = configuration.getGateStartConfiguration().hasInidividualRecall(); + dto.gateStartConfiguration.hasIndividualRecall = configuration.getGateStartConfiguration().hasIndividualRecall(); + dto.gateStartConfiguration.isResultEntryEnabled = configuration.getGateStartConfiguration().isResultEntryEnabled(); dto.gateStartConfiguration.hasPathfinder = configuration.getGateStartConfiguration().hasPathfinder(); dto.gateStartConfiguration.hasAdditionalGolfDownTime = configuration.getGateStartConfiguration().hasAdditionalGolfDownTime(); } if (configuration.getESSConfiguration() != null) { dto.essConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.ESSConfigurationDTO(); dto.essConfiguration.classFlag = configuration.getESSConfiguration().getClassFlag(); - dto.essConfiguration.hasIndividualRecall = configuration.getESSConfiguration().hasInidividualRecall(); + dto.essConfiguration.hasIndividualRecall = configuration.getESSConfiguration().hasIndividualRecall(); + dto.essConfiguration.isResultEntryEnabled = configuration.getESSConfiguration().isResultEntryEnabled(); } if (configuration.getBasicConfiguration() != null) { dto.basicConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.RacingProcedureConfigurationDTO(); dto.basicConfiguration.classFlag = configuration.getBasicConfiguration().getClassFlag(); - dto.basicConfiguration.hasIndividualRecall = configuration.getBasicConfiguration().hasInidividualRecall(); + dto.basicConfiguration.hasIndividualRecall = configuration.getBasicConfiguration().hasIndividualRecall(); + dto.basicConfiguration.isResultEntryEnabled = configuration.getBasicConfiguration().isResultEntryEnabled(); } if (configuration.getLeagueConfiguration() != null) { dto.leagueConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.LeagueConfigurationDTO(); dto.leagueConfiguration.classFlag = configuration.getLeagueConfiguration().getClassFlag(); - dto.leagueConfiguration.hasIndividualRecall = configuration.getLeagueConfiguration().hasInidividualRecall(); + dto.leagueConfiguration.hasIndividualRecall = configuration.getLeagueConfiguration().hasIndividualRecall(); + dto.leagueConfiguration.isResultEntryEnabled = configuration.getLeagueConfiguration().isResultEntryEnabled(); } return dto; } @@ -4859,40 +4866,42 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S configuration.setDefaultCourseDesignerMode(dto.defaultCourseDesignerMode); if (dto.rrs26Configuration != null) { RRS26ConfigurationImpl config = new RRS26ConfigurationImpl(); - config.setClassFlag(dto.rrs26Configuration.classFlag); - config.setHasInidividualRecall(dto.rrs26Configuration.hasIndividualRecall); + applyGeneralRacingProcedureConfigProperties(dto.rrs26Configuration, config); config.setStartModeFlags(dto.rrs26Configuration.startModeFlags); configuration.setRRS26Configuration(config); } if (dto.gateStartConfiguration != null) { GateStartConfigurationImpl config = new GateStartConfigurationImpl(); - config.setClassFlag(dto.gateStartConfiguration.classFlag); - config.setHasInidividualRecall(dto.gateStartConfiguration.hasIndividualRecall); + applyGeneralRacingProcedureConfigProperties(dto.gateStartConfiguration, config); config.setHasPathfinder(dto.gateStartConfiguration.hasPathfinder); config.setHasAdditionalGolfDownTime(dto.gateStartConfiguration.hasAdditionalGolfDownTime); configuration.setGateStartConfiguration(config); } if (dto.essConfiguration != null) { ESSConfigurationImpl config = new ESSConfigurationImpl(); - config.setClassFlag(dto.essConfiguration.classFlag); - config.setHasInidividualRecall(dto.essConfiguration.hasIndividualRecall); + applyGeneralRacingProcedureConfigProperties(dto.essConfiguration, config); configuration.setESSConfiguration(config); } if (dto.basicConfiguration != null) { RacingProcedureConfigurationImpl config = new RacingProcedureConfigurationImpl(); - config.setClassFlag(dto.basicConfiguration.classFlag); - config.setHasInidividualRecall(dto.basicConfiguration.hasIndividualRecall); + applyGeneralRacingProcedureConfigProperties(dto.basicConfiguration, config); configuration.setBasicConfiguration(config); } if (dto.leagueConfiguration != null) { LeagueConfigurationImpl config = new LeagueConfigurationImpl(); - config.setClassFlag(dto.leagueConfiguration.classFlag); - config.setHasInidividualRecall(dto.leagueConfiguration.hasIndividualRecall); + applyGeneralRacingProcedureConfigProperties(dto.leagueConfiguration, config); configuration.setLeagueConfiguration(config); } return configuration; } + private void applyGeneralRacingProcedureConfigProperties(RacingProcedureConfigurationDTO racingProcedureConfigurationDTO, + RacingProcedureConfigurationImpl config) { + config.setClassFlag(racingProcedureConfigurationDTO.classFlag); + config.setHasIndividualRecall(racingProcedureConfigurationDTO.hasIndividualRecall); + config.setResultEntryEnabled(racingProcedureConfigurationDTO.isResultEntryEnabled); + } + @Override public boolean setStartTimeAndProcedure(RaceLogSetStartTimeAndProcedureDTO dto) { TimePoint newStartTime = getService().setStartTimeAndProcedure(dto.leaderboardName, dto.raceColumnName, @@ -4919,10 +4928,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Util.Pair getTrackingTimes(String leaderboardName, String raceColumnName, String fleetName) throws NotFoundException { + public Pair getTrackingTimes(String leaderboardName, String raceColumnName, String fleetName) throws NotFoundException { final RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); - final Pair times = new TrackingTimesFinder(raceLog).analyze(); - return times == null ? null : new Pair(times.getA(), times.getB()); + final Pair times = new TrackingTimesFinder(raceLog).analyze(); + return times; } @Override @@ -5211,18 +5220,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S public void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, List> courseDTO) throws NotFoundException { RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); - String name = String.format("Course for %s - %s - %s", leaderboardName, raceColumnName, fleetName); - + String courseName = "Course of " + raceColumnName; + if(!LeaderboardNameConstants.DEFAULT_FLEET_NAME.equals(fleetName)) { + courseName += "- " + fleetName; + } CourseBase lastPublishedCourse = new LastPublishedCourseDesignFinder(raceLog).analyze(); if (lastPublishedCourse == null) { - lastPublishedCourse = new CourseDataImpl(name); + lastPublishedCourse = new CourseDataImpl(courseName); } List> controlPoints = new ArrayList<>(); for (Pair waypointDTO : courseDTO) { controlPoints.add(new Pair<>(getOrCreateControlPoint(waypointDTO.getA()), waypointDTO.getB())); } - Course course = new CourseImpl(name, lastPublishedCourse.getWaypoints()); + Course course = new CourseImpl(courseName, lastPublishedCourse.getWaypoints()); try { course.update(controlPoints, baseDomainFactory); @@ -5352,9 +5363,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private DeviceMappingDTO convertToDeviceMappingDTO(DeviceMapping mapping) throws TransformationException { String deviceId = serializeDeviceIdentifier(mapping.getDevice()); - Date from = mapping.getTimeRange().from() == null || mapping.getTimeRange().from().asMillis() == Long.MIN_VALUE ? + Date from = mapping.getTimeRange().from() == null || mapping.getTimeRange().from().equals(TimePoint.BeginningOfTime) ? null : mapping.getTimeRange().from().asDate(); - Date to = mapping.getTimeRange().to() == null || mapping.getTimeRange().to().asMillis() == Long.MAX_VALUE ? + Date to = mapping.getTimeRange().to() == null || mapping.getTimeRange().to().equals(TimePoint.EndOfTime) ? null : mapping.getTimeRange().to().asDate(); MappableToDevice item = null; final WithID mappedTo = mapping.getMappedTo(); @@ -6089,9 +6100,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final RaceLog raceLog = raceColumn.getRaceLog(fleet); final RegattaLog regattaLog = raceColumn.getRegattaLog(); final TrackingTimesFinder trackingTimesFinder = new TrackingTimesFinder(raceLog); - final Pair trackingTimes = trackingTimesFinder.analyze(); + final Pair trackingTimes = trackingTimesFinder.analyze(); try { - getService().getGPSFixStore().loadMarkTrack(writeableMarkTrack, regattaLog, mark, trackingTimes.getA(), trackingTimes.getB()); + getService().getGPSFixStore().loadMarkTrack(writeableMarkTrack, regattaLog, mark, + trackingTimes.getA().getTimePoint(), trackingTimes.getB().getTimePoint()); } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { logger.info("Error trying to load mark track for mark "+mark+" from "+trackingTimes.getA()+" to "+trackingTimes.getB()); } @@ -6222,9 +6234,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } - public Map,Pair> getTrackingTimes( + @Override + public Map, Pair> getTrackingTimes( Collection> raceColumnsAndFleets) { - Map,Pair> trackingTimes = new HashMap,Pair>(); + Map, Pair> trackingTimes = new HashMap<>(); for (Triple leaderboardRaceColumnFleetName : raceColumnsAndFleets) { try { trackingTimes.put(leaderboardRaceColumnFleetName, getTrackingTimes(leaderboardRaceColumnFleetName.getA(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java index 39a86c48b5e..30e8ef4bdb6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java @@ -30,6 +30,7 @@ public class DeviceConfigurationDTO implements IsSerializable { public static class RacingProcedureConfigurationDTO implements IsSerializable { public Flags classFlag; public Boolean hasIndividualRecall; + public Boolean isResultEntryEnabled; } public RacingProcedureType defaultRacingProcedureType; public CourseDesignerMode defaultCourseDesignerMode; diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html index 7cd06cccd28..1519e553966 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html @@ -4,6 +4,18 @@

What's New - SAP Sailing Race Manager

+
April 2016
+ +
    +
  • Whether or not result entry during or after the finishing phase is offered is now + a preference under "REGATTA CONFIGURATION (DEFAULT)." The device configuration managed + on the server can remotely manage this configuration setting. With this, it is possible + to configure up-front whether a race officer is offered the possibility to manage + results directly from the Race Manager app. This should not be used if an + external, official regatta management system is being used to capture and manage + the official scores.
  • +
+
March 2016