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

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionProvider.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
This commit is contained in:
fmittag
2013-05-21 14:55:06 +02:00
551 changed files with 40934 additions and 2409 deletions
+1
View File
@@ -0,0 +1 @@
This branch will contain a sailing simulator
+1
View File
@@ -35,3 +35,4 @@ Like businesses, sailors need the latest information to make strategic decisions
* [Maven Repository Browser](http://maven.sapsailing.com/maven/) * [Maven Repository Browser](http://maven.sapsailing.com/maven/)
* [Main Sailing Website](http://www.sapsailing.com) * [Main Sailing Website](http://www.sapsailing.com)
* [Visitor Statistics](http://analysis.sapsailing.com/) * [Visitor Statistics](http://analysis.sapsailing.com/)
* [Traffic and CPU for app01](http://mrtg.sapsailing.com/)
Executable → Regular
View File
@@ -11,4 +11,5 @@ Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.impl, com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.quadtree, com.sap.sailing.domain.common.quadtree,
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test", com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test",
com.sap.sailing.domain.common.racelog com.sap.sailing.domain.common.racelog,
com.sap.sailing.domain.common.dto
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@@ -8,13 +9,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
public abstract class AbstractLeaderboardDTO implements IsSerializable { public abstract class AbstractLeaderboardDTO implements Serializable {
private static final long serialVersionUID = -205106531931903527L;
public String name; public String name;
private List<RaceColumnDTO> races; private List<RaceColumnDTO> races;
@@ -1,8 +1,9 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import com.google.gwt.user.client.rpc.IsSerializable; import java.io.Serializable;
public class BoatClassDTO extends NamedDTO implements IsSerializable { public class BoatClassDTO extends NamedDTO implements Serializable {
private static final long serialVersionUID = 1981789833769906676L;
private double hullLengthInMeters; private double hullLengthInMeters;
BoatClassDTO() {} BoatClassDTO() {}
@@ -1,8 +1,9 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import com.google.gwt.user.client.rpc.IsSerializable; import java.io.Serializable;
public class CompetitorDTO extends NamedDTO implements IsSerializable { public class CompetitorDTO extends NamedDTO implements Serializable {
private static final long serialVersionUID = -4997852354821083154L;
public String twoLetterIsoCountryCode; public String twoLetterIsoCountryCode;
public String threeLetterIocCountryCode; public String threeLetterIocCountryCode;
public String countryName; public String countryName;
@@ -1,9 +1,9 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.Color;
public class FleetDTO extends NamedDTO implements IsSerializable { public class FleetDTO extends NamedDTO {
private static final long serialVersionUID = 1336494392278190103L;
private Color color; private Color color;
private int orderNo; private int orderNo;
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -7,8 +8,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.google.gwt.user.client.rpc.IsSerializable;
/** /**
* Captures the serializable properties of a leaderboard which in particular has the competitors, any optional display * Captures the serializable properties of a leaderboard which in particular has the competitors, any optional display
* name mappings for the competitors, races and their net / total points as well as possible reasons for maximum points * name mappings for the competitors, races and their net / total points as well as possible reasons for maximum points
@@ -17,7 +16,9 @@ import com.google.gwt.user.client.rpc.IsSerializable;
* @author Axel Uhl (d043530) * @author Axel Uhl (d043530)
* *
*/ */
public class LeaderboardDTO extends AbstractLeaderboardDTO implements IsSerializable { public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializable {
private static final long serialVersionUID = -520930809792750648L;
/** /**
* The competitor list, ordered ascending by total rank * The competitor list, ordered ascending by total rank
*/ */
@@ -1,8 +1,8 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.List; import java.util.List;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Tack;
@@ -15,7 +15,9 @@ import com.sap.sailing.domain.common.Tack;
* @author Axel Uhl (D043530) * @author Axel Uhl (D043530)
* *
*/ */
public class LeaderboardEntryDTO implements IsSerializable { public class LeaderboardEntryDTO implements Serializable {
private static final long serialVersionUID = -4678693781217869837L;
/** /**
* Identifies the race in which the competitor achieved this score. This makes it possible to find out in which * Identifies the race in which the competitor achieved this score. This makes it possible to find out in which
* fleet the competitor started in this column. * fleet the competitor started in this column.
@@ -56,8 +58,8 @@ public class LeaderboardEntryDTO implements IsSerializable {
/** /**
* If <code>null</code>, no leg details are known yet, the race is not being tracked or the details * If <code>null</code>, no leg details are known yet, the race is not being tracked or the details
* haven't been requested from the server yet. Otherwise, the list holds one entry per {@link Leg} of the * haven't been requested from the server yet. Otherwise, the list holds one entry per <code>Leg</code> of the
* {@link Course} being sailed in the race for which this object holds the scoring details. * <code>Course</code> being sailed in the race for which this object holds the scoring details.
*/ */
public List<LegEntryDTO> legDetails; public List<LegEntryDTO> legDetails;
@@ -1,17 +1,17 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import com.google.gwt.user.client.rpc.IsSerializable;
/** /**
* Holds data about one competitor and all races represented by the owning {@link LeaderboardDTO leaderboard}. * Holds data about one competitor and all races represented by the owning {@link LeaderboardDTO leaderboard}.
* *
* @author Axel Uhl (D043530) * @author Axel Uhl (D043530)
* *
*/ */
public class LeaderboardRowDTO implements IsSerializable { public class LeaderboardRowDTO implements Serializable {
private static final long serialVersionUID = -5421934148931661900L;
public CompetitorDTO competitor; public CompetitorDTO competitor;
public Map<String, LeaderboardEntryDTO> fieldsByRaceColumnName; public Map<String, LeaderboardEntryDTO> fieldsByRaceColumnName;
public Double carriedPoints; public Double carriedPoints;
@@ -1,8 +1,8 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.Map; import java.util.Map;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.NauticalSide;
@@ -14,7 +14,8 @@ import com.sap.sailing.domain.common.NauticalSide;
* @author Axel Uhl (D043530) * @author Axel Uhl (D043530)
* *
*/ */
public class LegEntryDTO implements IsSerializable { public class LegEntryDTO implements Serializable {
private static final long serialVersionUID = -1236734337344886025L;
public Double distanceTraveledInMeters; public Double distanceTraveledInMeters;
public Double averageSpeedOverGroundInKnots; public Double averageSpeedOverGroundInKnots;
public int rank; public int rank;
@@ -1,8 +1,9 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import com.google.gwt.user.client.rpc.IsSerializable; import java.io.Serializable;
public class NamedDTO implements IsSerializable { public class NamedDTO implements Serializable {
private static final long serialVersionUID = -6620231501480297257L;
public String name; public String name;
public NamedDTO() {} public NamedDTO() {}
@@ -1,9 +1,7 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import com.google.gwt.user.client.rpc.IsSerializable;
public class PlacemarkDTO extends NamedDTO implements IsSerializable {
public class PlacemarkDTO extends NamedDTO {
private static final long serialVersionUID = -6734956459060435983L;
private String countryCode; private String countryCode;
private PositionDTO position; private PositionDTO position;
private long population; private long population;
@@ -1,14 +1,12 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import com.google.gwt.user.client.rpc.IsSerializable; public class PlacemarkOrderDTO extends NamedDTO {
private static final long serialVersionUID = -4981887843843495494L;
public class PlacemarkOrderDTO extends NamedDTO implements IsSerializable {
private List<PlacemarkDTO> placemarks; private List<PlacemarkDTO> placemarks;
public PlacemarkOrderDTO() { public PlacemarkOrderDTO() {
@@ -1,9 +1,9 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import com.google.gwt.i18n.client.NumberFormat; import java.io.Serializable;
import com.google.gwt.user.client.rpc.IsSerializable;
public class PositionDTO implements IsSerializable { public class PositionDTO implements Serializable {
private static final long serialVersionUID = -8799012230990258044L;
public double latDeg; public double latDeg;
public double lngDeg; public double lngDeg;
@@ -19,11 +19,6 @@ public class PositionDTO implements IsSerializable {
return "("+latDeg+", "+lngDeg+")"; return "("+latDeg+", "+lngDeg+")";
} }
public String toFormattedString() {
NumberFormat fmt = NumberFormat.getFormat("#.###");
return fmt.format(latDeg)+", "+fmt.format(lngDeg);
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@@ -8,12 +9,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.TimingConstants; import com.sap.sailing.domain.common.TimingConstants;
public class RaceColumnDTO extends NamedDTO implements IsSerializable { public class RaceColumnDTO extends NamedDTO implements Serializable {
private static final long serialVersionUID = -3228244237400937852L;
private boolean medalRace; private boolean medalRace;
private List<FleetDTO> fleets; private List<FleetDTO> fleets;
private Map<FleetDTO, RegattaAndRaceIdentifier> trackedRaceIdentifiersPerFleet; private Map<FleetDTO, RegattaAndRaceIdentifier> trackedRaceIdentifiersPerFleet;
@@ -1,6 +1,7 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
public class RaceColumnInSeriesDTO extends RaceColumnDTO { public class RaceColumnInSeriesDTO extends RaceColumnDTO {
private static final long serialVersionUID = 3308901125173317674L;
private String seriesName; private String seriesName;
private String regattaName; private String regattaName;
@@ -1,12 +1,13 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.util.Date; import java.util.Date;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
public class RaceDTO extends NamedDTO implements IsSerializable { public class RaceDTO extends NamedDTO {
private static final long serialVersionUID = 2613189982608149975L;
/** /**
* Tells if this race is currently being tracked, meaning that a {@link RaceTracker} is * Tells if this race is currently being tracked, meaning that a {@link RaceTracker} is
* listening for incoming GPS fixes, mark passings etc., to update a {@link TrackedRace} object * listening for incoming GPS fixes, mark passings etc., to update a {@link TrackedRace} object
@@ -1,9 +1,11 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
public class RaceStatusDTO implements IsSerializable { public class RaceStatusDTO implements Serializable {
private static final long serialVersionUID = -5665104615048232751L;
public TrackedRaceStatusEnum status; public TrackedRaceStatusEnum status;
public double loadingProgress; public double loadingProgress;
@@ -1,10 +1,11 @@
package com.sap.sailing.gwt.ui.shared; package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
import com.google.gwt.user.client.rpc.IsSerializable; public class TrackedRaceDTO implements Serializable {
private static final long serialVersionUID = -9040930745014670924L;
public class TrackedRaceDTO implements IsSerializable {
/** /**
* Default constructor for GWT-Serialization * Default constructor for GWT-Serialization
*/ */
@@ -407,6 +407,12 @@ public class StoreAndForward implements Runnable {
} }
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "Exception in StoreAndForward", e); logger.log(Level.SEVERE, "Exception in StoreAndForward", e);
try {
Thread.sleep(100); // if the exception persists, wait a little, hoping something may recover
} catch (InterruptedException e1) {
logger.log(Level.INFO, "StoreAndForward.run interrupted; setting stopped=true", e1);
stopped = true;
}
} }
} }
logger.info("StoreAndForward is closing receiving server socket"); logger.info("StoreAndForward is closing receiving server socket");
@@ -5,11 +5,13 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.BindException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.junit.After; import org.junit.After;
@@ -55,7 +57,23 @@ public class ActivelyConnectingStoreAndForwardTest {
sailMasterDummyListenerThread = new Thread("ActivelyConnectingStoreAndForwardTest-Listener") { sailMasterDummyListenerThread = new Thread("ActivelyConnectingStoreAndForwardTest-Listener") {
public void run() { public void run() {
try { try {
ServerSocket ss = new ServerSocket(RECEIVE_PORT); ServerSocket ss = null;
for (int numberOfTries=0; numberOfTries<3 && ss == null; numberOfTries++) {
try {
ss = new ServerSocket(RECEIVE_PORT);
} catch (BindException be) {
logger.log(Level.INFO, "Couldn't bind server socket in StoreAndForward"+
(numberOfTries<2?". Trying again...":""), be);
if (numberOfTries == 2) {
throw be;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
synchronized (ActivelyConnectingStoreAndForwardTest.this) { synchronized (ActivelyConnectingStoreAndForwardTest.this) {
sendingSocket = ss.accept(); sendingSocket = ss.accept();
logger.info("ActivelyConnectingStoreAndForwardTest-Listener accepted socket connect request on port "+RECEIVE_PORT); logger.info("ActivelyConnectingStoreAndForwardTest-Listener accepted socket connect request on port "+RECEIVE_PORT);
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.BindException;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.text.DateFormat; import java.text.DateFormat;
@@ -17,6 +18,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.NavigableSet; import java.util.NavigableSet;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.junit.After; import org.junit.After;
@@ -87,8 +89,20 @@ public class EndToEndListeningStoreAndFowardTest {
swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE; swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE;
swissTimingAdapterPersistence.dropAllMessageData(); swissTimingAdapterPersistence.dropAllMessageData();
swissTimingAdapterPersistence.dropAllRaceMasterData(); swissTimingAdapterPersistence.dropAllRaceMasterData();
storeAndForward = new StoreAndForward(RECEIVE_PORT, CLIENT_PORT, SwissTimingFactory.INSTANCE, storeAndForward = null;
swissTimingAdapterPersistence, mongoDBService); for (int numberOfTries = 0; numberOfTries < 3 && storeAndForward == null; numberOfTries++) {
try {
storeAndForward = new StoreAndForward(RECEIVE_PORT, CLIENT_PORT, SwissTimingFactory.INSTANCE,
swissTimingAdapterPersistence, mongoDBService);
} catch (BindException be) {
logger.log(Level.INFO, "Couldn't bind server socket in StoreAndForward"+
(numberOfTries<2?". Trying again...":""), be);
if (numberOfTries == 2) {
throw be;
}
Thread.sleep(100);
}
}
sendingSocket = new Socket("localhost", RECEIVE_PORT); sendingSocket = new Socket("localhost", RECEIVE_PORT);
sendingStream = sendingSocket.getOutputStream(); sendingStream = sendingSocket.getOutputStream();
swissTimingFactory = SwissTimingFactory.INSTANCE; swissTimingFactory = SwissTimingFactory.INSTANCE;
@@ -114,7 +128,9 @@ public class EndToEndListeningStoreAndFowardTest {
racingEventService.stopTracking(raceHandle.getRegatta()); racingEventService.stopTracking(raceHandle.getRegatta());
} }
logger.info("Calling StoreAndForward.stop() in tearDown"); logger.info("Calling StoreAndForward.stop() in tearDown");
storeAndForward.stop(); if (storeAndForward != null) {
storeAndForward.stop();
}
logger.exiting(getClass().getName(), "tearDown"); logger.exiting(getClass().getName(), "tearDown");
} }
@@ -159,7 +159,7 @@ public class DomainFactoryImpl implements DomainFactory {
com.sap.sailing.domain.base.Course domainCourse = new CourseImpl("Course", waypoints); com.sap.sailing.domain.base.Course domainCourse = new CourseImpl("Course", waypoints);
BoatClass boatClass = getOrCreateBoatClassFromRaceID(raceID); BoatClass boatClass = getOrCreateBoatClassFromRaceID(raceID);
logger.info("Creating RaceDefinitionImpl for race "+raceID); logger.info("Creating RaceDefinitionImpl for race "+raceID);
RaceDefinition result = new RaceDefinitionImpl(raceID, domainCourse, boatClass, competitors); RaceDefinition result = new RaceDefinitionImpl(regatta.getId().toString()+"/"+raceID, domainCourse, boatClass, competitors);
regatta.addRace(result); regatta.addRace(result);
return result; return result;
} }
File diff suppressed because it is too large Load Diff
@@ -4,6 +4,7 @@ import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -44,4 +45,9 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry {
return null; return null;
} }
@Override
public boolean isRaceBeingTracked(RaceDefinition r) {
return false;
}
} }
@@ -11,6 +11,7 @@ Export-Package: com.sap.sailing.domain.base,
com.sap.sailing.domain.confidence, com.sap.sailing.domain.confidence,
com.sap.sailing.domain.confidence.impl;x-friends:="com.sap.sailing.domain.test", com.sap.sailing.domain.confidence.impl;x-friends:="com.sap.sailing.domain.test",
com.sap.sailing.domain.leaderboard, com.sap.sailing.domain.leaderboard,
com.sap.sailing.domain.leaderboard.caching,
com.sap.sailing.domain.leaderboard.impl, com.sap.sailing.domain.leaderboard.impl,
com.sap.sailing.domain.leaderboard.meta, com.sap.sailing.domain.leaderboard.meta,
com.sap.sailing.domain.polarsheets, com.sap.sailing.domain.polarsheets,
@@ -24,4 +25,5 @@ Bundle-ActivationPolicy: lazy
Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0", Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.domain.shared.android;bundle-version="1.0.0", com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
com.sap.sailing.domain.common, com.sap.sailing.domain.common,
org.json.simple;bundle-version="1.1.0" org.json.simple;bundle-version="1.1.0",
com.sap.sailing.geocoding
@@ -3,14 +3,23 @@ package com.sap.sailing.domain.base;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import com.sap.sailing.domain.base.impl.DomainFactoryImpl; import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Placemark;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.PlacemarkDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
public interface DomainFactory extends SharedDomainFactory { public interface DomainFactory extends SharedDomainFactory {
static DomainFactory INSTANCE = new DomainFactoryImpl(); static DomainFactory INSTANCE = new DomainFactoryImpl();
@@ -82,4 +91,16 @@ public interface DomainFactory extends SharedDomainFactory {
ScoringScheme createScoringScheme(ScoringSchemeType scoringSchemeType); ScoringScheme createScoringScheme(ScoringSchemeType scoringSchemeType);
CompetitorDTO convertToCompetitorDTO(Competitor c);
FleetDTO convertToFleetDTO(Series series, Fleet fleet);
FleetDTO convertToFleetDTO(RaceColumn raceColumn, Fleet fleet);
RaceDTO createRaceDTO(TrackedRegattaRegistry trackedRegattaRegistry, boolean b, RegattaAndRaceIdentifier raceIdentifier, TrackedRace trackedRace);
PlacemarkDTO convertToPlacemarkDTO(Placemark placemark);
List<CompetitorDTO> getCompetitorDTOList(List<Competitor> competitors);
} }
@@ -6,29 +6,52 @@ import java.io.Serializable;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Boat; import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Gate; import com.sap.sailing.domain.base.Gate;
import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory; import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Team; import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.CountryCode;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Placemark;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WithID; import com.sap.sailing.domain.common.WithID;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.PlacemarkDTO;
import com.sap.sailing.domain.common.dto.PlacemarkOrderDTO;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.dto.TrackedRaceDTO;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall; import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
@@ -36,10 +59,17 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie; import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
import com.sap.sailing.geocoding.ReverseGeocoder;
public class DomainFactoryImpl implements DomainFactory { public class DomainFactoryImpl implements DomainFactory {
private static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
/** /**
* Ensure that the <em>same</em> string is used as key that is also used to set the {@link Nationality} * Ensure that the <em>same</em> string is used as key that is also used to set the {@link Nationality}
* object's {@link Nationality#getThreeLetterIOCAcronym() IOC code}. * object's {@link Nationality#getThreeLetterIOCAcronym() IOC code}.
@@ -68,6 +98,8 @@ public class DomainFactoryImpl implements DomainFactory {
private final ReferenceQueue<Waypoint> waypointCacheReferenceQueue; private final ReferenceQueue<Waypoint> waypointCacheReferenceQueue;
private final WeakHashMap<Competitor, CompetitorDTO> weakCompetitorDTOCache;
/** /**
* Weak references to {@link Waypoint} objects of this type are registered with * Weak references to {@link Waypoint} objects of this type are registered with
* {@link DomainFactoryImpl#waypointCacheReferenceQueue} upon construction so that when their referents are no * {@link DomainFactoryImpl#waypointCacheReferenceQueue} upon construction so that when their referents are no
@@ -94,6 +126,7 @@ public class DomainFactoryImpl implements DomainFactory {
private final Set<String> mayStartWithNoUpwindLeg; private final Set<String> mayStartWithNoUpwindLeg;
public DomainFactoryImpl() { public DomainFactoryImpl() {
weakCompetitorDTOCache = new WeakHashMap<Competitor, CompetitorDTO>();
waypointCacheReferenceQueue = new ReferenceQueue<Waypoint>(); waypointCacheReferenceQueue = new ReferenceQueue<Waypoint>();
nationalityCache = new HashMap<String, Nationality>(); nationalityCache = new HashMap<String, Nationality>();
markCache = new HashMap<Serializable, Mark>(); markCache = new HashMap<Serializable, Mark>();
@@ -294,4 +327,137 @@ public class DomainFactoryImpl implements DomainFactory {
} }
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name()); throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
} }
@Override
public CompetitorDTO convertToCompetitorDTO(Competitor c) {
CompetitorDTO competitorDTO = weakCompetitorDTOCache.get(c);
if (competitorDTO == null) {
CountryCode countryCode = c.getTeam().getNationality().getCountryCode();
competitorDTO = new CompetitorDTO(c.getName(), countryCode == null ? ""
: countryCode.getTwoLetterISOCode(),
countryCode == null ? "" : countryCode.getThreeLetterIOCCode(), countryCode == null ? ""
: countryCode.getName(), c.getBoat().getSailID(), c.getId().toString(),
new BoatClassDTO(c.getBoat().getBoatClass().getName(), c.getBoat().getBoatClass().getHullLength()
.getMeters()));
weakCompetitorDTOCache.put(c, competitorDTO);
}
return competitorDTO;
}
@Override
public FleetDTO convertToFleetDTO(Series series, Fleet fleet) {
return new FleetDTO(fleet.getName(), series.getName(), fleet.getOrdering(), fleet.getColor());
}
@Override
public FleetDTO convertToFleetDTO(RaceColumn raceColumn, Fleet fleet) {
String seriesNameOfFleet = null;
if (raceColumn instanceof RaceColumnInSeries) {
seriesNameOfFleet = ((RaceColumnInSeries) raceColumn).getSeries().getName();
}
return new FleetDTO(fleet.getName(), seriesNameOfFleet, fleet.getOrdering(), fleet.getColor());
}
@Override
public RaceDTO createRaceDTO(TrackedRegattaRegistry trackedRegattaRegistry, boolean withGeoLocationData, RegattaAndRaceIdentifier raceIdentifier, TrackedRace trackedRace) {
// Optional: Getting the places of the race
PlacemarkOrderDTO racePlaces = withGeoLocationData ? getRacePlaces(trackedRace) : null;
TrackedRaceDTO trackedRaceDTO = new TrackedRaceDTO();
trackedRaceDTO.startOfTracking = trackedRace.getStartOfTracking() == null ? null : trackedRace.getStartOfTracking().asDate();
trackedRaceDTO.hasWindData = trackedRace.hasWindData();
trackedRaceDTO.hasGPSData = trackedRace.hasGPSData();
RaceDTO raceDTO = new RaceDTO(raceIdentifier, trackedRaceDTO, trackedRegattaRegistry.isRaceBeingTracked(trackedRace.getRace()));
raceDTO.places = racePlaces;
raceDTO.startOfRace = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate();
raceDTO.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate();
return raceDTO;
}
private PlacemarkOrderDTO getRacePlaces(TrackedRace trackedRace) {
Pair<Placemark, Placemark> startAndFinish = getStartFinishPlacemarksForTrackedRace(trackedRace);
PlacemarkOrderDTO racePlaces = new PlacemarkOrderDTO();
if (startAndFinish.getA() != null) {
racePlaces.getPlacemarks().add(convertToPlacemarkDTO(startAndFinish.getA()));
}
if (startAndFinish.getB() != null) {
racePlaces.getPlacemarks().add(convertToPlacemarkDTO(startAndFinish.getB()));
}
if (racePlaces.isEmpty()) {
racePlaces = null;
}
return racePlaces;
}
private Pair<Placemark, Placemark> getStartFinishPlacemarksForTrackedRace(TrackedRace race) {
double radiusCalculationFactor = 10.0;
Placemark startBest = null;
Placemark finishBest = null;
// Get start postition
Iterator<Mark> startMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator();
GPSFix startMarkFix = startMarks.hasNext() ? race.getOrCreateTrack(startMarks.next()).getLastRawFix() : null;
Position startPosition = startMarkFix != null ? startMarkFix.getPosition() : null;
if (startPosition != null) {
try {
// Get distance to nearest placemark and calculate the search radius
Placemark startNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(startPosition);
if (startNearest != null) {
Distance startNearestDistance = startNearest.distanceFrom(startPosition);
double startRadius = startNearestDistance.getKilometers() * radiusCalculationFactor;
// Get the estimated best start place
startBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(startPosition, startRadius,
new Placemark.ByPopulationDistanceRatio(startPosition));
}
} catch (IOException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
} catch (org.json.simple.parser.ParseException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
}
}
// Get finish position
Iterator<Mark> finishMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator();
GPSFix finishMarkFix = finishMarks.hasNext() ? race.getOrCreateTrack(finishMarks.next()).getLastRawFix() : null;
Position finishPosition = finishMarkFix != null ? finishMarkFix.getPosition() : null;
if (startPosition != null && finishPosition != null) {
if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) {
finishBest = startBest;
} else {
try {
// Get distance to nearest placemark and calculate the search radius
Placemark finishNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(finishPosition);
Distance finishNearestDistance = finishNearest.distanceFrom(finishPosition);
double finishRadius = finishNearestDistance.getKilometers() * radiusCalculationFactor;
// Get the estimated best finish place
finishBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(finishPosition, finishRadius,
new Placemark.ByPopulationDistanceRatio(finishPosition));
} catch (IOException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
} catch (org.json.simple.parser.ParseException e) {
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
}
}
}
Pair<Placemark, Placemark> placemarks = new Pair<Placemark, Placemark>(startBest, finishBest);
return placemarks;
}
@Override
public PlacemarkDTO convertToPlacemarkDTO(Placemark placemark) {
Position position = placemark.getPosition();
return new PlacemarkDTO(placemark.getName(), placemark.getCountryCode(), new PositionDTO(position.getLatDeg(),
position.getLngDeg()), placemark.getPopulation());
}
@Override
public List<CompetitorDTO> getCompetitorDTOList(List<Competitor> competitors) {
List<CompetitorDTO> result = new ArrayList<CompetitorDTO>();
for (Competitor competitor : competitors) {
result.add(convertToCompetitorDTO(competitor));
}
return result;
}
} }
@@ -60,13 +60,6 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
*/ */
private final boolean persistent; private final boolean persistent;
/**
* Constructs a regatta with an empty {@link RaceLogStore}.
*/
public RegattaImpl(String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id) {
this(EmptyRaceLogStore.INSTANCE, baseName, boatClass, trackedRegattaRegistry, scoringScheme, id, null);
}
/** /**
* Constructs a regatta with an empty {@link RaceLogStore}. * Constructs a regatta with an empty {@link RaceLogStore}.
*/ */
@@ -1,10 +1,13 @@
package com.sap.sailing.domain.leaderboard; package com.sap.sailing.domain.leaderboard;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.RaceColumnListener;
@@ -16,9 +19,12 @@ import com.sap.sailing.domain.common.Named;
import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
/** /**
* A leaderboard is used to display the results of one or more {@link TrackedRace races}. It manages the competitors' * A leaderboard is used to display the results of one or more {@link TrackedRace races}. It manages the competitors'
@@ -61,6 +67,10 @@ public interface Leaderboard extends Named {
Fleet getFleet(); Fleet getFleet();
} }
LeaderboardDTO computeDTO(final TimePoint timePoint,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory)
throws NoWindException;
/** /**
* Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this * Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this
* leaderboard, with suppressed competitors removed. See also {@link #getAllCompetitors()} which also returns * leaderboard, with suppressed competitors removed. See also {@link #getAllCompetitors()} which also returns
@@ -381,4 +391,43 @@ public interface Leaderboard extends Named {
* @return the default {@link CourseArea} for all races of this leaderboard. * @return the default {@link CourseArea} for all races of this leaderboard.
*/ */
CourseArea getDefaultCourseArea(); CourseArea getDefaultCourseArea();
/**
* Must be called when the leaderboard is removed from its server, becoming in accessible. This will give the leaderboard
* a chance to release all its resources that won't be collected or freed automatically. In particular, a leaderboard may
* hold on to listeners which in turn are registered with {@link TrackedRace}s and therefore won't be released to the garbage
* collector unless the tracked race becomes unreferenced. This may take long because the tracked race can generally
* be referenced by more than one leaderboard. For example, a test leaderboard may additionally reference a tracked
* race already referenced by an "official" leaderboard. When the test leaderboard is removed, its listeners would not
* be released and avoid garbage-collecting the test leaderboard. When calling this method, the leaderboard will cleanly
* unregister its listeners from the tracked races and therefore become eligible for garbage collection.
*/
void destroy();
/**
* Returns a data transfer object (DTO) that has the leaderboard's data for the race columns with basic information
* for all columns, and with detailed information for those columns whose names are provided in
* <code>namesOfRaceColumnsForWhichToLoadLegDetails</code>. The leaderboard is evaluated at <code>timePoint</code>,
* or, if <code>timePoint</code> is <code>null</code>, for the "live" time point (now - delay).
* <p>
*
* The implementation uses different approaches for caching the results. For "live" requests, a
* {@link LiveLeaderboardUpdater} is used to keep refreshing the cached results. Other queries are managed by a
* {@link LeaderboardDTOCache} which remembers a number of results before it starts evicting the least frequently
* used ones.
*
* @param timePoint
* <code>null</code> for "live" results for the time point "now" - delay; otherwise, the explicit time
* point at which to evaluate the leaderboard status
* @param namesOfRaceColumnsForWhichToLoadLegDetails
* the names of the race columns of which to expand the details in the result
* @param trackedRegattaRegistry
* used to determine which of the races are still being tracked and which ones are not
* @param baseDomainFactory
* required as factory and cache for various DTO types
*/
LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
InterruptedException, ExecutionException;
} }
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardCache;
import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFix;
@@ -1,4 +1,7 @@
package com.sap.sailing.domain.leaderboard; package com.sap.sailing.domain.leaderboard.caching;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
/** /**
* Interface for leaderboard state cache management. When passed to a {@link LeaderboardCacheManager}, leaderboards * Interface for leaderboard state cache management. When passed to a {@link LeaderboardCacheManager}, leaderboards
@@ -1,10 +1,9 @@
package com.sap.sailing.gwt.ui.server; package com.sap.sailing.domain.leaderboard.caching;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -15,18 +14,19 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardCache;
import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager; import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
import com.sap.sailing.domain.leaderboard.ScoreCorrection; import com.sap.sailing.domain.leaderboard.ScoreCorrection;
import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener; import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener;
import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.util.impl.LockUtil; import com.sap.sailing.util.impl.LockUtil;
/** /**
@@ -45,13 +45,13 @@ public class LeaderboardDTOCache implements LeaderboardCache {
private static final Logger logger = Logger.getLogger(LeaderboardDTOCache.class.getName()); private static final Logger logger = Logger.getLogger(LeaderboardDTOCache.class.getName());
/** /**
* In live operations, {@link #getLeaderboardByName(String, Date, Collection)} is the application's * In live operations, {@link #getLeaderboardByName(Date, Collection, DomainFactory, TrackedRegattaRegistry)} is the application's
* bottleneck. When two clients ask the same data for the same leaderboard with their * bottleneck. When two clients ask the same data for the same leaderboard with their
* <code>waitForLatestAnalyses</code> parameters set to <code>false</code>, expansion state and (quantized) time * <code>waitForLatestAnalyses</code> parameters set to <code>false</code>, expansion state and (quantized) time
* stamp, no two computations should be spawned for the two clients. Instead, if the computation is still running, * stamp, no two computations should be spawned for the two clients. Instead, if the computation is still running,
* all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache. * all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache.
*/ */
private final WeakHashMap<Leaderboard, Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>> leaderboardCache; private final Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> leaderboardCache;
private int leaderboardByNameCacheHitCount; private int leaderboardByNameCacheHitCount;
private int leaderboardByNameCacheMissCount; private int leaderboardByNameCacheMissCount;
@@ -62,51 +62,45 @@ public class LeaderboardDTOCache implements LeaderboardCache {
*/ */
private final boolean waitForLatestAnalyses; private final boolean waitForLatestAnalyses;
private final SailingServiceImpl sailingService; private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
/** /**
* A multi-threaded executor for the currently running leaderboard requests, executing the {@link Future}s currently * A multi-threaded executor for the currently running leaderboard requests, executing the {@link Future}s currently
* pending. * pending.
*/ */
private final Executor computeLeadearboardByNameExecutor; private static final Executor computeLeadearboardByNameExecutor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>());
private final LeaderboardCacheManager leaderboardCacheManager; private final LeaderboardCacheManager leaderboardCacheManager;
public LeaderboardDTOCache(SailingServiceImpl sailingService, boolean waitForLatestAnalyses) { private final Leaderboard leaderboard;
this.sailingService = sailingService;
public LeaderboardDTOCache(boolean waitForLatestAnalyses, Leaderboard leaderboard) {
this.leaderboard = leaderboard;
this.waitForLatestAnalyses = waitForLatestAnalyses; this.waitForLatestAnalyses = waitForLatestAnalyses;
// if the leaderboard becomes weakly referenced and eventually GCed, then so can the cached results for it // if the leaderboard becomes weakly referenced and eventually GCed, then so can the cached results for it
this.leaderboardCache = new WeakHashMap<Leaderboard, Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>>(); this.leaderboardCache = new LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>() {
this.leaderboardCacheManager = new LeaderboardCacheManager(this);
final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
this.computeLeadearboardByNameExecutor =
new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>());
}
@Override
public void invalidate(Leaderboard leaderboard) {
synchronized (leaderboardCache) {
leaderboardCache.remove(leaderboard);
}
}
@Override
public void add(Leaderboard leaderboard) {
LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> map = new LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>() {
private static final long serialVersionUID = 7287916997229815039L; private static final long serialVersionUID = 7287916997229815039L;
@Override @Override
protected boolean removeEldestEntry(Map.Entry<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> e) { protected boolean removeEldestEntry(Map.Entry<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> e) {
return size() > 10; // remember 10 LeaderboardDTOs per leaderborad return size() > 10; // remember 10 LeaderboardDTOs per leaderborad
} }
}; };
this.leaderboardCacheManager = new LeaderboardCacheManager(this);
this.leaderboardCacheManager.add(leaderboard);
}
@Override
public void invalidate(Leaderboard leaderboard) {
synchronized (leaderboardCache) { synchronized (leaderboardCache) {
leaderboardCache.put(leaderboard, map); leaderboardCache.clear();
} }
} }
@Override
public void add(Leaderboard leaderboard) {}
/** /**
* If the cache holds entries for the <code>leaderboard</code> requested, compare <code>timePoint</code> to the * If the cache holds entries for the <code>leaderboard</code> requested, compare <code>timePoint</code> to the
* {@link #getLatestModification latest modification} affecting the <code>leaderboard</code>. If * {@link #getLatestModification latest modification} affecting the <code>leaderboard</code>. If
@@ -119,9 +113,9 @@ public class LeaderboardDTOCache implements LeaderboardCache {
* {@link SailingServiceImpl#computeLeaderboardByName(Leaderboard, TimePoint, Collection, boolean)} if a new cache * {@link SailingServiceImpl#computeLeaderboardByName(Leaderboard, TimePoint, Collection, boolean)} if a new cache
* entry needs to be computed. Caching distinguished between * entry needs to be computed. Caching distinguished between
*/ */
public LeaderboardDTO getLeaderboardByName(final Leaderboard leaderboard, final TimePoint timePoint, public LeaderboardDTO getLeaderboardByName(final TimePoint timePoint, final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails) final DomainFactory baseDomainFactory, final TrackedRegattaRegistry trackedRegattaRegistry) throws NoWindException, InterruptedException,
throws NoWindException, InterruptedException, ExecutionException { ExecutionException {
long startOfRequestHandling = System.currentTimeMillis(); long startOfRequestHandling = System.currentTimeMillis();
final TimePoint adjustedTimePoint; final TimePoint adjustedTimePoint;
TimePoint timePointOfLastModification = leaderboard.getTimePointOfLatestModification(); TimePoint timePointOfLastModification = leaderboard.getTimePointOfLatestModification();
@@ -134,50 +128,40 @@ public class LeaderboardDTOCache implements LeaderboardCache {
Util.Pair<TimePoint, Collection<String>> key = new Util.Pair<TimePoint, Collection<String>>(adjustedTimePoint, Util.Pair<TimePoint, Collection<String>> key = new Util.Pair<TimePoint, Collection<String>>(adjustedTimePoint,
namesOfRaceColumnsForWhichToLoadLegDetails); namesOfRaceColumnsForWhichToLoadLegDetails);
FutureTask<LeaderboardDTO> future = null; FutureTask<LeaderboardDTO> future = null;
Map<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> map = null;
boolean cacheHit = false; boolean cacheHit = false;
synchronized (leaderboardCache) { /*
map = leaderboardCache.get(leaderboard); * Waiting for latest analyzes results largely regards wind estimation and maneuver cache; see SmartFutureCache.
if (map == null) { * Even if waitForLatestAnalysis is requested, it is OK to cache. The cache would be invalidated when the race
future = null; * changes, forcing a new re-calculation based on the latest analysis results. Once the race stabilizes, the
leaderboardCacheManager.add(leaderboard); // adds it to leaderboardCache by calling back our add(Leaderboard) method * latest analysis results for maneuvers and wind estimation will no longer change and can quickly be obtained
map = leaderboardCache.get(leaderboard); * from the respective SmartFutureCache. At the same time, if a LeaderboardDTOCache entry is found, that was
} else { * based on the latest analysis results at the time. If new evidence is received, that would also invalidate the
/* * LeaderboardDTOCache. Therefore, it's okay to re-use the LeaderboardDTOCache match even if the latest analysis
* Waiting for latest analyzes results largely regards wind estimation and maneuver cache; see * results are requested.
* SmartFutureCache. Even if waitForLatestAnalysis is requested, it is OK to cache. The cache would be */
* invalidated when the race changes, forcing a new re-calculation based on the latest analysis results. future = leaderboardCache.get(key);
* Once the race stabilizes, the latest analysis results for maneuvers and wind estimation will no if (future == null) {
* longer change and can quickly be obtained from the respective SmartFutureCache. At the same time, if final Thread callerThread = Thread.currentThread();
* a LeaderboardDTOCache entry is found, that was based on the latest analysis results at the time. If future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
* new evidence is received, that would also invalidate the LeaderboardDTOCache. Therefore, it's okay to @Override
* re-use the LeaderboardDTOCache match even if the latest analysis results are requested. public LeaderboardDTO call() throws Exception {
*/ // The outer getLeaderboardByName(...) method will always wait for this future's completion.
future = map.get(key); // Therefore, it's safe to propagate the calling thread's locks to this one:
} LockUtil.propagateLockSetFrom(callerThread);
if (future == null) { try {
final Thread callerThread = Thread.currentThread(); LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() { namesOfRaceColumnsForWhichToLoadLegDetails, waitForLatestAnalyses,
@Override trackedRegattaRegistry, baseDomainFactory);
public LeaderboardDTO call() throws Exception { return result;
// The outer getLeaderboardByName(...) method will always wait for this future's completion. } finally {
// Therefore, it's safe to propagate the calling thread's locks to this one: LockUtil.unpropagateLockSetFrom(callerThread);
LockUtil.propagateLockSetFrom(callerThread);
try {
LeaderboardDTO result = sailingService.computeLeaderboardByName(leaderboard,
adjustedTimePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
waitForLatestAnalyses);
return result;
} finally {
LockUtil.unpropagateLockSetFrom(callerThread);
}
} }
}); }
computeLeadearboardByNameExecutor.execute(future); });
map.put(key, future); computeLeadearboardByNameExecutor.execute(future);
} else { leaderboardCache.put(key, future);
cacheHit = true; } else {
} cacheHit = true;
} }
if (cacheHit) { if (cacheHit) {
leaderboardByNameCacheHitCount++; leaderboardByNameCacheHitCount++;
@@ -1,4 +1,4 @@
package com.sap.sailing.gwt.ui.server; package com.sap.sailing.domain.leaderboard.caching;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -8,11 +8,13 @@ import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
/** /**
* Computing a leaderboard live, particularly when the fleet tracked is large, requires considerable resources. * Computing a leaderboard live, particularly when the fleet tracked is large, requires considerable resources.
@@ -54,8 +56,6 @@ public class LiveLeaderboardUpdater implements Runnable {
private final Leaderboard leaderboard; private final Leaderboard leaderboard;
private final SailingServiceImpl sailingService;
/** /**
* Live leaderboard results containing column details for all column names in * Live leaderboard results containing column details for all column names in
* {@link #columnNamesForWhichCurrentLiveLeaderboardHasTheDetails}. May be <code>null</code> if * {@link #columnNamesForWhichCurrentLiveLeaderboardHasTheDetails}. May be <code>null</code> if
@@ -100,10 +100,15 @@ public class LiveLeaderboardUpdater implements Runnable {
* takes longer than {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds. * takes longer than {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds.
*/ */
private Thread thread; private Thread thread;
private TrackedRegattaRegistry trackedRegattaRegistry;
private DomainFactory baseDomainFactory;
public LiveLeaderboardUpdater(Leaderboard leaderboard, SailingServiceImpl sailingService) { public LiveLeaderboardUpdater(Leaderboard leaderboard, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) {
this.leaderboard = leaderboard; this.leaderboard = leaderboard;
this.sailingService = sailingService; this.trackedRegattaRegistry = trackedRegattaRegistry;
this.baseDomainFactory = baseDomainFactory;
this.timePointOfLastRequestForColumnDetails = new HashMap<String, TimePoint>(); this.timePointOfLastRequestForColumnDetails = new HashMap<String, TimePoint>();
this.columnNamesForWhichCurrentLiveLeaderboardHasTheDetails = new HashSet<String>(); this.columnNamesForWhichCurrentLiveLeaderboardHasTheDetails = new HashSet<String>();
} }
@@ -248,8 +253,9 @@ public class LiveLeaderboardUpdater implements Runnable {
TimePoint timeLastUpdateWasStarted = now; TimePoint timeLastUpdateWasStarted = now;
try { try {
final Set<String> namesOfRaceColumnsForWhichToLoadLegDetails = getColumnNamesForWhichToFetchDetails(timePoint); final Set<String> namesOfRaceColumnsForWhichToLoadLegDetails = getColumnNamesForWhichToFetchDetails(timePoint);
LeaderboardDTO newCacheValue = sailingService.computeLeaderboardByName(leaderboard, timePoint, LeaderboardDTO newCacheValue = leaderboard.computeDTO(timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails, /* waitForLatestAnalyses */false); namesOfRaceColumnsForWhichToLoadLegDetails, /* waitForLatestAnalyses */false,
trackedRegattaRegistry, baseDomainFactory);
updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, newCacheValue); updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, newCacheValue);
} catch (NoWindException e) { } catch (NoWindException e) {
logger.info("Unable to update cached leaderboard results for leaderboard " + leaderboard.getName() + ": " logger.info("Unable to update cached leaderboard results for leaderboard " + leaderboard.getName() + ": "
@@ -1,41 +1,85 @@
package com.sap.sailing.domain.leaderboard.impl; package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NavigableSet; import java.util.NavigableSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Competitor; 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.Fleet; import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener; import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint; import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindError;
import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.LegEntryDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.util.impl.RaceColumnListeners; import com.sap.sailing.util.impl.RaceColumnListeners;
/** /**
@@ -49,6 +93,8 @@ import com.sap.sailing.util.impl.RaceColumnListeners;
*/ */
public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener {
private static final long serialVersionUID = 330156778603279333L; private static final long serialVersionUID = 330156778603279333L;
private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName());
static final Double DOUBLE_0 = new Double(0); static final Double DOUBLE_0 = new Double(0);
@@ -78,6 +124,27 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
* A synchronized set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. * A synchronized set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}.
*/ */
private final Set<Competitor> suppressedCompetitors; private final Set<Competitor> suppressedCompetitors;
private final Map<Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache;
/**
* This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor}
* can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't
* move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently,
* ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either).
*/
private transient Executor raceDetailsExecutor;
/**
* Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}.
*/
private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
private transient ThreadPoolExecutor executor;
private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
private transient LeaderboardDTOCache leaderboardDTOCache;
/** /**
* A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
@@ -170,14 +237,129 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
} }
} }
public AbstractSimpleLeaderboardImpl(SettableScoreCorrection scoreCorrection, /**
ThresholdBasedResultDiscardingRule resultDiscardingRule) { * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked race
* changes in any way.
*
* @author Axel Uhl (D043530)
*
*/
private class CacheInvalidationListener implements RaceChangeListener {
private final TrackedRace trackedRace;
private final Competitor competitor;
public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) {
this.trackedRace = trackedRace;
this.competitor = competitor;
}
public TrackedRace getTrackedRace() {
return trackedRace;
}
public void removeFromTrackedRace() {
trackedRace.removeListener(this);
}
private void invalidateCacheAndRemoveThisListenerFromTrackedRace() {
synchronized (raceDetailsAtEndOfTrackingCache) {
raceDetailsAtEndOfTrackingCache.remove(new Pair<TrackedRace, Competitor>(trackedRace, competitor));
removeFromTrackedRace();
}
}
@Override
public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void statusChanged(TrackedRaceStatus newStatus) {
// when the status changes away from LOADING, calculations may start or resume, making it necessary to clear the cache
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
Iterable<MarkPassing> markPassings) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void windDataReceived(Wind wind, WindSource windSource) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void windDataRemoved(Wind wind, WindSource windSource) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void delayToLiveChanged(long delayToLiveInMillis) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@Override
public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
}
public AbstractSimpleLeaderboardImpl(SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
this.carriedPoints = new HashMap<Competitor, Double>(); this.carriedPoints = new HashMap<Competitor, Double>();
this.scoreCorrection = scoreCorrection; this.scoreCorrection = scoreCorrection;
this.displayNames = new HashMap<Competitor, String>(); this.displayNames = new HashMap<Competitor, String>();
this.resultDiscardingRule = resultDiscardingRule; this.resultDiscardingRule = resultDiscardingRule;
this.suppressedCompetitors = Collections.synchronizedSet(new HashSet<Competitor>()); this.suppressedCompetitors = Collections.synchronizedSet(new HashSet<Competitor>());
raceColumnListeners = new RaceColumnListeners(); this.raceColumnListeners = new RaceColumnListeners();
this.raceDetailsAtEndOfTrackingCache = new HashMap<Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>();
initTransientFields();
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
initTransientFields();
}
private void initTransientFields() {
this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
// When many updates are triggered in a short period of time by a single thread, ensure that the single thread
// providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
// core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>());
}
@Override
public void destroy() {
for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) {
cacheInvalidationListener.removeFromTrackedRace();
}
} }
@Override @Override
@@ -490,6 +672,21 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override @Override
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
// It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets;
// only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink
// all cache invalidation listeners for said trackedRace
if (!Util.contains(getTrackedRaces(), trackedRace)) {
synchronized (cacheInvalidationListeners) {
for (Iterator<CacheInvalidationListener> cacheInvalidationListenerIter=cacheInvalidationListeners.iterator();
cacheInvalidationListenerIter.hasNext(); ) {
CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next();
if (cacheInvalidationListener.getTrackedRace() == trackedRace) {
cacheInvalidationListener.removeFromTrackedRace();
cacheInvalidationListenerIter.remove();
}
}
}
}
} }
@Override @Override
@@ -769,4 +966,553 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
} }
@Override
public LeaderboardDTO computeDTO(final TimePoint timePoint,
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses,
TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory)
throws NoWindException {
long startOfRequestHandling = System.currentTimeMillis();
LeaderboardDTO result = null;
result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity()==null ?
null : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
this.getScoreCorrection()==null?null:this.getScoreCorrection().getComment(),
this.getScoringScheme().isHigherBetter());
result.competitors = new ArrayList<CompetitorDTO>();
result.name = this.getName();
result.competitorDisplayNames = new HashMap<CompetitorDTO, String>();
for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
}
for (RaceColumn raceColumn : this.getRaceColumns()) {
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
this.getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint));
for (Fleet fleet : raceColumn.getFleets()) {
TimePoint latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = null;
RegattaAndRaceIdentifier raceIdentifier = null;
RaceDTO race = null;
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(raceColumn, fleet);
if (trackedRace != null) {
raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta()
.getName(), trackedRace.getRace().getName());
race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, false, raceIdentifier, trackedRace);
if (trackedRace.hasStarted(timePoint) && trackedRace.hasGPSData() && trackedRace.hasWindData()) {
TimePoint liveTimePointForTrackedRace = timePoint;
final TimePoint endOfRace = trackedRace.getEndOfRace();
if (endOfRace != null) {
liveTimePointForTrackedRace = endOfRace;
}
latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = liveTimePointForTrackedRace;
}
}
// Note: the RaceColumnDTO won't be created by the following addRace call because it has been created
// above by the result.createEmptyRaceColumn call
result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race);
if (latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive != null) {
raceColumnDTO.setWhenLastTrackedRaceWasLive(fleetDTO, latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive.asDate());
}
}
result.setCompetitorsFromBestToWorst(raceColumnDTO, baseDomainFactory.getCompetitorDTOList(this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
}
result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis());
result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>();
result.hasCarriedPoints = this.hasCarriedPoints();
result.discardThresholds = this.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces();
// Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep invalidating
// the ranks cache in TrackedLegImpl. Then problem then is that the sorting based on wind data is repeated for each
// competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg details
// are requested only once and pass them into getLeaderboardEntryDTO
final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor,Integer>>();
for (final RaceColumn raceColumn : this.getRaceColumns()) {
// if details for the column are requested, cache the leg's ranks
if (namesOfRaceColumnsForWhichToLoadLegDetails != null
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) {
for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null) {
trackedRace.getRace().getCourse().lockForRead();
try {
for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint));
}
} finally {
trackedRace.getRace().getCourse().unlockAfterRead();
}
}
}
}
}
for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) {
CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
LeaderboardRowDTO row = new LeaderboardRowDTO();
row.competitor = competitorDTO;
row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>();
row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null;
addOverallDetailsToRow(timePoint, competitor, row);
result.competitors.add(competitorDTO);
Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
for (final RaceColumn raceColumn : this.getRaceColumns()) {
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(new Callable<LeaderboardEntryDTO>() {
@Override
public LeaderboardEntryDTO call() {
try {
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint);
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
namesOfRaceColumnsForWhichToLoadLegDetails != null
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName()),
waitForLatestAnalyses, legRanksCache, baseDomainFactory);
} catch (NoWindException e) {
logger.info("Exception trying to compute leaderboard entry for competitor "+competitor.getName()+
" in race column "+raceColumn.getName()+": "+e.getMessage());
logger.throwing(AbstractSimpleLeaderboardImpl.class.getName(), "computeLeaderboardByName.future.call()", e);
throw new NoWindError(e);
}
}
});
executor.execute(future);
futuresForColumnName.put(raceColumn.getName(), future);
}
for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
try {
row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
// See also bug 1371: for stability reasons, don't let the exception percolate but rather accept null values.
// If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So this helps
// robustness from a user's perspective.
logger.log(Level.SEVERE, AbstractSimpleLeaderboardImpl.class.getName()+".computeDTO("
+this.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+
"): exception during computing leaderboard entry for competitor "+competitor.getName()+
" in race column "+raceColumnNameAndFuture.getKey()+". Leaving empty.", e);
}
}
result.rows.put(competitorDTO, row);
String displayName = this.getDisplayName(competitor);
if (displayName != null) {
result.competitorDisplayNames.put(competitorDTO, displayName);
}
}
logger.info("computeLeaderboardByName("+this.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+") took "+
(System.currentTimeMillis()-startOfRequestHandling)+"ms");
return result;
}
private void addOverallDetailsToRow(final TimePoint timePoint,
final Competitor competitor, LeaderboardRowDTO row) throws NoWindException {
final Pair<GPSFixMoving, Speed> maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint);
if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) {
row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots();
row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate();
}
final Long totalTimeSailedDownwindInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.DOWNWIND, timePoint);
row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwindInMilliseconds==null?null:1./1000.*totalTimeSailedDownwindInMilliseconds;
final Long totalTimeSailedUpwindInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.UPWIND, timePoint);
row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwindInMilliseconds==null?null:1./1000.*totalTimeSailedUpwindInMilliseconds;
final Long totalTimeSailedReachingInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.REACHING, timePoint);
row.totalTimeSailedReachingInSeconds = totalTimeSailedReachingInMilliseconds==null?null:1./1000.*totalTimeSailedReachingInMilliseconds;
final Long totalTimeSailedInMilliseconds = this.getTotalTimeSailedInMilliseconds(competitor, timePoint);
row.totalTimeSailedInSeconds = totalTimeSailedInMilliseconds==null?null:1./1000.*totalTimeSailedInMilliseconds;
final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
}
/**
* @param waitForLatestAnalyses
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
* may not reflect all data already received; otherwise, the method will always block for the latest
* cache updates to have happened before returning.
* @param baseDomainFactory TODO
*/
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory) throws NoWindException {
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
entryDTO.netPoints = entry.getNetPoints();
entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
entryDTO.totalPoints = entry.getTotalPoints();
entryDTO.reasonForMaxPoints = entry.getMaxPointsReason();
entryDTO.discarded = entry.isDiscarded();
if (addLegDetails && trackedRace != null) {
try {
RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache);
entryDTO.legDetails = raceDetails.getLegDetails();
entryDTO.windwardDistanceToOverallLeaderInMeters = raceDetails.getWindwardDistanceToOverallLeader() == null ? null
: raceDetails.getWindwardDistanceToOverallLeader().getMeters();
entryDTO.averageCrossTrackErrorInMeters = raceDetails.getAverageCrossTrackError() == null ? null
: raceDetails.getAverageCrossTrackError().getMeters();
final TimePoint startOfRace = trackedRace.getStartOfRace();
if (startOfRace != null) {
Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine(competitor,
startOfRace);
entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null
: distanceToStartLineAtStartOfRace.getMeters();
Speed speedAtStartTime = trackedRace.getTrack(competitor).getEstimatedSpeed(startOfRace);
entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null : speedAtStartTime.getKnots();
NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
trackedRace.lockForRead(competitorMarkPassings);
try {
if (!competitorMarkPassings.isEmpty()) {
TimePoint competitorStartTime = competitorMarkPassings.iterator().next().getTimePoint();
Speed competitorSpeedWhenPassingStart = trackedRace.getTrack(competitor).getEstimatedSpeed(
competitorStartTime);
entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null
: competitorSpeedWhenPassingStart.getKnots();
entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace.getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor);
entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null :
distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters();
}
} finally {
trackedRace.unlockAfterRead(competitorMarkPassings);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception
}
}
final Fleet fleet = entry.getFleet();
entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(raceColumn, fleet);
return entryDTO;
}
private static class RaceDetails {
private final List<LegEntryDTO> legDetails;
private final Distance windwardDistanceToOverallLeader;
private final Distance averageCrossTrackError;
public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToOverallLeader,
Distance averageCrossTrackError) {
super();
this.legDetails = legDetails;
this.windwardDistanceToOverallLeader = windwardDistanceToOverallLeader;
this.averageCrossTrackError = averageCrossTrackError;
}
public List<LegEntryDTO> getLegDetails() {
return legDetails;
}
public Distance getWindwardDistanceToOverallLeader() {
return windwardDistanceToOverallLeader;
}
public Distance getAverageCrossTrackError() {
return averageCrossTrackError;
}
}
/**
* If <code>timePoint</code> is after the end of the race's tracking the query will be adjusted to obtain the values
* at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this
* way equals the end of the tracking time, the query results will be looked up in a cache first and if not found,
* they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener}
* will be registered with the race which will be triggered for any event received by the race.
* @param waitForLatestAnalyses
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
* may not reflect all data already received; otherwise, the method will always block for the latest
* cache updates to have happened before returning.
*/
private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException, InterruptedException, ExecutionException {
RaceDetails raceDetails;
if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache);
} else {
raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache);
}
return raceDetails;
}
private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache)
throws NoWindException, InterruptedException, ExecutionException {
final Pair<TrackedRace, Competitor> key = new Pair<TrackedRace, Competitor>(trackedRace, competitor);
RunnableFuture<RaceDetails> raceDetails;
synchronized (raceDetailsAtEndOfTrackingCache) {
raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
if (raceDetails == null) {
raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() {
@Override
public RaceDetails call() throws Exception {
TimePoint end = trackedRace.getEndOfRace();
if (end == null) {
end = trackedRace.getEndOfTracking();
}
return calculateRaceDetails(trackedRace, competitor, end,
// TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock
/* TODO old comment when it was still true: "because this is done only once after end of tracking" */
/* waitForLatestAnalyses (maneuver and cross track error) */ false,
legRanksCache);
}
});
raceDetailsExecutor.execute(raceDetails);
raceDetailsAtEndOfTrackingCache.put(key, raceDetails);
final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor);
trackedRace.addListener(cacheInvalidationListener);
cacheInvalidationListeners.add(cacheInvalidationListener);
}
}
return raceDetails.get();
}
private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
List<LegEntryDTO> legDetails;
legDetails = new ArrayList<LegEntryDTO>();
final Course course = trackedRace.getRace().getCourse();
course.lockForRead(); // hold back any course re-configurations while looping over the legs
try {
for (Leg leg : course.getLegs()) {
LegEntryDTO legEntry;
// We loop over a copy of the course's legs; during a course change, legs may become "stale," even with
// regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change
// immediately. That's why we've acquired a read lock for the course above.
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) {
legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache);
} else {
legEntry = null;
}
legDetails.add(legEntry);
}
final Distance windwardDistanceToOverallLeader = trackedRace == null ? null : trackedRace
.getWindwardDistanceToOverallLeader(competitor, timePoint);
final Distance averageCrossTrackError = trackedRace == null ? null : trackedRace.getAverageCrossTrackError(
competitor, timePoint, waitForLatestAnalyses);
return new RaceDetails(legDetails, windwardDistanceToOverallLeader, averageCrossTrackError);
} finally {
course.unlockAfterRead();
}
}
private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
LegEntryDTO result;
if (trackedLeg == null) {
result = null;
} else {
result = new LegEntryDTO();
final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint);
result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
final Distance averageCrossTrackError = trackedLeg.getAverageCrossTrackError(timePoint, waitForLatestAnalyses);
result.averageCrossTrackErrorInMeters = averageCrossTrackError == null ? null : averageCrossTrackError.getMeters();
Double speedOverGroundInKnots;
if (trackedLeg.hasFinishedLeg(timePoint)) {
speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
} else {
final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots();
}
result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
result.estimatedTimeToNextWaypointInSeconds = trackedLeg.getEstimatedTimeToNextMarkInSeconds(timePoint);
result.timeInMilliseconds = trackedLeg.getTimeInMilliSeconds(timePoint);
result.finished = trackedLeg.hasFinishedLeg(timePoint);
result.gapToLeaderInSeconds = trackedLeg.getGapToLeaderInSeconds(timePoint,
legRanksCache.get(trackedLeg.getLeg()).entrySet().iterator().next().getKey());
if (result.gapToLeaderInSeconds != null) {
// FIXME problem: asking just after the beginning of the leg yields very different values from asking for the end of the previous leg.
// This is because for the previous leg it's decided based on the mark passings; for the next (current) leg it's decided based on
// windward distance and VMG
Double gapAtEndOfPreviousLegInSeconds = getGapAtEndOfPreviousLegInSeconds(trackedLeg);
if (gapAtEndOfPreviousLegInSeconds != null) {
result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLegInSeconds;
}
}
LinkedHashMap<Competitor, Integer> legRanks = legRanksCache.get(trackedLeg.getLeg());
if (legRanks != null) {
result.rank = legRanks.get(trackedLeg.getCompetitor());
} else {
result.rank = trackedLeg.getRank(timePoint);
}
result.started = trackedLeg.hasStartedLeg(timePoint);
Speed velocityMadeGood;
if (trackedLeg.hasFinishedLeg(timePoint)) {
velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint);
} else {
velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint);
}
result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots();
Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint);
result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo
.getMeters();
final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace();
if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) {
// not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg
// because that may have been detected as slightly before the mark passing time, hence associated with the previous leg
List<Maneuver> maneuvers = trackedLeg.getTrackedLeg().getTrackedRace()
.getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses);
if (maneuvers != null) {
result.numberOfManeuvers = new HashMap<ManeuverType, Integer>();
result.numberOfManeuvers.put(ManeuverType.TACK, 0);
result.numberOfManeuvers.put(ManeuverType.JIBE, 0);
result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0);
Map<ManeuverType, Double> totalManeuverLossInMeters = new HashMap<ManeuverType, Double>();
totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0);
totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0);
totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0);
TimePoint startOfLeg = trackedLeg.getStartTime();
for (Maneuver maneuver : maneuvers) {
// don't count maneuvers that were in previous legs
switch (maneuver.getType()) {
case TACK:
case JIBE:
case PENALTY_CIRCLE:
if (!maneuver.getTimePoint().before(startOfLeg) && (!trackedLeg.hasFinishedLeg(timePoint) ||
maneuver.getTimePoint().before(trackedLeg.getFinishTime()))) {
if (maneuver.getManeuverLoss() != null) {
result.numberOfManeuvers.put(maneuver.getType(),
result.numberOfManeuvers.get(maneuver.getType()) + 1);
totalManeuverLossInMeters.put(maneuver.getType(),
totalManeuverLossInMeters.get(maneuver.getType())
+ maneuver.getManeuverLoss().getMeters());
}
}
break;
case MARK_PASSING:
// analyze all mark passings, not only those after this leg's start, to catch the mark passing
// maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver;
if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) {
result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide();
}
break;
default:
/* Do nothing here.
* Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard.
*/
}
}
result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>();
for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE,
ManeuverType.PENALTY_CIRCLE }) {
if (result.numberOfManeuvers.get(maneuverType) != 0) {
result.averageManeuverLossInMeters.put(
maneuverType,
totalManeuverLossInMeters.get(maneuverType)
/ result.numberOfManeuvers.get(maneuverType));
}
}
}
}
}
return result;
}
private Double getGapAtEndOfPreviousLegInSeconds(TrackedLegOfCompetitor trackedLeg) throws NoWindException {
final Double result;
final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse();
course.lockForRead();
try {
int indexOfStartWaypoint = course.getIndexOfWaypoint(trackedLeg.getLeg().getFrom());
if (indexOfStartWaypoint == 0) {
// trackedLeg was the first leg; gap is determined by gap of start line passing time points
Iterable<MarkPassing> markPassingsForLegStart = trackedLeg.getTrackedLeg().getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
trackedLeg.getTrackedLeg().getTrackedRace().lockForRead(markPassingsForLegStart);
try {
final Iterator<MarkPassing> markPassingsIter = markPassingsForLegStart.iterator();
if (markPassingsIter.hasNext()) {
TimePoint firstStart = markPassingsIter.next().getTimePoint();
final MarkPassing markPassingForFrom = trackedLeg.getTrackedLeg().getTrackedRace()
.getMarkPassing(trackedLeg.getCompetitor(), trackedLeg.getLeg().getFrom());
if (markPassingForFrom != null) {
TimePoint competitorStart = markPassingForFrom.getTimePoint();
result = (double) (competitorStart.asMillis() - firstStart.asMillis()) / 1000.;
} else {
result = null;
}
} else {
result = null;
}
} finally {
trackedLeg.getTrackedLeg().getTrackedRace().unlockAfterRead(markPassingsForLegStart);
}
} else {
TrackedLeg previousTrackedLeg = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLeg(course.getLegs().get(indexOfStartWaypoint-1));
TrackedLegOfCompetitor previousTrackedLegOfCompetitor = previousTrackedLeg.getTrackedLeg(trackedLeg.getCompetitor());
result = previousTrackedLegOfCompetitor.getGapToLeaderInSeconds(previousTrackedLegOfCompetitor.getFinishTime());
}
return result;
} finally {
course.unlockAfterRead();
}
}
private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException {
LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry,
baseDomainFactory);
return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails);
}
private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry,
DomainFactory baseDomainFactory) {
LiveLeaderboardUpdater result = this.liveLeaderboardUpdater;
if (result == null) {
synchronized (this) {
result = this.liveLeaderboardUpdater;
if (result == null) {
this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory);
result = this.liveLeaderboardUpdater;
}
}
}
return result;
}
private LeaderboardDTOCache getLeaderboardDTOCache() {
LeaderboardDTOCache result = this.leaderboardDTOCache;
if (result == null) {
synchronized (this) {
result = this.leaderboardDTOCache;
if (result == null) {
// The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes
// are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a
// single fix was updated is too expensive if users use the replay feature while a race is still running.
// Therefore, using waitForLatestAnalyses==false seems appropriate here.
this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this);
result = this.leaderboardDTOCache;
}
}
}
return result;
}
@Override
public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, InterruptedException, ExecutionException {
LeaderboardDTO result = null;
if (timePoint == null) {
// date==null means live mode; however, if we're after the end of all races and after all score
// corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map
// this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly
// when the tracked race associations or score corrections or tracked race contents changes:
final TimePoint nowMinusDelay = this.getNowMinusDelay();
final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification();
if (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification)) {
// if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode
// and pull the result from the regular leaderboard cache:
timePoint = timePointOfLatestModification;
} else {
// don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead:
timePoint = null;
result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, trackedRegattaRegistry, baseDomainFactory);
}
}
if (timePoint != null) {
// in replay we'd like up-to-date results; they are still cached
// which is OK because the cache is invalidated whenever any of the tracked races attached to the
// leaderboard changes.
result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
baseDomainFactory, trackedRegattaRegistry);
}
return result;
}
} }
@@ -41,4 +41,7 @@ public interface TrackedRegattaRegistry {
* @param raceID the ID as obtained from {@link RaceDefinition#getId()} * @param raceID the ID as obtained from {@link RaceDefinition#getId()}
*/ */
Regatta getRememberedRegattaForRace(Serializable raceID); Regatta getRememberedRegattaForRace(Serializable raceID);
boolean isRaceBeingTracked(RaceDefinition r);
} }
@@ -0,0 +1 @@
bin/
+7
View File
@@ -169,6 +169,13 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
version="0.0.0" version="0.0.0"
unpack="false"/> unpack="false"/>
<plugin
id="com.sap.sailing.simulator"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin <plugin
id="com.google.gwt.osgi" id="com.google.gwt.osgi"
download-size="0" download-size="0"
@@ -11,13 +11,13 @@ import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.ExpandableSortableColumn; import com.sap.sailing.gwt.ui.leaderboard.ExpandableSortableColumn;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO; import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
@@ -1,5 +1,8 @@
package com.sap.sailing.gwt.ui.test; package com.sap.sailing.gwt.ui.test;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -8,9 +11,6 @@ import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
import com.sap.sailing.gwt.ui.leaderboard.SortableColumn; import com.sap.sailing.gwt.ui.leaderboard.SortableColumn;
import com.sap.sailing.gwt.ui.leaderboard.SortedCellTable; import com.sap.sailing.gwt.ui.leaderboard.SortedCellTable;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
public class LeaderboardPanelMock extends LeaderboardPanel { public class LeaderboardPanelMock extends LeaderboardPanel {
@@ -0,0 +1,60 @@
package com.sap.sailing.gwt.ui.test;
import static org.junit.Assert.*;
import org.junit.Test;
import com.sap.sailing.gwt.ui.simulator.util.LineSegment;
import com.sap.sailing.gwt.ui.simulator.util.LineSegment.Point;
public class LineSegmentTest {
@Test
public void test1() {
LineSegment s1 = new LineSegment(0,0,10,10);
LineSegment s2 = new LineSegment(10,0,0,10);
Point intersectionPoint = s1.intersect(s2);
if (intersectionPoint != null) {
System.out.println("The lines interect at " + intersectionPoint);
}
assertEquals("Lines do intersect", 5.0, intersectionPoint.getX(),1e-6);
assertEquals("Lines do intersect", 5.0, intersectionPoint.getY(),1e-6);
}
@Test
public void test2() {
LineSegment s1 = new LineSegment(0,0,0,10);
LineSegment s2 = new LineSegment(10,0,10,10);
Point intersectionPoint = s1.intersect(s2);
assertEquals("Lines do not intersect", null, intersectionPoint);
}
@Test
public void test3() {
LineSegment s1 = new LineSegment(53.987292187570304, 10.944955638570837,53.991202136256135, 10.951666848382969);
LineSegment b1 = new LineSegment(53.977189863832564, 10.886260271572164,53.96560719314814, 10.925463437534281);
LineSegment b2 = new LineSegment(53.96560719314814, 10.925463437534281,53.991220287799436, 10.947350263095805);
LineSegment b3 = new LineSegment(53.991220287799436, 10.947350263095805,54.00280295848386, 10.90814709713369);
LineSegment b4 = new LineSegment(54.00280295848386, 10.90814709713369,53.977189863832564, 10.886260271572164);
s1.intersect(b1);
s1.intersect(b2);
s1.intersect(b3);
s1.intersect(b4);
Point intersectionPoint = s1.intersect(b1);
assertEquals("Lines s1 and b1 do not intersect", null, intersectionPoint);
intersectionPoint = s1.intersect(b2);
assertEquals("Lines s and b2 do not intersect", null, intersectionPoint);
intersectionPoint = s1.intersect(b3);
assertEquals("Lines s3 and b3 do not intersect", null, intersectionPoint);
intersectionPoint = s1.intersect(b4);
assertEquals("Lines s4 and b4 do not intersect", null, intersectionPoint);
}
}
@@ -14,10 +14,10 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.server.SailingServiceImpl; import com.sap.sailing.gwt.ui.server.SailingServiceImpl;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
public class TestColumnSwapping { public class TestColumnSwapping {
@@ -0,0 +1,27 @@
package com.sap.sailing.gwt.ui.test;
import static org.junit.Assert.*;
import org.junit.Test;
import com.sap.sailing.gwt.ui.simulator.util.*;
public class WindGridColorPaletteTest {
@Test
public void test() {
int min = 10;
int max = 100;
WindGridColorPalette cp = new WindGridColorPalette(min,max);
//assertEquals("Lightest color", "#FFFFFF", cp.getColor(min));
//assertEquals("Darkest color", "#0000FF", cp.getColor(max));
//assertEquals("In between color", "#7F7FFF", cp.getColor((max+min)/2));
assertEquals("Lightest color", "#FFFFFF", cp.getColor(min));
assertEquals("Darkest color", "#4B7FBB", cp.getColor(max));
assertEquals("In between color", "#A5BFDD", cp.getColor((max+min)/2));
}
}
@@ -1,42 +1,43 @@
Manifest-Version: 1.0 Manifest-Version: 1.0
Bundle-ManifestVersion: 2 Bundle-ManifestVersion: 2
Bundle-Name: Sailing App GWT UI Bundle-Name: Sailing App GWT UI
Bundle-SymbolicName: com.sap.sailing.gwt.ui Bundle-SymbolicName: com.sap.sailing.gwt.ui
Bundle-Version: 1.0.0.qualifier Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: com.sap.sailing.domain, Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.shared.android;bundle-version="1.0.0", com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
com.sap.sailing.server.gateway, com.sap.sailing.server.gateway,
com.sap.sailing.server, com.sap.sailing.server,
com.sap.sailing.domain.tractracadapter, com.sap.sailing.domain.tractracadapter,
com.sap.sailing.mongodb, com.sap.sailing.mongodb,
com.mongodb.driver;bundle-version="2.6.2", com.mongodb.driver;bundle-version="2.6.2",
com.sap.sailing.domain.swisstimingadapter, com.sap.sailing.domain.swisstimingadapter,
com.sap.sailing.domain.persistence, com.sap.sailing.domain.persistence,
com.sap.sailing.domain.swisstimingadapter.persistence, com.sap.sailing.domain.swisstimingadapter.persistence,
com.sap.sailing.domain.tractracadapter.persistence, com.sap.sailing.domain.tractracadapter.persistence,
com.google.gwt.osgi;bundle-version="2.4.0", com.google.gwt.osgi;bundle-version="2.4.0",
com.sap.sailing.domain.common, com.sap.sailing.domain.common,
com.sap.sailing.resultimport, com.sap.sailing.resultimport,
org.moxieapps.gwt.highcharts;bundle-version="1.5.0", org.moxieapps.gwt.highcharts;bundle-version="1.5.0",
com.sap.sailing.geocoding, com.sap.sailing.geocoding,
com.sap.sailing.server.replication, com.sap.sailing.server.replication,
com.googlecode.java-diff-utils;bundle-version="1.3.0", com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.freg.resultimport, com.sap.sailing.freg.resultimport,
com.sap.sailing.domain.swisstimingreplayadapter com.sap.sailing.domain.swisstimingreplayadapter,
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator com.sap.sailing.simulator;bundle-version="1.0.0"
Bundle-ActivationPolicy: lazy Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Export-Package: com.sap.sailing.gwt.ui.client.shared.media Bundle-ActivationPolicy: lazy
Import-Package: javax.jms;version="1.1.0", Export-Package: com.sap.sailing.gwt.ui.client.shared.media
javax.servlet;version="2.5.0", Import-Package: javax.jms;version="1.1.0",
javax.servlet.http;version="2.5.0", javax.servlet;version="2.5.0",
org.json.simple, javax.servlet.http;version="2.5.0",
org.json.simple.parser, org.json.simple,
org.osgi.framework, org.json.simple.parser,
org.osgi.util.tracker org.osgi.framework,
Web-ContextPath: /gwt org.osgi.util.tracker
Bundle-ClassPath: WEB-INF/classes/, Web-ContextPath: /gwt
WEB-INF/lib/gwt-maps.jar, Bundle-ClassPath: WEB-INF/classes/,
WEB-INF/lib/gwt-servlet.jar WEB-INF/lib/gwt-maps.jar,
TrackLifecycle: true WEB-INF/lib/gwt-servlet.jar
TrackLifecycle: true
+54 -53
View File
@@ -1,53 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp"> <launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/> <booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/> <stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/> <booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES"> <listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.ui.RegattaOverview"/> <listEntry value="com.sap.sailing.gwt.ui.RegattaOverview"/>
</listAttribute> <listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/AdminConsole.html"/> </listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> <stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/AdminConsole.html"/>
<listEntry value="/com.sap.sailing.gwt.ui"/> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
</listAttribute> <listEntry value="/com.sap.sailing.gwt.ui"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"> </listAttribute>
<listEntry value="4"/> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
</listAttribute> <listEntry value="4"/>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups"> </listAttribute>
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/> <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/> <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
</listAttribute> <listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH"> </listAttribute>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/> <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.tractrac.clientmodule/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.tractrac.clientmodule/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute> <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/> </listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/> <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/> <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.RegattaOverview"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
</launchConfiguration> <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/>
</launchConfiguration>
+385
View File
@@ -0,0 +1,385 @@
@import url(fontface.css);
//@import url(CommonControls.css);
/** Add css rules here for your application. */
/*
body #calendar:after
content: " ";
display: block;
height: 0;
clear: both;
visibility: hidden;
*/
.clear {
clear: both;
font-size: 1px;
height: 0px;
line-height: 0px;
margin: -1px 0 0;
width: 100%;
}
.cover {
left: -1000px;
overflow: hidden;
position: absolute;
top: -1000px;
}
.titleLabelWrapper {
position: relative;
margin-left: auto;
margin-right: auto;
width: 50%;
}
.titleLabel {
text-align: center;
font-family: 'UbuntuMedium', Arial, Verdana, sans-serif;
font-size: 18px;
color: #fff;
margin: 23px 0 0 0;
}
body {
background: #ebebeb;
font-family: 'UbuntuRegular', Arial, Verdana, sans-serif;
line-height: 1;
font-size: 14px;
font-weight: 400;
padding: 80px 0 160px 0;
margin: 0;
color: #7c7c7c;
}
table td,select,button {
font-family: 'UbuntuLight', Arial, Verdana, sans-serif;
line-height: 1;
font-size: 14px;
}
.contentOuterPanel {
background: #dddcdc;
margin: 0 12px 0 12px;
padding: 12px;
}
/** Example rules used by the template application (remove for your app) */
h1 {
font-size: 2em;
font-family: 'UbuntuBold', Arial, Verdana, sans-serif;
color: #777777;
margin: 40px 0px 70px;
text-align: center;
}
table {
border-collapse: separate;
}
/*.RaceBoardHeaderPanel {
margin: 0 12px 14px 12px;
}*/
.sapLogo {
float: left;
margin: 13px 0 0 12px;
}
.pioLogo {
float: right;
margin: 11px 20px 0 12px;
}
.sailingAnalyticsLabelPanel {
margin: 10px 0 0 0;
}
.sailingAnalyticsLabel {
float: left;
color: #fff;
font-size: 14px;
font-weight: bold;
margin: 15px 0 0 0;
}
.timePanelSettings {
display: none;
}
/*.leaderboardLabel {
border-bottom: 2px solid #007dc0;
padding: 0 0 6px 0;
font-family: 'UbuntuBold', Arial, Verdana, sans-serif;
}*/
.refreshAndSettings {
background: #f2f2f2;
border-bottom: 2px solid #fff;
border-top: 2px solid #fff;
height: 30px;
}
.refreshAndSettings table td {
padding: 0 10px 0 0;
}
.refreshAndSettings table table td {
padding: 0 0 0 0;
}
.race-name {
line-height: 1.0;
}
input.magnifier {
display: block;
margin: 3px auto 0 auto !important;
}
.breadcrumbPanel {
height: 30px;
margin: 0 12px 13px 12px;
}
.mainPanel {
width: 100%;
}
/*.raceBoardNavigation {
float: right;
margin: 26px 0 0 0;
font-family: 'UbuntuLight', Arial, Verdana, sans-serif;
}
.raceBoardNavigation-navigationitem {
color: #dddcdc;
text-transform: uppercase;
font-weight: 500;
margin: 0 12px 0 0;
padding: 0 0 5px 0;
}
.raceBoardNavigation-navigationitem:hover {
border-bottom: 4px solid #f0ab00;
color: #dddcdc;
text-decoration: none;
}
.raceBoardNavigation-navigationitem:active {
border-bottom: 4px solid #f0ab00;
color: #dddcdc;
text-decoration: none;
}*/
.abstractChartPanel-importantMessageOfChart {
font-family: 'UbuntuBold', Arial, Verdana, sans-serif;
font-size: 20px;
height: 70px;
margin: 0 auto;
padding: 49px 0 0 80px;
width: 300px;
color: #7c7c7c;
background: url(images/important-message.png) left center no-repeat;
}
/*.breadcrumbPanel-NextArrow {
display: none;
}*/
.replayPanel {
position: relative;
z-index: 10;
}
.innerHeadline {
font-family: UbuntuMedium, Arial, Verdana, sans-serif;
font-size: 16px;
color: #7c7c7c;
margin: 15px 0
}
.sectorHeadline {
font-family: UbuntuLight, Arial, Verdana, sans-serif;
font-size: 16px;
}
.controlPanelInnerWrapper {
margin: 0 10px;
}
.windPanel {
width: 100%;
}
.choosePattern {
width: 100%;
margin: 0 0 15px 0;
}
.choosePattern td:last-child {
text-align: right;
}
.optionsPanel {
background: url(images/simulator/panel_bg.png) 0 0 repeat-x;
border-bottom: 4px solid #7c7b7c;
padding: 0 10px;
}
.mapOptions {
background: url(images/simulator/panel_bg.png) 0 0 repeat-x;
border-bottom: 4px solid #7c7b7c;
padding: 0 10px;
}
.timePanelButton {
float: left;
margin: 0 0 0 5px;
}
.playPauseButton {
padding: 0;
width: 28px;
height: 24px;
background: url(images/timeslider-play-active-icon.png) 0 0 no-repeat;
cursor: pointer;
}
.playPauseButton:hover{
background: url(images/playbutton-hover.png) 0 0 no-repeat !important;
cursor: pointer;
}
.playPauseButtonPause {
background: url(images/timeslider-pause-icon.png) 0 0 no-repeat;
}
.playPauseButtonPause:hover {
background: url(images/playbuttonPause-hover.png) 0 0 no-repeat !important;
cursor: pointer;
}
.backToLivePlayButton {
float: left;
padding: 3px 10px 4px;
}
.sliderWrapper {
float: left;
width: 100%;
margin: 0 0 7px 0;
}
.sliderLabel {
width: 200px;
padding: 12px 0 14px 12px;
}
/* styles for the masterPanel */
#masterPanelLeft .gwt-SliderBar-shell {
background: url(images/simulator/slider_bg.png) 0 0 repeat-x;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border: 1px solid #ccc;
height: 36px;
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line {
border: 1px solid #919191;
background-color: #818181;
height: 4px;
width: 90%;
top: 23px;
overflow: hidden;
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-disabled {
border: 1px solid gray;
}
/*#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-knob {
top: 14px;
width: 11px;
height: 21px;
z-index: 1;
cursor: pointer;
}*/
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick {
top: 18px;
width: 1px;
height: 6pt;
background: #919191;
overflow: hidden;
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-mark {
top: 21pt;
width: 6px;
height: 6pt;
background: #f0ab00;
overflow: hidden;
border-left: 4px solid #fff;
border-right: 4px solid #fff;
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick-disabled {
background: gray;
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-ticklabel {
font-size: 12px;
cursor: default;
font-family: 'UbuntuBold',Arial,Verdana,sans-serif;
top: 5px;
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-markerlabel {
top: 30pt;
font-size: 12px;
color: #bc9434;
cursor: default;
font-family: 'UbuntuBold',Arial,Verdana,sans-serif;
}
#masterPanelLeft .gwt-SliderBar-shell-focused {
}
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-sliding {
cursor: pointer;
}
.boatClassLabel {
width: 235px;
margin: 0 0 15px 0;
}
.sailingPanel {
width: 100%;
}
.dockLayoutPanel div[style="position: absolute; overflow: hidden; left: 0px; right: 0px; bottom: 0px; height: 90px;"] {
box-shadow: 0 0 8px #424242;
z-index: 10;
}
.MapDisplayOptions {
margin: 0 15px 0 0;
}
.MapDisplayOptions label,.gwt-RadioButton label {
margin: 0 0 0 5px;
}
.MapDisplayOptions {
margin: 0 0 0 5px;
}
.timeSliderWrapperPanel{
-webkit-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* WebKit */
-moz-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Firefox */
box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Standard */
}
.polarDiv
{background-color: white;width:100%;height:100px;border-radius: 5px; -moz-border-radius: 5px;
-webkit-border-radius: 5px; border: 1px solid #177980;}
@@ -0,0 +1,42 @@
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>SAP Sailing Analytics Race Simulator</title>
<meta content='width = device-width, initial-scale = 1.0, user-scalable = yes' name='viewport'>
<meta content='yes' name='apple-mobile-web-app-capable'>
<meta content='black' name='apple-mobile-web-app-status-bar-style'>
<!-- <link href='iphone-splash-screen.png' rel='apple-touch-startup-image'> -->
<link href='images/sap-sailing-app-icon.png' rel='apple-touch-icon'>
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
<link href="CommonControls.css" rel="stylesheet" type="text/css">
<!--<link href='http://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700,300italic,400italic,500italic,700italic' rel='stylesheet' type='text/css'>-->
<!-- <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300,300italic' rel='stylesheet' type='text/css'>-->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts2_3.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts-more.js"></script>
<script type="text/javascript" src="highcharts/js/exporting.js"></script>
<!-- <script type="text/javascript" src="highcharts/js/highcharts.js"></script>-->
<!-- Optionally, add a highcharts theme file -->
<script type="text/javascript" src="highcharts/js/themes/grid.js"></script>
<script type="text/javascript" language="javascript" src="com.sap.sailing.gwt.ui.Simulator/com.sap.sailing.gwt.ui.Simulator.nocache.js"></script>
</head>
<body>
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
<noscript>
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
Your web browser must have JavaScript enabled
in order for this application to display correctly.
</div>
</noscript>
</body>
</html>
@@ -33,6 +33,16 @@
<url-pattern>/media</url-pattern> <url-pattern>/media</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet>
<display-name>Simulator Service</display-name>
<servlet-name>SimulatorService</servlet-name>
<servlet-class>com.sap.sailing.gwt.ui.server.SimulatorServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SimulatorService</servlet-name>
<url-pattern>com.sap.sailing.gwt.ui.Simulator/simulator</url-pattern>
</servlet-mapping>
<servlet> <servlet>
<display-name>User Management Service</display-name> <display-name>User Management Service</display-name>
<servlet-name>UserManagementService</servlet-name> <servlet-name>UserManagementService</servlet-name>
@@ -52,6 +62,7 @@
<servlet-mapping> <servlet-mapping>
<servlet-name>remoteLoggingServlet</servlet-name> <servlet-name>remoteLoggingServlet</servlet-name>
<url-pattern>/com.sap.sailing.gwt.ui.AdminConsole/remote_logging</url-pattern> <url-pattern>/com.sap.sailing.gwt.ui.AdminConsole/remote_logging</url-pattern>
<url-pattern>/com.sap.sailing.gwt.ui.Simulator/remote_logging</url-pattern>
</servlet-mapping> </servlet-mapping>
<security-constraint> <security-constraint>
+4 -1
View File
@@ -28,8 +28,11 @@ bin.includes = META-INF/,\
com.sap.sailing.gwt.ui.RaceBoard/,\ com.sap.sailing.gwt.ui.RaceBoard/,\
highcharts/,\ highcharts/,\
Spectator.css,\ Spectator.css,\
fontface.css,\ fontface.css,\
webfonts/,\ webfonts/,\
com.sap.sailing.gwt.ui.Simulator/,\
Simulator.html,\
Simulator.css,\
RegattaOverview.css,\ RegattaOverview.css,\
RegattaOverview.html,\ RegattaOverview.html,\
com.sap.sailing.gwt.ui.RegattaOverview/,\ com.sap.sailing.gwt.ui.RegattaOverview/,\
@@ -0,0 +1,737 @@
/**
* @license @product.name@ JS v@product.version@ (@product.date@)
* Exporting module
*
* (c) 2010-2011 Torstein Hønsi
*
* License: www.highcharts.com/license
*/
// JSLint options:
/*global Highcharts, document, window, Math, setTimeout */
(function () { // encapsulate
// create shortcuts
var HC = Highcharts,
Chart = HC.Chart,
addEvent = HC.addEvent,
removeEvent = HC.removeEvent,
createElement = HC.createElement,
discardElement = HC.discardElement,
css = HC.css,
merge = HC.merge,
each = HC.each,
extend = HC.extend,
math = Math,
mathMax = math.max,
doc = document,
win = window,
hasTouch = doc.documentElement.ontouchstart !== undefined,
M = 'M',
L = 'L',
DIV = 'div',
HIDDEN = 'hidden',
NONE = 'none',
PREFIX = 'highcharts-',
ABSOLUTE = 'absolute',
PX = 'px',
UNDEFINED,
defaultOptions = HC.getOptions();
// Add language
extend(defaultOptions.lang, {
downloadPNG: 'Download PNG image',
downloadJPEG: 'Download JPEG image',
downloadPDF: 'Download PDF document',
downloadSVG: 'Download SVG vector image',
exportButtonTitle: 'Export to raster or vector image',
printButtonTitle: 'Print the chart'
});
// Buttons and menus are collected in a separate config option set called 'navigation'.
// This can be extended later to add control buttons like zoom and pan right click menus.
defaultOptions.navigation = {
menuStyle: {
border: '1px solid #A0A0A0',
background: '#FFFFFF'
},
menuItemStyle: {
padding: '0 5px',
background: NONE,
color: '#303030',
fontSize: hasTouch ? '14px' : '11px'
},
menuItemHoverStyle: {
background: '#4572A5',
color: '#FFFFFF'
},
buttonOptions: {
align: 'right',
backgroundColor: {
linearGradient: [0, 0, 0, 20],
stops: [
[0.4, '#F7F7F7'],
[0.6, '#E3E3E3']
]
},
borderColor: '#B0B0B0',
borderRadius: 3,
borderWidth: 1,
//enabled: true,
height: 20,
hoverBorderColor: '#909090',
hoverSymbolFill: '#81A7CF',
hoverSymbolStroke: '#4572A5',
symbolFill: '#E0E0E0',
//symbolSize: 12,
symbolStroke: '#A0A0A0',
//symbolStrokeWidth: 1,
symbolX: 11.5,
symbolY: 10.5,
verticalAlign: 'top',
width: 24,
y: 10
}
};
// Add the export related options
defaultOptions.exporting = {
//enabled: true,
//filename: 'chart',
type: 'image/png',
url: 'http://export.highcharts.com/',
width: 800,
buttons: {
exportButton: {
//enabled: true,
symbol: 'exportIcon',
x: -10,
symbolFill: '#A8BF77',
hoverSymbolFill: '#768F3E',
_id: 'exportButton',
_titleKey: 'exportButtonTitle',
menuItems: [{
textKey: 'downloadPNG',
onclick: function () {
this.exportChart();
}
}, {
textKey: 'downloadJPEG',
onclick: function () {
this.exportChart({
type: 'image/jpeg'
});
}
}, {
textKey: 'downloadPDF',
onclick: function () {
this.exportChart({
type: 'application/pdf'
});
}
}, {
textKey: 'downloadSVG',
onclick: function () {
this.exportChart({
type: 'image/svg+xml'
});
}
}
// Enable this block to add "View SVG" to the dropdown menu
/*
,{
text: 'View SVG',
onclick: function () {
var svg = this.getSVG()
.replace(/</g, '\n&lt;')
.replace(/>/g, '&gt;');
doc.body.innerHTML = '<pre>' + svg + '</pre>';
}
} // */
]
},
printButton: {
//enabled: true,
symbol: 'printIcon',
x: -36,
symbolFill: '#B5C9DF',
hoverSymbolFill: '#779ABF',
_id: 'printButton',
_titleKey: 'printButtonTitle',
onclick: function () {
this.print();
}
}
}
};
extend(Chart.prototype, {
/**
* Return an SVG representation of the chart
*
* @param additionalOptions {Object} Additional chart options for the generated SVG representation
*/
getSVG: function (additionalOptions) {
var chart = this,
chartCopy,
sandbox,
svg,
seriesOptions,
options = merge(chart.options, additionalOptions); // copy the options and add extra options
// IE compatibility hack for generating SVG content that it doesn't really understand
if (!doc.createElementNS) {
/*jslint unparam: true*//* allow unused parameter ns in function below */
doc.createElementNS = function (ns, tagName) {
return doc.createElement(tagName);
};
/*jslint unparam: false*/
}
// create a sandbox where a new chart will be generated
sandbox = createElement(DIV, null, {
position: ABSOLUTE,
top: '-9999em',
width: chart.chartWidth + PX,
height: chart.chartHeight + PX
}, doc.body);
// override some options
extend(options.chart, {
renderTo: sandbox,
forExport: true
});
options.exporting.enabled = false; // hide buttons in print
options.chart.plotBackgroundImage = null; // the converter doesn't handle images
// prepare for replicating the chart
options.series = [];
each(chart.series, function (serie) {
seriesOptions = merge(serie.options, {
animation: false, // turn off animation
showCheckbox: false,
visible: serie.visible
});
if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
// remove image markers
if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
seriesOptions.marker.symbol = 'circle';
}
options.series.push(seriesOptions);
}
});
// generate the chart copy
chartCopy = new Highcharts.Chart(options);
// reflect axis extremes in the export
each(['xAxis', 'yAxis'], function (axisType) {
each(chart[axisType], function (axis, i) {
var axisCopy = chartCopy[axisType][i],
extremes = axis.getExtremes(),
userMin = extremes.userMin,
userMax = extremes.userMax;
if (userMin !== UNDEFINED || userMax !== UNDEFINED) {
axisCopy.setExtremes(userMin, userMax, true, false);
}
});
});
// get the SVG from the container's innerHTML
svg = chartCopy.container.innerHTML;
// free up memory
options = null;
chartCopy.destroy();
discardElement(sandbox);
// sanitize
svg = svg
.replace(/zIndex="[^"]+"/g, '')
.replace(/isShadow="[^"]+"/g, '')
.replace(/symbolName="[^"]+"/g, '')
.replace(/jQuery[0-9]+="[^"]+"/g, '')
.replace(/isTracker="[^"]+"/g, '')
.replace(/url\([^#]+#/g, 'url(#')
.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
.replace(/ href=/g, ' xlink:href=')
.replace(/\n/, ' ')
.replace(/<\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)
/* This fails in IE < 8
.replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
return s2 +'.'+ s3[0];
})*/
// Replace HTML entities, issue #347
.replace(/&nbsp;/g, '\u00A0') // no-break space
.replace(/&shy;/g, '\u00AD') // soft hyphen
// IE specific
.replace(/<IMG /g, '<image ')
.replace(/height=([^" ]+)/g, 'height="$1"')
.replace(/width=([^" ]+)/g, 'width="$1"')
.replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
.replace(/id=([^" >]+)/g, 'id="$1"')
.replace(/class=([^" ]+)/g, 'class="$1"')
.replace(/ transform /g, ' ')
.replace(/:(path|rect)/g, '$1')
.replace(/style="([^"]+)"/g, function (s) {
return s.toLowerCase();
});
// IE9 beta bugs with innerHTML. Test again with final IE9.
svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
.replace(/&quot;/g, "'");
if (svg.match(/ xmlns="/g).length === 2) {
svg = svg.replace(/xmlns="[^"]+"/, '');
}
return svg;
},
/**
* Submit the SVG representation of the chart to the server
* @param {Object} options Exporting options. Possible members are url, type and width.
* @param {Object} chartOptions Additional chart options for the SVG representation of the chart
*/
exportChart: function (options, chartOptions) {
var form,
chart = this,
svg = chart.getSVG(merge(chart.options.exporting.chartOptions, chartOptions)); // docs
// merge the options
options = merge(chart.options.exporting, options);
// create the form
form = createElement('form', {
method: 'post',
action: options.url,
enctype: 'multipart/form-data'
}, {
display: NONE
}, doc.body);
// add the values
each(['filename', 'type', 'width', 'svg'], function (name) {
createElement('input', {
type: HIDDEN,
name: name,
value: {
filename: options.filename || 'chart',
type: options.type,
width: options.width,
svg: svg
}[name]
}, null, form);
});
// submit
form.submit();
// clean up
discardElement(form);
},
/**
* Print the chart
*/
print: function () {
var chart = this,
container = chart.container,
origDisplay = [],
origParent = container.parentNode,
body = doc.body,
childNodes = body.childNodes;
if (chart.isPrinting) { // block the button while in printing mode
return;
}
chart.isPrinting = true;
// hide all body content
each(childNodes, function (node, i) {
if (node.nodeType === 1) {
origDisplay[i] = node.style.display;
node.style.display = NONE;
}
});
// pull out the chart
body.appendChild(container);
// print
win.print();
// allow the browser to prepare before reverting
setTimeout(function () {
// put the chart back in
origParent.appendChild(container);
// restore all body content
each(childNodes, function (node, i) {
if (node.nodeType === 1) {
node.style.display = origDisplay[i];
}
});
chart.isPrinting = false;
}, 1000);
},
/**
* Display a popup menu for choosing the export type
*
* @param {String} name An identifier for the menu
* @param {Array} items A collection with text and onclicks for the items
* @param {Number} x The x position of the opener button
* @param {Number} y The y position of the opener button
* @param {Number} width The width of the opener button
* @param {Number} height The height of the opener button
*/
contextMenu: function (name, items, x, y, width, height) {
var chart = this,
navOptions = chart.options.navigation,
menuItemStyle = navOptions.menuItemStyle,
chartWidth = chart.chartWidth,
chartHeight = chart.chartHeight,
cacheName = 'cache-' + name,
menu = chart[cacheName],
menuPadding = mathMax(width, height), // for mouse leave detection
boxShadow = '3px 3px 10px #888',
innerMenu,
hide,
menuStyle;
// create the menu only the first time
if (!menu) {
// create a HTML element above the SVG
chart[cacheName] = menu = createElement(DIV, {
className: PREFIX + name
}, {
position: ABSOLUTE,
zIndex: 1000,
padding: menuPadding + PX
}, chart.container);
innerMenu = createElement(DIV, null,
extend({
MozBoxShadow: boxShadow,
WebkitBoxShadow: boxShadow,
boxShadow: boxShadow
}, navOptions.menuStyle), menu);
// hide on mouse out
hide = function () {
css(menu, { display: NONE });
};
addEvent(menu, 'mouseleave', hide);
// create the items
each(items, function (item) {
if (item) {
var div = createElement(DIV, {
onmouseover: function () {
css(this, navOptions.menuItemHoverStyle);
},
onmouseout: function () {
css(this, menuItemStyle);
},
innerHTML: item.text || chart.options.lang[item.textKey]
}, extend({
cursor: 'pointer'
}, menuItemStyle), innerMenu);
div[hasTouch ? 'ontouchstart' : 'onclick'] = function () {
hide();
item.onclick.apply(chart, arguments);
};
// Keep references to menu divs to be able to destroy them
chart.exportDivElements.push(div);
}
});
// Keep references to menu and innerMenu div to be able to destroy them
chart.exportDivElements.push(innerMenu, menu);
chart.exportMenuWidth = menu.offsetWidth;
chart.exportMenuHeight = menu.offsetHeight;
}
menuStyle = { display: 'block' };
// if outside right, right align it
if (x + chart.exportMenuWidth > chartWidth) {
menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
} else {
menuStyle.left = (x - menuPadding) + PX;
}
// if outside bottom, bottom align it
if (y + height + chart.exportMenuHeight > chartHeight) {
menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
} else {
menuStyle.top = (y + height - menuPadding) + PX;
}
css(menu, menuStyle);
},
/**
* Add the export button to the chart
*/
addButton: function (options) {
var chart = this,
renderer = chart.renderer,
btnOptions = merge(chart.options.navigation.buttonOptions, options),
onclick = btnOptions.onclick,
menuItems = btnOptions.menuItems,
buttonWidth = btnOptions.width,
buttonHeight = btnOptions.height,
box,
symbol,
button,
borderWidth = btnOptions.borderWidth,
boxAttr = {
stroke: btnOptions.borderColor
},
symbolAttr = {
stroke: btnOptions.symbolStroke,
fill: btnOptions.symbolFill
},
symbolSize = btnOptions.symbolSize || 12;
// Keeps references to the button elements
if (!chart.exportDivElements) {
chart.exportDivElements = [];
chart.exportSVGElements = [];
}
if (btnOptions.enabled === false) {
return;
}
// element to capture the click
function revert() {
symbol.attr(symbolAttr);
box.attr(boxAttr);
}
// the box border
box = renderer.rect(
0,
0,
buttonWidth,
buttonHeight,
btnOptions.borderRadius,
borderWidth
)
//.translate(buttonLeft, buttonTop) // to allow gradients
.align(btnOptions, true)
.attr(extend({
fill: btnOptions.backgroundColor,
'stroke-width': borderWidth,
zIndex: 19
}, boxAttr)).add();
// the invisible element to track the clicks
button = renderer.rect(
0,
0,
buttonWidth,
buttonHeight,
0
)
.align(btnOptions)
.attr({
id: btnOptions._id,
fill: 'rgba(255, 255, 255, 0.001)',
title: chart.options.lang[btnOptions._titleKey],
zIndex: 21
}).css({
cursor: 'pointer'
})
.on('mouseover', function () {
symbol.attr({
stroke: btnOptions.hoverSymbolStroke,
fill: btnOptions.hoverSymbolFill
});
box.attr({
stroke: btnOptions.hoverBorderColor
});
})
.on('mouseout', revert)
.on('click', revert)
.add();
// add the click event
if (menuItems) {
onclick = function () {
revert();
var bBox = button.getBBox();
chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
};
}
/*addEvent(button.element, 'click', function() {
onclick.apply(chart, arguments);
});*/
button.on('click', function () {
onclick.apply(chart, arguments);
});
// the icon
symbol = renderer.symbol(
btnOptions.symbol,
btnOptions.symbolX - (symbolSize / 2),
btnOptions.symbolY - (symbolSize / 2),
symbolSize,
symbolSize
)
.align(btnOptions, true)
.attr(extend(symbolAttr, {
'stroke-width': btnOptions.symbolStrokeWidth || 1,
zIndex: 20
})).add();
// Keep references to the renderer element so to be able to destroy them later.
chart.exportSVGElements.push(box, button, symbol);
},
/**
* Destroy the buttons.
*/
destroyExport: function () {
var i,
chart = this,
elem;
// Destroy the extra buttons added
for (i = 0; i < chart.exportSVGElements.length; i++) {
elem = chart.exportSVGElements[i];
// Destroy and null the svg/vml elements
elem.onclick = elem.ontouchstart = null;
chart.exportSVGElements[i] = elem.destroy();
}
// Destroy the divs for the menu
for (i = 0; i < chart.exportDivElements.length; i++) {
elem = chart.exportDivElements[i];
// Remove the event handler
removeEvent(elem, 'mouseleave');
// Remove inline events
chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
// Destroy the div by moving to garbage bin
discardElement(elem);
}
}
});
/**
* Crisp for 1px stroke width, which is default. In the future, consider a smarter,
* global function.
*/
function crisp(arr) {
var i = arr.length;
while (i--) {
if (typeof arr[i] === 'number') {
arr[i] = Math.round(arr[i]) - 0.5;
}
}
return arr;
}
// Create the export icon
HC.Renderer.prototype.symbols.exportIcon = function (x, y, width, height) {
return crisp([
M, // the disk
x, y + width,
L,
x + width, y + height,
x + width, y + height * 0.8,
x, y + height * 0.8,
'Z',
M, // the arrow
x + width * 0.5, y + height * 0.8,
L,
x + width * 0.8, y + height * 0.4,
x + width * 0.4, y + height * 0.4,
x + width * 0.4, y,
x + width * 0.6, y,
x + width * 0.6, y + height * 0.4,
x + width * 0.2, y + height * 0.4,
'Z'
]);
};
// Create the print icon
HC.Renderer.prototype.symbols.printIcon = function (x, y, width, height) {
return crisp([
M, // the printer
x, y + height * 0.7,
L,
x + width, y + height * 0.7,
x + width, y + height * 0.4,
x, y + height * 0.4,
'Z',
M, // the upper sheet
x + width * 0.2, y + height * 0.4,
L,
x + width * 0.2, y,
x + width * 0.8, y,
x + width * 0.8, y + height * 0.4,
'Z',
M, // the lower sheet
x + width * 0.2, y + height * 0.7,
L,
x, y + height,
x + width, y + height,
x + width * 0.8, y + height * 0.7,
'Z'
]);
};
// Add the buttons on chart load
Chart.prototype.callbacks.push(function (chart) {
var n,
exportingOptions = chart.options.exporting,
buttons = exportingOptions.buttons;
if (exportingOptions.enabled !== false) {
for (n in buttons) {
chart.addButton(buttons[n]);
}
// Destroy the export elements at chart destroy
addEvent(chart, 'destroy', chart.destroyExport);
}
});
}());
window.console && console.log('--- Running modules/exporting.src.js from GitHub ---');
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

+1
View File
@@ -53,6 +53,7 @@
<module>com.sap.sailing.gwt.ui.UserManagement</module> <module>com.sap.sailing.gwt.ui.UserManagement</module>
<module>com.sap.sailing.gwt.ui.VideoPopup</module> <module>com.sap.sailing.gwt.ui.VideoPopup</module>
<module>com.sap.sailing.gwt.ui.YoutubePopup</module> <module>com.sap.sailing.gwt.ui.YoutubePopup</module>
<module>com.sap.sailing.gwt.ui.Simulator</module>
<module>com.sap.sailing.gwt.ui.RegattaOverview</module> <module>com.sap.sailing.gwt.ui.RegattaOverview</module>
<module>com.sap.sailing.gwt.ui.PolarSheets</module> <module>com.sap.sailing.gwt.ui.PolarSheets</module>
</modules> </modules>
@@ -6,8 +6,8 @@ import java.util.List;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
public class GetCompetitorsRaceDataAction extends DefaultAsyncAction<CompetitorsRaceDataDTO> { public class GetCompetitorsRaceDataAction extends DefaultAsyncAction<CompetitorsRaceDataDTO> {
@@ -4,8 +4,8 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
public class GetLeaderboardByNameAction extends DefaultAsyncAction<LeaderboardDTO> { public class GetLeaderboardByNameAction extends DefaultAsyncAction<LeaderboardDTO> {
private final SailingServiceAsync sailingService; private final SailingServiceAsync sailingService;
@@ -6,9 +6,9 @@ import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
public class GetRaceMapDataAction extends DefaultAsyncAction<RaceMapDataDTO> { public class GetRaceMapDataAction extends DefaultAsyncAction<RaceMapDataDTO> {
@@ -5,13 +5,13 @@ import java.util.List;
import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
public abstract class AbstractRaceManagementPanel extends AbstractEventManagementPanel implements RaceSelectionChangeListener { public abstract class AbstractRaceManagementPanel extends AbstractEventManagementPanel implements RaceSelectionChangeListener {
@@ -41,6 +41,7 @@ import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
@@ -50,7 +51,6 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.Component;
import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
public abstract class AbstractTrackedRacesListComposite extends SimplePanel implements Component<TrackedRacesSettings>, public abstract class AbstractTrackedRacesListComposite extends SimplePanel implements Component<TrackedRacesSettings>,
@@ -8,11 +8,11 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox; import com.google.gwt.user.datepicker.client.DateBox;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.PositionDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindDTO;
public class AddWindFixDialog extends DataEntryDialog<WindDTO> { public class AddWindFixDialog extends DataEntryDialog<WindDTO> {
@@ -47,6 +47,9 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled; import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled;
import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor; import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor;
@@ -65,10 +68,7 @@ import com.sap.sailing.gwt.ui.client.URLEncoder;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration;
import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
@@ -35,6 +35,7 @@ import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel; import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell;
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardGroupDialog.LeaderboardGroupDescriptor; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardGroupDialog.LeaderboardGroupDescriptor;
import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel; import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel;
@@ -47,7 +48,6 @@ import com.sap.sailing.gwt.ui.client.URLEncoder;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
@@ -8,9 +8,9 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
/** /**
* Fills a {@link RaceColumnDTO} object by setting its {@link RaceColumnDTO#setMedalRace(boolean) medal race} property, * Fills a {@link RaceColumnDTO} object by setting its {@link RaceColumnDTO#setMedalRace(boolean) medal race} property,
@@ -16,10 +16,10 @@ import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO;
@@ -16,9 +16,9 @@ import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
public class RaceColumnsInLeaderboardDialog extends DataEntryDialog<List<RaceColumnDTO>> { public class RaceColumnsInLeaderboardDialog extends DataEntryDialog<List<RaceColumnDTO>> {
private final ListBox addRacesListBox; private final ListBox addRacesListBox;
@@ -14,6 +14,7 @@ import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.FontWeight; import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.CellTable;
@@ -416,7 +417,8 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
public SafeHtml getValue(MarkDTO mark) { public SafeHtml getValue(MarkDTO mark) {
SafeHtmlBuilder builder = new SafeHtmlBuilder(); SafeHtmlBuilder builder = new SafeHtmlBuilder();
if(mark.position != null) { if(mark.position != null) {
builder.appendEscaped(mark.position.toFormattedString()); NumberFormat fmt = NumberFormat.getFormat("#.###");
builder.appendEscaped(fmt.format(mark.position.latDeg)+", "+fmt.format(mark.position.lngDeg));
} }
return builder.toSafeHtml(); return builder.toSafeHtml();
} }
@@ -17,12 +17,12 @@ import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
import com.sap.sailing.gwt.ui.client.RegattaDisplayer; import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
public class RacesListBoxPanel extends FormPanel implements RegattaDisplayer, RaceSelectionChangeListener { public class RacesListBoxPanel extends FormPanel implements RegattaDisplayer, RaceSelectionChangeListener {
@@ -21,6 +21,9 @@ import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher;
@@ -28,9 +31,6 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO;
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
@@ -29,7 +30,6 @@ import com.sap.sailing.gwt.ui.client.RegattaSelectionProvider;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO;
@@ -19,10 +19,10 @@ import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.shared.BoatClassDTO;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
@@ -19,9 +19,9 @@ import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.FleetDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO;
public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> { public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
@@ -12,6 +12,7 @@ import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.HorizontalPanel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.ErrorReporter;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
import com.sap.sailing.gwt.ui.client.RegattaDisplayer; import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
@@ -19,7 +20,6 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
/** /**
@@ -34,6 +34,7 @@ import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor;
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
@@ -46,7 +47,6 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
@@ -62,7 +62,7 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W
@Override @Override
public final void onModuleLoad() { public final void onModuleLoad() {
if(DebugInfo.isDebugIdEnabled()) { if (DebugInfo.isDebugIdEnabled()) {
PendingAjaxCallBundle bundle = GWT.create(PendingAjaxCallBundle.class); PendingAjaxCallBundle bundle = GWT.create(PendingAjaxCallBundle.class);
TextResource script = bundle.ajaxSemaphoreJS(); TextResource script = bundle.ajaxSemaphoreJS();
JavaScriptInjector.inject(script.getText()); JavaScriptInjector.inject(script.getText());
@@ -70,10 +70,8 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W
DebugInfo.setDebugIdAttribute(DEBUG_ID_ATTRIBUTE, false); DebugInfo.setDebugIdAttribute(DEBUG_ID_ATTRIBUTE, false);
DebugInfo.setDebugIdPrefix(DEBUG_ID_PREFIX); DebugInfo.setDebugIdPrefix(DEBUG_ID_PREFIX);
} }
doOnModuleLoad(); doOnModuleLoad();
if (DebugInfo.isDebugIdEnabled()) {
if(DebugInfo.isDebugIdEnabled()) {
PendingAjaxCallMarker.decrementPendingAjaxCalls(MarkedAsyncCallback.CATEGORY_GLOBAL); PendingAjaxCallMarker.decrementPendingAjaxCalls(MarkedAsyncCallback.CATEGORY_GLOBAL);
} }
} }
@@ -1,6 +1,6 @@
package com.sap.sailing.gwt.ui.client; package com.sap.sailing.gwt.ui.client;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO;
/** /**
@@ -10,7 +10,7 @@ import java.util.Set;
import com.sap.sailing.domain.common.filter.Filter; import com.sap.sailing.domain.common.filter.Filter;
import com.sap.sailing.domain.common.filter.FilterSet; import com.sap.sailing.domain.common.filter.FilterSet;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class CompetitorSelectionModel implements CompetitorSelectionProvider { public class CompetitorSelectionModel implements CompetitorSelectionProvider {
private final Set<CompetitorDTO> allCompetitors; private final Set<CompetitorDTO> allCompetitors;
@@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.client;
import com.sap.sailing.domain.common.filter.Filter; import com.sap.sailing.domain.common.filter.Filter;
import com.sap.sailing.domain.common.filter.FilterSet; import com.sap.sailing.domain.common.filter.FilterSet;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO;
public interface CompetitorSelectionProvider { public interface CompetitorSelectionProvider {
@@ -13,9 +13,9 @@ import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.shared.FleetDTO; import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
public class RaceTimesInfoProvider { public class RaceTimesInfoProvider {
@@ -22,23 +22,23 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO;
@@ -22,24 +22,24 @@ import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardEntryDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO; import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO;
@@ -0,0 +1,54 @@
package com.sap.sailing.gwt.ui.client;
import java.util.List;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.gwt.ui.shared.BoatClassDTOsAndNotificationMessage;
import com.sap.sailing.gwt.ui.shared.ConfigurationException;
import com.sap.sailing.gwt.ui.shared.PolarDiagramDTOAndNotificationMessage;
import com.sap.sailing.gwt.ui.shared.Request1TurnerDTO;
import com.sap.sailing.gwt.ui.shared.RequestTotalTimeDTO;
import com.sap.sailing.gwt.ui.shared.Response1TurnerDTO;
import com.sap.sailing.gwt.ui.shared.ResponseTotalTimeDTO;
import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO;
import com.sap.sailing.gwt.ui.shared.SimulatorUISelectionDTO;
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO;
import com.sap.sailing.gwt.ui.shared.WindLatticeDTO;
import com.sap.sailing.gwt.ui.shared.WindLatticeGenParamsDTO;
import com.sap.sailing.gwt.ui.shared.WindPatternDTO;
import com.sap.sailing.gwt.ui.shared.windpattern.WindPatternDisplay;
import com.sap.sailing.gwt.ui.shared.windpattern.WindPatternNotFoundException;
@RemoteServiceRelativePath("simulator")
public interface SimulatorService extends RemoteService {
PositionDTO[] getRaceLocations();
WindLatticeDTO getWindLatice(WindLatticeGenParamsDTO params);
WindFieldDTO getWindField(WindFieldGenParamsDTO params, WindPatternDisplay pattern) throws WindPatternNotFoundException;
List<WindPatternDTO> getWindPatterns();
WindPatternDisplay getWindPatternDisplay(WindPatternDTO pattern);
SimulatorResultsDTO getSimulatorResults(char mode, WindFieldGenParamsDTO params, WindPatternDisplay pattern, boolean withWindField,
SimulatorUISelectionDTO selection) throws WindPatternNotFoundException, ConfigurationException;
BoatClassDTOsAndNotificationMessage getBoatClasses() throws ConfigurationException;
PolarDiagramDTOAndNotificationMessage getPolarDiagram(Double bearingStep, int boatClassIndex) throws ConfigurationException;
ResponseTotalTimeDTO getTotalTime(RequestTotalTimeDTO requestData) throws ConfigurationException;
Response1TurnerDTO get1Turner(Request1TurnerDTO requestData) throws ConfigurationException;
List<String> getLegsNames(int selectedRaceIndex);
List<String> getRacesNames();
List<String> getCompetitorsNames(int selectedRaceIndex);
}
@@ -0,0 +1,50 @@
package com.sap.sailing.gwt.ui.client;
import java.util.List;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.gwt.ui.shared.BoatClassDTOsAndNotificationMessage;
import com.sap.sailing.gwt.ui.shared.PolarDiagramDTOAndNotificationMessage;
import com.sap.sailing.gwt.ui.shared.Request1TurnerDTO;
import com.sap.sailing.gwt.ui.shared.RequestTotalTimeDTO;
import com.sap.sailing.gwt.ui.shared.Response1TurnerDTO;
import com.sap.sailing.gwt.ui.shared.ResponseTotalTimeDTO;
import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO;
import com.sap.sailing.gwt.ui.shared.SimulatorUISelectionDTO;
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO;
import com.sap.sailing.gwt.ui.shared.WindLatticeDTO;
import com.sap.sailing.gwt.ui.shared.WindLatticeGenParamsDTO;
import com.sap.sailing.gwt.ui.shared.WindPatternDTO;
import com.sap.sailing.gwt.ui.shared.windpattern.WindPatternDisplay;
public interface SimulatorServiceAsync {
void getRaceLocations(AsyncCallback<PositionDTO[]> callback);
void getWindLatice(WindLatticeGenParamsDTO params, AsyncCallback<WindLatticeDTO> callback);
void getWindField(WindFieldGenParamsDTO params, WindPatternDisplay display, AsyncCallback<WindFieldDTO> callback);
void getWindPatterns(AsyncCallback<List<WindPatternDTO>> callback);
void getWindPatternDisplay(WindPatternDTO pattern, AsyncCallback<WindPatternDisplay> callback);
void getBoatClasses(AsyncCallback<BoatClassDTOsAndNotificationMessage> callback);
void getPolarDiagram(Double bearingStep, int boatClassIndex, AsyncCallback<PolarDiagramDTOAndNotificationMessage> callback);
void getTotalTime(RequestTotalTimeDTO requestData, AsyncCallback<ResponseTotalTimeDTO> asyncCallback);
void get1Turner(final Request1TurnerDTO requestData, AsyncCallback<Response1TurnerDTO> asyncCallback);
void getLegsNames(int selectedRaceIndex, AsyncCallback<List<String>> asyncCallback);
void getRacesNames(AsyncCallback<List<String>> asyncCallback);
void getSimulatorResults(char mode, WindFieldGenParamsDTO params, WindPatternDisplay pattern, boolean withWindField, SimulatorUISelectionDTO selection,
AsyncCallback<SimulatorResultsDTO> callback);
void getCompetitorsNames(int selectedRaceIndex, AsyncCallback<List<String>> asyncCallback);
}
@@ -13,7 +13,7 @@ import com.google.gwt.user.client.ui.VerticalPanel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.gwt.ui.shared.PositionDTO; import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
@@ -588,4 +588,51 @@ public interface StringMessages extends Messages {
String filterNothing(); String filterNothing();
String topNCompetitorsByRaceRank(int n); String topNCompetitorsByRaceRank(int n);
String topNCompetitorsByTotalRank(int n); String topNCompetitorsByTotalRank(int n);
/*Start of simulator related messages*/
String simulator();
String optionsBar();
String update();
String setup();
String strength();
String oscillation();
String oscillating();
String amplitude();
String frequency();
String noise();
String sailing();
String optimality();
String maximumTurns();
String raceDirection();
String upWind();
String downWind();
String strategies();
String omniscient();
String opportunistic();
String mixed();
String maps();
String startEnd();
String racecourse();
String summary();
String replay();
String display();
String simulatorPolarHeader();
String show();
String hide();
String leftWind();
String rightWind();
String strategyOption1();
String strategyOption2();
String strategyOption3();
String legLabel();
String raceLabel();
String competitorLabel();
String freemode();
String whatIfCourse();
/*End of simulator related messages*/
} }
@@ -583,7 +583,7 @@ nationalityMustBeISOorIOCcode=The nationality must be a two letter ISO code or a
raceRank=Race rank raceRank=Race rank
waypoints=Waypoints waypoints=Waypoints
disableRaceFilter=Disable filter disableRaceFilter=Disable filter
enableRaceFilter=Enable filter enableRaceFilter=Enable filter
raceStatusColumn=Race Status raceStatusColumn=Race Status
loading=Loading (on a slow internet connection this can take some minutes)... loading=Loading (on a slow internet connection this can take some minutes)...
showHiddenRaces=Show HIDDEN races showHiddenRaces=Show HIDDEN races
@@ -591,3 +591,42 @@ selectedCompetitors=Selected competitors
filterNothing=Filter nothing filterNothing=Filter nothing
topNCompetitorsByRaceRank=Race Top {0} topNCompetitorsByRaceRank=Race Top {0}
topNCompetitorsByTotalRank=Total Top {0} topNCompetitorsByTotalRank=Total Top {0}
simulator=Simulator
optionsBar=Optionsbar
update=Update
setup=Setup
strength=Strength (in kn)
oscillation=Oscillation
oscillating=Oscillating
amplitude=Amplitude(in Grad)
frequency=Frequency(in 1/h)
noise=Noise
sailing=Sailing
optimality=Optimality
maximumTurns=Maximum Turns
raceDirection=Race Direction
upWind=Upwind
downWind=Downwind
strategies=Strategies
omniscient=Omniscient
opportunistic=Opportunistic
mixed=Mixed
maps=Maps
startEnd=Start-End
racecourse=Course
summary=Summary
replay=Replay
display=Display
simulatorPolarHeader = Polar diagram
show = Show
hide = hide
leftWind=Left wind
rightWind=Right wind
strategyOption1=strategyOption1
strategyOption2=strategyOption2
strategyOption3=strategyOption3
legLabel=Leg
raceLabel=Race
competitorLabel=Competitor
freemode=Keep Bearings
whatIfCourse=What-If Course
@@ -581,7 +581,7 @@ nationalityMustBeISOorIOCcode=Die Nationaltät muß als ISO-Code (2 Zeichen) ode
raceRank=Wettfahrtrang raceRank=Wettfahrtrang
waypoints=Wegpunkte waypoints=Wegpunkte
disableRaceFilter=Deaktiviere Filter disableRaceFilter=Deaktiviere Filter
enableRaceFilter=Aktiviere Filter enableRaceFilter=Aktiviere Filter
raceStatusColumn=Renn-Status raceStatusColumn=Renn-Status
loading=Lade... loading=Lade...
showHiddenRaces=Rennen im Status HIDDEN anzeigen showHiddenRaces=Rennen im Status HIDDEN anzeigen
@@ -589,3 +589,43 @@ selectedCompetitors=Ausgewählte Teilnehmer
filterNothing=Nichts filtern filterNothing=Nichts filtern
topNCompetitorsByRaceRank=Wettfahrt Top {0} topNCompetitorsByRaceRank=Wettfahrt Top {0}
topNCompetitorsByTotalRank=Gesamt Top {0} topNCompetitorsByTotalRank=Gesamt Top {0}
showHiddenRaces=Rennen im Status HIDDEN anzeigen
simulator=Simulator
optionsBar=Optionsbar
update=Update
setup=Setup
strength=Strength(in kn)
oscillation=Oscillation
oscillating=Oscillating
amplitude=Amplitude(in Grad)
frequency=Frequency(in 1/h)
noise=Noise
sailing=Sailing
optimality=Optimality
maximumTurns=Maximum Turns
raceDirection=Race Direction
upWind=Upwind
downWind=Downwind
strategies=Strategies
omniscient=Omniscient
opportunistic=Opportunistic
mixed=Mixed
maps=Maps
startEnd=Start-End
racecourse=Course
summary=Summary
replay=Replay
display=Display
simulatorPolarHeader = Polardiagramm
show = zeigen
hide = verbergen
leftWind=Left wind
rightWind=Right wind
strategyOption1=strategyOption1
strategyOption2=strategyOption2
strategyOption3=strategyOption3
legLabel=Leg
raceLabel=Race
competitorLabel=Competitor
freemode=Winkel erhalten
whatIfCourse=What-If Course
@@ -211,7 +211,7 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
playSpeedBox.setVisibleLength(3); playSpeedBox.setVisibleLength(3);
playSpeedBox.setWidth("25px"); playSpeedBox.setWidth("25px");
playSpeedBox.setHeight("14px"); playSpeedBox.setHeight("14px");
playSpeedBox.setValue(1); playSpeedBox.setValue((int)timer.getPlaySpeedFactor()); // Christopher: initialize play speed box according to play speed factor
playSpeedBox.setTitle(stringMessages.playSpeedHelp()); playSpeedBox.setTitle(stringMessages.playSpeedHelp());
playSpeedBox.addValueChangeHandler(new ValueChangeHandler<Integer>() { playSpeedBox.addValueChangeHandler(new ValueChangeHandler<Integer>() {
@Override @Override
@@ -338,9 +338,6 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
if (!min.equals(timeRangeProvider.getFromTime())) { if (!min.equals(timeRangeProvider.getFromTime())) {
changed = true; changed = true;
timeSlider.setMinValue(new Double(min.getTime()), fireEvent); timeSlider.setMinValue(new Double(min.getTime()), fireEvent);
if (timeSlider.getCurrentValue() == null) {
timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent);
}
} }
if (changed) { if (changed) {
if(!timeRangeProvider.isZoomed()) { if(!timeRangeProvider.isZoomed()) {
@@ -353,6 +350,11 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
} else { } else {
timeSlider.setStepSize(1000, fireEvent); timeSlider.setStepSize(1000, fireEvent);
} }
// Christopher: following setCurrentValue requires stepsize to be set <> 0 (otherwise division by zero; NaN)
if (timeSlider.getCurrentValue() == null) {
timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent);
}
} }
} }
@@ -155,6 +155,10 @@ public class Timer {
} }
} }
public double getPlaySpeedFactor() {
return playSpeedFactor;
}
public void setRefreshInterval(long refreshIntervalInMillis) { public void setRefreshInterval(long refreshIntervalInMillis) {
this.refreshInterval = refreshIntervalInMillis; this.refreshInterval = refreshIntervalInMillis;
if (playState == PlayStates.Playing) { if (playState == PlayStates.Playing) {
@@ -37,6 +37,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.impl.Util.Triple;
@@ -53,7 +54,6 @@ import com.sap.sailing.gwt.ui.client.TimeRangeWithZoomProvider;
import com.sap.sailing.gwt.ui.client.Timer; import com.sap.sailing.gwt.ui.client.Timer;
import com.sap.sailing.gwt.ui.client.Timer.PlayModes; import com.sap.sailing.gwt.ui.client.Timer.PlayModes;
import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.Component;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
@@ -29,6 +29,7 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.Marker.Symbol;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.SimplePanel;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
@@ -38,7 +39,6 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.TimeListener; import com.sap.sailing.gwt.ui.client.TimeListener;
import com.sap.sailing.gwt.ui.client.Timer; import com.sap.sailing.gwt.ui.client.Timer;
import com.sap.sailing.gwt.ui.client.Timer.PlayModes; import com.sap.sailing.gwt.ui.client.Timer.PlayModes;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
public class LeaderboardRankChart extends SimplePanel implements RequiresResize, TimeListener { public class LeaderboardRankChart extends SimplePanel implements RequiresResize, TimeListener {
private static final int LINE_WIDTH = 1; private static final int LINE_WIDTH = 1;
@@ -1,218 +1,218 @@
package com.sap.sailing.gwt.ui.client.shared.controls.slider; package com.sap.sailing.gwt.ui.client.shared.controls.slider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Element;
import com.sap.sailing.gwt.ui.client.shared.controls.slider.TimeTicksCalculator.NormalizedInterval; import com.sap.sailing.gwt.ui.client.shared.controls.slider.TimeTicksCalculator.NormalizedInterval;
import com.sap.sailing.gwt.ui.client.shared.controls.slider.TimeTicksCalculator.TickPosition; import com.sap.sailing.gwt.ui.client.shared.controls.slider.TimeTicksCalculator.TickPosition;
public class TimeSlider extends SliderBar { public class TimeSlider extends SliderBar {
private final TimeTicksCalculator calc = new TimeTicksCalculator(); private final TimeTicksCalculator calc = new TimeTicksCalculator();
private List<TickPosition> calculatedTimeTicks; private List<TickPosition> calculatedTimeTicks;
private boolean isZoomed; private boolean isZoomed;
private final int TICKCOUNT = 10; private final int TICKCOUNT = 10;
public TimeSlider() { public TimeSlider() {
calculatedTimeTicks = new ArrayList<TickPosition>(); calculatedTimeTicks = new ArrayList<TickPosition>();
isZoomed = false; isZoomed = false;
} }
private void calculateTicks() { private void calculateTicks() {
if (!isMinMaxInitialized()) if (!isMinMaxInitialized())
return; return;
calculatedTimeTicks.clear(); calculatedTimeTicks.clear();
long minMaxDiffInMs = maxValue.longValue() - minValue.longValue(); long minMaxDiffInMs = maxValue.longValue() - minValue.longValue();
long tickInterval = minMaxDiffInMs / TICKCOUNT; long tickInterval = minMaxDiffInMs / TICKCOUNT;
NormalizedInterval normalizedTimeTickInterval = calc.normalizeTimeTickInterval(tickInterval); NormalizedInterval normalizedTimeTickInterval = calc.normalizeTimeTickInterval(tickInterval);
calculatedTimeTicks = calc.calculateTimeTicks(normalizedTimeTickInterval, minValue.longValue(), maxValue.longValue(), 1); calculatedTimeTicks = calc.calculateTimeTicks(normalizedTimeTickInterval, minValue.longValue(), maxValue.longValue(), 1);
} }
/** /**
* Draw the ticks along the line. * Draw the ticks along the line.
*/ */
protected void drawTicks() { protected void drawTicks() {
if (!isAttached() || !isMinMaxInitialized()) if (!isAttached() || !isMinMaxInitialized())
return; return;
// Draw the ticks // Draw the ticks
int lineWidth = lineElement.getOffsetWidth(); int lineWidth = lineElement.getOffsetWidth();
// Create the ticks or make them visible // Create the ticks or make them visible
for (int i = 0; i < calculatedTimeTicks.size(); i++) { for (int i = 0; i < calculatedTimeTicks.size(); i++) {
TickPosition tickPosition = calculatedTimeTicks.get(i); TickPosition tickPosition = calculatedTimeTicks.get(i);
Element tick = null; Element tick = null;
if (i < tickElements.size()) { if (i < tickElements.size()) {
tick = tickElements.get(i); tick = tickElements.get(i);
} else { // Create the new tick } else { // Create the new tick
tick = DOM.createDiv(); tick = DOM.createDiv();
DOM.setStyleAttribute(tick, "position", "absolute"); DOM.setStyleAttribute(tick, "position", "absolute");
DOM.setStyleAttribute(tick, "display", "none"); DOM.setStyleAttribute(tick, "display", "none");
DOM.appendChild(getElement(), tick); DOM.appendChild(getElement(), tick);
tickElements.add(tick); tickElements.add(tick);
} }
if (enabled) { if (enabled) {
DOM.setElementProperty(tick, "className", "gwt-SliderBar-tick"); DOM.setElementProperty(tick, "className", "gwt-SliderBar-tick");
} else { } else {
DOM.setElementProperty(tick, "className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled"); DOM.setElementProperty(tick, "className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled");
} }
// Position the tick and make it visible // Position the tick and make it visible
DOM.setStyleAttribute(tick, "visibility", "hidden"); DOM.setStyleAttribute(tick, "visibility", "hidden");
DOM.setStyleAttribute(tick, "display", ""); DOM.setStyleAttribute(tick, "display", "");
int tickWidth = tick.getOffsetWidth(); int tickWidth = tick.getOffsetWidth();
long pos = (tickPosition.getPosition().getTime() - minValue.longValue()) * lineWidth / (maxValue.longValue() - minValue.longValue()); long pos = (tickPosition.getPosition().getTime() - minValue.longValue()) * lineWidth / (maxValue.longValue() - minValue.longValue());
int tickLeftOffset = lineLeftOffset + (int) pos - (tickWidth / 2); int tickLeftOffset = lineLeftOffset + (int) pos - (tickWidth / 2);
tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth);
DOM.setStyleAttribute(tick, "left", tickLeftOffset + "px"); DOM.setStyleAttribute(tick, "left", tickLeftOffset + "px");
DOM.setStyleAttribute(tick, "visibility", "visible"); DOM.setStyleAttribute(tick, "visibility", "visible");
} }
// Hide unused ticks // Hide unused ticks
for (int i = (calculatedTimeTicks.size() + 1); i < tickElements.size(); i++) { for (int i = (calculatedTimeTicks.size() + 1); i < tickElements.size(); i++) {
DOM.setStyleAttribute(tickElements.get(i), "display", "none"); DOM.setStyleAttribute(tickElements.get(i), "display", "none");
} }
} }
/** /**
* Draw the labels along the line. * Draw the labels along the line.
*/ */
protected void drawTickLabels() { protected void drawTickLabels() {
if (!isAttached() || !isMinMaxInitialized()) if (!isAttached() || !isMinMaxInitialized())
return; return;
// Draw the tick labels // Draw the tick labels
int lineWidth = lineElement.getOffsetWidth(); int lineWidth = lineElement.getOffsetWidth();
// Create the labels or make them visible // Create the labels or make them visible
Double previousValue = null; Double previousValue = null;
for (int i = 0; i < calculatedTimeTicks.size(); i++) { for (int i = 0; i < calculatedTimeTicks.size(); i++) {
TickPosition tickPosition = calculatedTimeTicks.get(i); TickPosition tickPosition = calculatedTimeTicks.get(i);
Element label = null; Element label = null;
if (i < tickLabelElements.size()) { if (i < tickLabelElements.size()) {
label = tickLabelElements.get(i); label = tickLabelElements.get(i);
} else { // Create the new label } else { // Create the new label
label = DOM.createDiv(); label = DOM.createDiv();
DOM.setStyleAttribute(label, "position", "absolute"); DOM.setStyleAttribute(label, "position", "absolute");
DOM.setStyleAttribute(label, "display", "none"); DOM.setStyleAttribute(label, "display", "none");
if (enabled) { if (enabled) {
DOM.setElementProperty(label, "className", "gwt-SliderBar-ticklabel"); DOM.setElementProperty(label, "className", "gwt-SliderBar-ticklabel");
} else { } else {
DOM.setElementProperty(label, "className", "gwt-SliderBar-ticklabel-disabled"); DOM.setElementProperty(label, "className", "gwt-SliderBar-ticklabel-disabled");
} }
DOM.appendChild(getElement(), label); DOM.appendChild(getElement(), label);
tickLabelElements.add(label); tickLabelElements.add(label);
} }
// Set the label text // Set the label text
double value = tickPosition.getPosition().getTime(); double value = tickPosition.getPosition().getTime();
DOM.setStyleAttribute(label, "visibility", "hidden"); DOM.setStyleAttribute(label, "visibility", "hidden");
DOM.setStyleAttribute(label, "display", ""); DOM.setStyleAttribute(label, "display", "");
DOM.setElementProperty(label, "innerHTML", formatTickLabel(value, previousValue)); DOM.setElementProperty(label, "innerHTML", formatTickLabel(value, previousValue));
// Move to the left so the label width is not clipped by the // Move to the left so the label width is not clipped by the
// shell // shell
DOM.setStyleAttribute(label, "left", "0px"); DOM.setStyleAttribute(label, "left", "0px");
// Position the label and make it visible // Position the label and make it visible
int labelWidth = label.getOffsetWidth(); int labelWidth = label.getOffsetWidth();
long pos = (tickPosition.getPosition().getTime() - minValue.longValue()) * lineWidth / (maxValue.longValue() - minValue.longValue()); long pos = (tickPosition.getPosition().getTime() - minValue.longValue()) * lineWidth / (maxValue.longValue() - minValue.longValue());
int labelLeftOffset = lineLeftOffset + (int) pos - (labelWidth / 2); int labelLeftOffset = lineLeftOffset + (int) pos - (labelWidth / 2);
labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
DOM.setStyleAttribute(label, "left", labelLeftOffset + "px"); DOM.setStyleAttribute(label, "left", labelLeftOffset + "px");
DOM.setStyleAttribute(label, "visibility", "visible"); DOM.setStyleAttribute(label, "visibility", "visible");
previousValue = value; previousValue = value;
} }
// Hide unused labels // Hide unused labels
for (int i = (calculatedTimeTicks.size() + 1); i < tickLabelElements.size(); i++) { for (int i = (calculatedTimeTicks.size() + 1); i < tickLabelElements.size(); i++) {
DOM.setStyleAttribute(tickLabelElements.get(i), "display", "none"); DOM.setStyleAttribute(tickLabelElements.get(i), "display", "none");
} }
} }
public void clearMarkersAndLabelsAndTicks() { public void clearMarkersAndLabelsAndTicks() {
clearMarkers(); clearMarkers();
for (Element elem : tickLabelElements) { for (Element elem : tickLabelElements) {
DOM.setStyleAttribute(elem, "display", "none"); DOM.setStyleAttribute(elem, "display", "none");
DOM.setStyleAttribute(elem, "visibility", "hidden"); DOM.setStyleAttribute(elem, "visibility", "hidden");
} }
for (Element elem : tickElements) { for (Element elem : tickElements) {
DOM.setStyleAttribute(elem, "display", "none"); DOM.setStyleAttribute(elem, "display", "none");
DOM.setStyleAttribute(elem, "visibility", "hidden"); DOM.setStyleAttribute(elem, "visibility", "hidden");
} }
for (Element elem : markerLabelElements) { for (Element elem : markerLabelElements) {
DOM.setStyleAttribute(elem, "display", "none"); DOM.setStyleAttribute(elem, "display", "none");
DOM.setStyleAttribute(elem, "visibility", "hidden"); DOM.setStyleAttribute(elem, "visibility", "hidden");
} }
for (Element elem : markerElements) { for (Element elem : markerElements) {
DOM.setStyleAttribute(elem, "display", "none"); DOM.setStyleAttribute(elem, "display", "none");
DOM.setStyleAttribute(elem, "visibility", "hidden"); DOM.setStyleAttribute(elem, "visibility", "hidden");
} }
} }
/** /**
* Draw the knob where it is supposed to be relative to the line. * Draw the knob where it is supposed to be relative to the line.
*/ */
protected void drawKnob() { protected void drawKnob() {
if (!isAttached() || !isMinMaxInitialized()) if (!isAttached() || !isMinMaxInitialized())
return; return;
Element knobElement = knobImage.getElement(); Element knobElement = knobImage.getElement();
if(curValue >= minValue && curValue <= maxValue) { if(curValue >= minValue && curValue <= maxValue) {
// Move the knob to the correct position // Move the knob to the correct position
int lineWidth = lineElement.getOffsetWidth(); int lineWidth = lineElement.getOffsetWidth();
int knobWidth = knobElement.getOffsetWidth(); int knobWidth = knobElement.getOffsetWidth();
int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2)); int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2));
knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1); knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1);
DOM.setStyleAttribute(knobElement, "left", knobLeftOffset + "px"); DOM.setStyleAttribute(knobElement, "left", knobLeftOffset + "px");
DOM.setStyleAttribute(knobElement, "visibility", "visible"); DOM.setStyleAttribute(knobElement, "visibility", "visible");
DOM.setStyleAttribute(knobElement, "display", ""); DOM.setStyleAttribute(knobElement, "display", "");
} else { } else {
DOM.setStyleAttribute(knobElement, "display", "none"); DOM.setStyleAttribute(knobElement, "display", "none");
DOM.setStyleAttribute(knobElement, "visibility", "hidden"); DOM.setStyleAttribute(knobElement, "visibility", "hidden");
} }
} }
public void setMaxValue(Double maxValue, boolean fireEvent) { public void setMaxValue(Double maxValue, boolean fireEvent) {
if(!isZoomed) { if(!isZoomed) {
super.setMaxValue(maxValue, fireEvent); super.setMaxValue(maxValue, fireEvent);
} else { } else {
this.maxValue = maxValue; this.maxValue = maxValue;
} }
calculateTicks(); calculateTicks();
} }
public void setMinValue(Double minValue, boolean fireEvent) { public void setMinValue(Double minValue, boolean fireEvent) {
if(!isZoomed) { if(!isZoomed) {
super.setMinValue(minValue, fireEvent); super.setMinValue(minValue, fireEvent);
} else { } else {
this.minValue = minValue; this.minValue = minValue;
} }
calculateTicks(); calculateTicks();
} }
public void setStepSize(double stepSize, boolean fireEvent) { public void setStepSize(double stepSize, boolean fireEvent) {
if(!isZoomed) { if(!isZoomed) {
super.setStepSize(stepSize, fireEvent); super.setStepSize(stepSize, fireEvent);
} else { } else {
this.stepSize = stepSize; this.stepSize = stepSize;
} }
} }
public boolean isZoomed() { public boolean isZoomed() {
return isZoomed; return isZoomed;
} }
public void setZoomed(boolean isZoomed) { public void setZoomed(boolean isZoomed) {
this.isZoomed = isZoomed; this.isZoomed = isZoomed;
} }
} }
@@ -4,10 +4,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ListBox;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.filter.BinaryOperator; import com.sap.sailing.domain.common.filter.BinaryOperator;
import com.sap.sailing.domain.common.filter.NumberFilter; import com.sap.sailing.domain.common.filter.NumberFilter;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
public abstract class AbstractCompetitorNumberFilterUIFactory<T extends Number> implements FilterUIFactory<CompetitorDTO> { public abstract class AbstractCompetitorNumberFilterUIFactory<T extends Number> implements FilterUIFactory<CompetitorDTO> {
protected List<BinaryOperator.Operators> supportedOperators; protected List<BinaryOperator.Operators> supportedOperators;
@@ -4,10 +4,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.ListBox;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.filter.TextFilter; import com.sap.sailing.domain.common.filter.TextFilter;
import com.sap.sailing.domain.common.filter.TextOperator; import com.sap.sailing.domain.common.filter.TextOperator;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
public abstract class AbstractCompetitorTextFilterUIFactory implements FilterUIFactory<CompetitorDTO> { public abstract class AbstractCompetitorTextFilterUIFactory implements FilterUIFactory<CompetitorDTO> {
@@ -16,11 +16,11 @@ import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.filter.Filter; import com.sap.sailing.domain.common.filter.Filter;
import com.sap.sailing.domain.common.filter.FilterSet; import com.sap.sailing.domain.common.filter.FilterSet;
import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.DataEntryDialog;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
public abstract class AbstractCompetitorsFilterSetDialog extends DataEntryDialog<FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>>> { public abstract class AbstractCompetitorsFilterSetDialog extends DataEntryDialog<FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>>> {
private final FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> competitorsFilterSet; private final FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> competitorsFilterSet;
@@ -1,6 +1,6 @@
package com.sap.sailing.gwt.ui.client.shared.filter; package com.sap.sailing.gwt.ui.client.shared.filter;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO;
/** /**
* A factory for FilterWithUI<Competitor> instances * A factory for FilterWithUI<Competitor> instances
@@ -1,8 +1,8 @@
package com.sap.sailing.gwt.ui.client.shared.filter; package com.sap.sailing.gwt.ui.client.shared.filter;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.filter.AbstractTextFilter; import com.sap.sailing.domain.common.filter.AbstractTextFilter;
import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
/** /**
* A filter filtering competitors by their nationality * A filter filtering competitors by their nationality

Some files were not shown because too many files have changed in this diff Show More