Merge branch 'origin/simulator_maps_v3'

This commit is contained in:
Christopher Ronnewinkel (D036654) committed 2014-08-04 22:56:57 +02:00
commit b7f2b6f63e
437 files changed
+14476 -7069

No files matched your search

@@ -0,0 +1,13 @@
# Operating Igtimi WindBots
For wind measurements we're trying to consistently use Igtimi WindBot equipment. The AdminConsole has a tab "Igtimi Accounts" where accounts for the Igtimi YachtBot web site can be managed. WindBots associated with any of these accounts will feed their wind readings, when active, into races being tracked. Operations of the WindBots mainly needs to take care of charging the devices, turning them on when they leave the harbor and turning them off when they leave the race course. This can also be delegated to race officials carrying these devices on their vessels.
The devices have three LEDs whose status works as follows:
1. The three LED’s are “power”, “connection”, and “GPS” in order from top to bottom.
2. All the lights should be on when operating normally.
3. Slow flashing is normal while booting.
4. Fast flashing means that function can’t start or has failed - e.g. SIM not inserted. i.e. you shouldn’t see this
5. During sync, the “power” light blips and the “connection” LED should stay on until the sync has finished.
More information on running the WindBots can be found [here](https://igtimi.desk.com/customer/portal/articles/1494533-yachtbot-tracker-basic-operation?b_id=1690).
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# This is a template for creating backup of data and persist them
# to a central backup server. Make sure to adapt this script to your needs.
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
export JAVA_HOME=/home/trac/jdk1.7.0_02
jar=`find plugins -name 'com.sap.sailing.expeditionconnector_*.jar' | sort | tail -1`
echo Using JAR file $jar
+4 -1
View File
@@ -1,6 +1,6 @@
# Welcome to the SAP Sailing Wiki
This is the <img src="http://www.sapsailing.com/images/sap-logo.png"/> Sailing Analytics Wiki where useful information regarding this project can be found.
This is the <img src="http://www.sapsailing.com/images/sap-logo_grey.png"/> Wiki where useful information regarding this project can be found.
### The Pitch
@@ -15,6 +15,7 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[EC2 Backup Strategy|wiki/amazon-ec2-backup-strategy]]
* [[TracTrac|wiki/tractrac-lifecycle]]
* [[Analytics on a stick|wiki/analytics-on-a-stick]]
* [[Operating Igtimi WindBots/windbot-operations]]
* [[Monitoring Apache and RabbitMQ|wiki/monitoring-apache-and-rabbitmq]]
* [[JMX Support|wiki/jmx]]
* Information for Developers
@@ -44,11 +45,13 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[Racecommittee App|wiki/racecommittee-app]]
* [[Web Services API|wiki/sailing-webservices]]
* [[Theses (Bachelor, Master, ...)|wiki/theses]]
* [[Google Analytics (Web Page Tracking)|wiki/ganalytics]]
* Planning and Event Information
* [[Project Planning (bigger development)|wiki/planning]]
* [[General Event Planning|wiki/general-event-planning]]
* [[Information about Extreme Sailing Series|wiki/extreme-sailing-series]]
* [[Travem&uuml;nder Woche 2014 event page|wiki/tw2014]]
* [[505 worlds Kiel 2014 event page|wiki/505-worlds-kiel-2014]]
### Internal services (not related to wiki but useful)
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
#
# Copyright (c) 2006 Johannes E. Schindelin
#
@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
#
# Copyright (c) 2006 Johannes E. Schindelin
#
@@ -2,5 +2,7 @@ package com.sap.sailing.domain.common;
public enum ScoringSchemeType {
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE,
HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT,
HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT
}
@@ -186,6 +186,22 @@ public abstract class AbstractLeaderboardDTO implements Serializable {
return null;
}
public int getRaceColumnsCount() {
return races.size();
}
public int getTrackedRacesCount() {
int result = 0;
for (RaceColumnDTO race : getRaceList()) {
for (FleetDTO fleet : race.getFleets()) {
if (race.isTrackedRace(fleet)) {
result++;
}
}
}
return result;
}
public List<RaceColumnDTO> getRaceList() {
return races;
}
@@ -13,6 +13,9 @@ public class TrackedRaceDTO implements Serializable {
public boolean hasWindData;
public boolean hasGPSData;
public boolean hasAudioData;
public boolean hasVideoData;
public boolean hasMeasuredWindData;
public Date startOfTracking;
public Date endOfTracking;
@@ -27,6 +30,9 @@ public class TrackedRaceDTO implements Serializable {
result = prime * result + ((endOfTracking == null) ? 0 : endOfTracking.hashCode());
result = prime * result + (hasGPSData ? 1231 : 1237);
result = prime * result + (hasWindData ? 1231 : 1237);
result = prime * result + (hasAudioData ? 1231 : 1237);
result = prime * result + (hasVideoData ? 1231 : 1237);
result = prime * result + (hasMeasuredWindData ? 1231 : 1237);
result = prime * result + ((startOfTracking == null) ? 0 : startOfTracking.hashCode());
result = prime * result + ((timePointOfNewestEvent == null) ? 0 : timePointOfNewestEvent.hashCode());
return result;
@@ -52,6 +58,12 @@ public class TrackedRaceDTO implements Serializable {
return false;
if (hasWindData != other.hasWindData)
return false;
if (hasAudioData != other.hasAudioData)
return false;
if (hasVideoData != other.hasVideoData)
return false;
if (hasMeasuredWindData != other.hasMeasuredWindData)
return false;
if (startOfTracking == null) {
if (other.startOfTracking != null)
return false;
@@ -28,6 +28,15 @@ public class MediaTrack implements Serializable {
public String toString() {
return mediaType.name() + '/' + mediaSubType.toString();
}
public static MimeType byName(String mimeTypeName) {
try {
return MimeType.valueOf(mimeTypeName);
} catch (IllegalArgumentException ex) {
return null;
}
}
}
public enum MediaType {
@@ -5,6 +5,17 @@
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<!-- Disable directory listing: -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.eclipse.jetty.servlet.DefaultServlet
</servlet-class>
<init-param>
<param-name>dirAllowed</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>Igtimi Oauth Callback REST API</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
@@ -7,6 +7,8 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -498,6 +500,16 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
URI uri = new URI((String) serverUrl);
result.add(uri);
}
// sort those to the front that don't do port 443 nor wss://
Collections.sort(result, new Comparator<URI>() {
@Override
public int compare(URI o1, URI o2) {
int o1Score = (o1.toString().contains("443") ? 1 : 0) + (o1.toString().contains("wss") ? 1 : 0);
int o2Score = (o2.toString().contains("443") ? 1 : 0) + (o2.toString().contains("wss") ? 1 : 0);
return o1Score - o2Score;
}
});
logger.info("Trying Igtimi WebSocket servers in the following order: "+result);
return result;
}
@@ -185,42 +185,47 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
Speed aws = getAWS(timePoint, awsPair);
com.sap.sse.common.Util.Pair<GpsLatLong, GpsLatLong> gpsPair = getSurroundingFixes(getGpsTrack(deviceSerialNumber), timePoint);
Position pos = getPosition(timePoint, gpsPair);
com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint);
com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint);
Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos);
if (awa != null && aws != null && pos != null && heading != null) {
Bearing apparentWindDirection = heading.add(awa);
SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
/*
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to improve
* precision of boat speed / coarse over SOG/COG measurements:
*
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter might
* happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position jump as
* well. Probably not though a direction/speed correlation :)
*
* All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate measure.
* I don't believe that delta position really adds any more "truth" to the measurement of physical reality,
* if that makes sense. I'd trust d-p even less at low speeds, where you'll see the most disagreement. Also
* there is a significant quantisation noise error in the d-p calculations from the GPS resolution too, so
* you'd have to smooth it before averaging - possibly in a speed dependent way.
*
* Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to
* calculate position, just the algorithm is different. I suspect that merging the two might be, in
* practice, just a slightly indirect way of averaging. If you like the central limit theorem in action over
* the set of algorithms!
*
* So again, my personal preference would be to work with the data that should be the most accurate
* (COG/SOG) and consider algorithms that handle smoothing of that data best."
*/
com.sap.sse.common.Util.Pair<SOG, SOG> sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint);
Speed sog = getSOG(timePoint, sogPair);
com.sap.sse.common.Util.Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint);
Bearing cog = getCOG(timePoint, cogPair);
SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
if (pos != null) {
com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint);
com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint);
Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos);
if (awa != null && aws != null && heading != null) {
Bearing apparentWindDirection = heading.add(awa);
SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
/*
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to
* improve precision of boat speed / coarse over SOG/COG measurements:
*
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter
* might happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position
* jump as well. Probably not though a direction/speed correlation :)
*
* All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate
* measure. I don't believe that delta position really adds any more "truth" to the measurement of
* physical reality, if that makes sense. I'd trust d-p even less at low speeds, where you'll see the
* most disagreement. Also there is a significant quantisation noise error in the d-p calculations from
* the GPS resolution too, so you'd have to smooth it before averaging - possibly in a speed dependent
* way.
*
* Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to
* calculate position, just the algorithm is different. I suspect that merging the two might be, in
* practice, just a slightly indirect way of averaging. If you like the central limit theorem in action
* over the set of algorithms!
*
* So again, my personal preference would be to work with the data that should be the most accurate
* (COG/SOG) and consider algorithms that handle smoothing of that data best."
*/
com.sap.sse.common.Util.Pair<SOG, SOG> sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint);
Speed sog = getSOG(timePoint, sogPair);
com.sap.sse.common.Util.Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint);
Bearing cog = getCOG(timePoint, cogPair);
SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
} else {
result = null;
}
} else {
result = null;
}
@@ -136,6 +136,8 @@ import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl;
import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl;
import com.sap.sailing.domain.racelog.impl.RaceLogImpl;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -485,44 +487,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME
.name()), resultDiscardingRule, scoringScheme, courseArea);
// For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage
// them here:
Map<String, Fleet> fleetsByName = new HashMap<String, Fleet>();
// For a FlexibleLeaderboard, there should be only the default fleet for any race column
for (Object dbRaceColumnAsObject : dbRaceColumns) {
BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject;
String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
RaceColumn raceColumn = result.addRaceColumn(columnName,
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()));
Map<String, RaceIdentifier> raceIdentifiers = loadRaceIdentifiers(dbRaceColumn);
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(null);
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(result.getFleet(null).getName());
if (defaultFleetRaceIdentifier == null) {
// Backward compatibility
defaultFleetRaceIdentifier = raceIdentifiers.get(null);
}
if (defaultFleetRaceIdentifier != null) {
Fleet defaultFleet = result.getFleet(null);
if (defaultFleet != null) {
raceIdentifiers.put(defaultFleet.getName(), defaultFleetRaceIdentifier);
raceColumn.setRaceIdentifier(defaultFleet, defaultFleetRaceIdentifier);
} else {
// leaderboard has no default fleet; don't know what to do with default RaceIdentifier
logger.warning("Discarding RaceIdentifier " + defaultFleetRaceIdentifier
+ " for default fleet for leaderboard " + result.getName()
+ " because no default fleet was found in leaderboard");
}
raceIdentifiers.remove(null);
}
List<Fleet> fleets = new ArrayList<Fleet>();
for (String fleetName : raceIdentifiers.keySet()) {
Fleet fleet = fleetsByName.get(fleetName);
if (fleet == null) {
fleet = new FleetImpl(fleetName);
fleetsByName.put(fleetName, fleet);
}
fleets.add(fleet);
}
if (fleets.isEmpty()) {
fleets.add(result.getFleet(null));
}
String columnName = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
RaceColumn raceColumn = result.addRaceColumn(columnName,
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()),
fleets.toArray(new Fleet[0]));
for (Map.Entry<String, RaceIdentifier> e : raceIdentifiers.entrySet()) {
raceColumn.setRaceIdentifier(fleetsByName.get(e.getKey()), e.getValue());
}
}
@@ -1347,6 +1336,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return loadRaceLogDefineMarkEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(CloseOpenEndedDeviceMappingEvent.class.getSimpleName())) {
return loadRaceLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(AdditionalScoringInformationEventImpl.class.getSimpleName())) {
return loadRaceLogAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
}
throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass));
@@ -1432,6 +1423,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
deviceMappingEventId, closingTimePoint);
}
private RaceLogEvent loadRaceLogAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
AdditionalScoringInformationType informationType = AdditionalScoringInformationType.valueOf(dbObject.get(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name()).toString());
return raceLogEventFactory.createAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, competitors, passId, informationType);
}
private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME);
@@ -111,5 +111,8 @@ public enum FieldNames {
FROM_MILLIS, TO_MILLIS,
// Result Import url fields
RESULT_PROVIDERNAME, RESULT_URL
RESULT_PROVIDERNAME, RESULT_URL,
// type of additional scoring information
RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE
}
@@ -82,6 +82,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -842,6 +843,21 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
return result;
}
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, AdditionalScoringInformationEvent event) {
BasicDBObject result = new BasicDBObject();
storeRaceLogIdentifier(raceLogIdentifier, result);
result.put(FieldNames.RACE_LOG_EVENT.name(), storeAdditionalScoringInformation(event));
return result;
}
private Object storeAdditionalScoringInformation(AdditionalScoringInformationEvent event) {
DBObject result = new BasicDBObject();
storeRaceLogEventProperties(event, result);
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), event.getClass().getSimpleName());
result.put(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name(), event.getType().name());
return result;
}
private Object storeRaceLogWindFix(RaceLogWindFixEvent event) {
DBObject result = new BasicDBObject();
storeRaceLogEventProperties(event, result);
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -166,4 +167,10 @@ public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor {
DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event);
mongoObjectFactory.storeRaceLogEventEvent(object);
}
@Override
public void visit(AdditionalScoringInformationEvent event) {
DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event);
mongoObjectFactory.storeRaceLogEventEvent(object);
}
}
@@ -64,7 +64,7 @@ public class MediaDBImpl implements MediaDB {
try {
dbVideos.insert(dbMediaTrack);
} catch (MongoException.DuplicateKey e) {
throw new IllegalArgumentException(e);
throw new IllegalArgumentException("Duplicate key '" + dbId + "' caused an error when importing media (title: '" + title + "')", e);
}
}
@@ -5,6 +5,17 @@
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<!-- Disable directory listing: -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.eclipse.jetty.servlet.DefaultServlet
</servlet-class>
<init-param>
<param-name>dirAllowed</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>RecordFixesPostServlet</servlet-name>
<servlet-class>com.sap.sailing.domain.racelogtracking.servlet.RecordFixesPostServlet</servlet-class>
@@ -17,6 +17,8 @@ Export-Package: com.sap.sailing.domain.base,
com.sap.sailing.domain.racelog,
com.sap.sailing.domain.racelog.analyzing.impl,
com.sap.sailing.domain.racelog.impl,
com.sap.sailing.domain.racelog.scoring,
com.sap.sailing.domain.racelog.scoring.impl,
com.sap.sailing.domain.racelog.state,
com.sap.sailing.domain.racelog.state.impl,
com.sap.sailing.domain.racelog.state.racingprocedure,
@@ -15,6 +15,9 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO;
*
*/
public interface CompetitorStore extends CompetitorFactory {
public interface CompetitorUpdateListener {
void competitorUpdated(Competitor competitor);
}
/**
* If a valid competitor is returned and the caller has information available that could be used to update the competitor,
@@ -62,4 +65,12 @@ public interface CompetitorStore extends CompetitorFactory {
Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, Nationality newNationality);
CompetitorDTO convertToCompetitorDTO(Competitor c);
/**
* Listeners added here are notified whenever {@link #updateCompetitor(String, String, Color, String, Nationality)} is called
* for any competitor in this store.
*/
void addCompetitorUpdateListener(CompetitorUpdateListener listener);
void removeCompetitorUpdateListener(CompetitorUpdateListener listener);
}
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -25,6 +26,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab
private static final long serialVersionUID = -4198298775476586931L;
private final Map<Serializable, Competitor> competitorCache;
private final Map<String, Competitor> competitorsByIdAsString;
private transient Set<CompetitorUpdateListener> listeners;
/**
* The competitors contained in this map will have their changeable properties
@@ -43,13 +45,25 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab
competitorsByIdAsString = new HashMap<String, Competitor>();
competitorsToUpdateDuringGetOrCreate = new HashSet<Competitor>();
weakCompetitorDTOCache = new WeakHashMap<Competitor, CompetitorDTO>();
listeners = Collections.synchronizedSet(new HashSet<CompetitorStore.CompetitorUpdateListener>());
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
weakCompetitorDTOCache = new WeakHashMap<Competitor, CompetitorDTO>();
listeners = Collections.synchronizedSet(new HashSet<CompetitorStore.CompetitorUpdateListener>());
}
@Override
public void addCompetitorUpdateListener(CompetitorUpdateListener listener) {
listeners.add(listener);
}
@Override
public void removeCompetitorUpdateListener(CompetitorUpdateListener listener) {
listeners.remove(listener);
}
private Competitor createCompetitor(Serializable id, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) {
Competitor result = new CompetitorImpl(id, name, displayColor, team, boat);
addNewCompetitor(id, result);
@@ -179,6 +193,11 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab
LockUtil.unlockAfterWrite(lock);
}
}
synchronized (listeners) {
for (CompetitorUpdateListener listener : listeners) {
listener.competitorUpdated(competitor);
}
}
return competitor;
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.racelog;
/**
* Marker interface that all classes implementing {@link RaceLogEvent} should
* also implement, if the addition of that event should invalidate leaderboard cache.
*
* @author Simon Marcel Pamies
*/
public interface InvalidatesLeaderboardCache {
}
@@ -5,6 +5,7 @@ import java.util.HashSet;
import java.util.NavigableSet;
import java.util.UUID;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WithID;
import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException;
import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator;
@@ -109,7 +110,8 @@ public interface RaceLog extends Track<RaceLogEvent>, WithID {
RaceLogEvent getEventById(Serializable id);
/**
* Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}.
* Get a {@link NavigableSet} of unrevoked events regardless of the {@code pass}. Events are sorted by
* their {@link TimePoint} and the oldest is returned first.
* @return
*/
NavigableSet<RaceLogEvent> getUnrevokedEvents();
@@ -12,6 +12,8 @@ import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -117,4 +119,7 @@ public interface RaceLogEventFactory {
CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId,
Serializable deviceMappingEventId, TimePoint closingTimePoint);
AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, RaceLogEventAuthor author, int currentPassId,
AdditionalScoringInformationType informationType);
}
@@ -12,6 +12,8 @@ import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.racelog.impl.RaceLogEventRestoreFactoryImpl;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -90,4 +92,8 @@ public interface RaceLogEventRestoreFactory extends RaceLogEventFactory {
CloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable pId, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint);
AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable pId, List<Competitor> competitors, int passId,
AdditionalScoringInformationType informationType);
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.racelog;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -51,4 +52,6 @@ public interface RaceLogEventVisitor {
public void visit(DefineMarkEvent event);
public void visit(CloseOpenEndedDeviceMappingEvent event);
public void visit(AdditionalScoringInformationEvent additionalScoringInformation);
}
@@ -0,0 +1,57 @@
package com.sap.sailing.domain.racelog.analyzing.impl;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
/**
* Returns all {@link AdditionalScoringInformationEvent}s from the race log
* that have not been revoked when using {@link #analyze()}. The resulting list
* has the newest event first. The pass id is not relevant for the result.
*
* The reason that this finder returns a list is that there could be more than
* one event of the same class but having a different {@link AdditionalScoringInformationType}.
*
* One can also use {@link #analyze(AdditionalScoringInformationType)} to check
* for the existence of an event with exactly the provided type.
*
* @author Simon Marcel Pamies
*/
public class AdditionalScoringInformationFinder extends RaceLogAnalyzer<List<AdditionalScoringInformationEvent>> {
public AdditionalScoringInformationFinder(RaceLog raceLog) {
super(raceLog);
}
/**
* Analyze the race log for all events that match the given type filter. Returns only the <emph>newest</emph>
* one as we assume that there can be only one unrevoked event of the same type.
*/
public AdditionalScoringInformationEvent analyze(AdditionalScoringInformationType filterBy) {
AdditionalScoringInformationEvent result = null;
final List<AdditionalScoringInformationEvent> allUnrevokedEventsFromNewestToOldest = analyze();
for (AdditionalScoringInformationEvent event : allUnrevokedEventsFromNewestToOldest) {
if (event != null && event.getType() == filterBy) {
result = event;
break;
}
}
return result;
}
@Override
protected List<AdditionalScoringInformationEvent> performAnalysis() {
final List<AdditionalScoringInformationEvent> result = new ArrayList<AdditionalScoringInformationEvent>();
// fetch all unrevoked events starting with newest going to oldest
for (RaceLogEvent event : getRaceLog().getUnrevokedEventsDescending()) {
if (event instanceof AdditionalScoringInformationEvent) {
result.add((AdditionalScoringInformationEvent) event);
}
}
return result;
}
}
@@ -0,0 +1,134 @@
package com.sap.sailing.domain.racelog.impl;
import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent;
import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent;
import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent;
import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent;
import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent;
public abstract class AbstractRaceLogChangedVisitor implements RaceLogEventVisitor {
protected abstract void notifyListenerAboutEventAdded(RaceLogEvent event);
public void visit(RaceLogFlagEvent event) {
notifyListenerAboutEventAdded(event);
}
public void visit(RaceLogPassChangeEvent event) {
notifyListenerAboutEventAdded(event);
}
public void visit(RaceLogRaceStatusEvent event) {
notifyListenerAboutEventAdded(event);
}
public void visit(RaceLogStartTimeEvent event) {
notifyListenerAboutEventAdded(event);
}
public void visit(RaceLogCourseAreaChangedEvent event) {
notifyListenerAboutEventAdded(event);
}
public void visit(RaceLogCourseDesignChangedEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogFinishPositioningListChangedEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogFinishPositioningConfirmedEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogPathfinderEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogGateLineOpeningTimeEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogStartProcedureChangedEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogProtestStartTimeEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RaceLogWindFixEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(DeviceCompetitorMappingEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(DeviceMarkMappingEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(DenoteForTrackingEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(StartTrackingEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RevokeEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(RegisterCompetitorEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(DefineMarkEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(CloseOpenEndedDeviceMappingEvent event) {
notifyListenerAboutEventAdded(event);
}
@Override
public void visit(AdditionalScoringInformationEvent additionalScoringInformation) {
notifyListenerAboutEventAdded(additionalScoringInformation);
}
}
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -132,4 +133,9 @@ public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor {
public void visit(CloseOpenEndedDeviceMappingEvent event) {
}
@Override
public void visit(AdditionalScoringInformationEvent event) {
}
}
@@ -1,133 +1,18 @@
package com.sap.sailing.domain.racelog.impl;
import com.sap.sailing.domain.racelog.RaceLogChangedListener;
import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent;
import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent;
import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
import com.sap.sailing.domain.racelog.tracking.DeviceCompetitorMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DeviceMarkMappingEvent;
import com.sap.sailing.domain.racelog.tracking.RegisterCompetitorEvent;
import com.sap.sailing.domain.racelog.tracking.StartTrackingEvent;
import com.sap.sailing.domain.racelog.RaceLogEvent;
public class RaceLogChangedVisitor implements RaceLogEventVisitor {
private RaceLogChangedListener listener;
public class RaceLogChangedVisitor extends AbstractRaceLogChangedVisitor {
private final RaceLogChangedListener listener;
public RaceLogChangedVisitor(RaceLogChangedListener listener) {
this.listener = listener;
}
public void visit(RaceLogFlagEvent event) {
listener.eventAdded(event);
}
public void visit(RaceLogPassChangeEvent event) {
listener.eventAdded(event);
}
public void visit(RaceLogRaceStatusEvent event) {
listener.eventAdded(event);
}
public void visit(RaceLogStartTimeEvent event) {
listener.eventAdded(event);
}
public void visit(RaceLogCourseAreaChangedEvent event) {
listener.eventAdded(event);
}
public void visit(RaceLogCourseDesignChangedEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogFinishPositioningListChangedEvent event) {
protected void notifyListenerAboutEventAdded(RaceLogEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogFinishPositioningConfirmedEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogPathfinderEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogGateLineOpeningTimeEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogStartProcedureChangedEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogProtestStartTimeEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RaceLogWindFixEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(DeviceCompetitorMappingEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(DeviceMarkMappingEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(DenoteForTrackingEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(StartTrackingEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RevokeEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(RegisterCompetitorEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(DefineMarkEvent event) {
listener.eventAdded(event);
}
@Override
public void visit(CloseOpenEndedDeviceMappingEvent event) {
listener.eventAdded(event);
}
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.racelog.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -30,6 +31,9 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -268,4 +272,11 @@ public class RaceLogEventFactoryImpl implements RaceLogEventFactory {
return new CloseOpenEndedDeviceMappingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint,
UUID.randomUUID(), passId, deviceMappingEventId, closingTimePoint);
}
@Override
public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id,
RaceLogEventAuthor author, int currentPassId, AdditionalScoringInformationType informationType) {
return new AdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id, Collections.<Competitor>emptyList(), currentPassId,
informationType);
}
}
@@ -28,6 +28,9 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -178,4 +181,11 @@ public class RaceLogEventRestoreFactoryImpl extends RaceLogEventFactoryImpl impl
return new CloseOpenEndedDeviceMappingEventImpl(createdAt, author, logicalTimePoint, pId, passId,
deviceMappingEventId, closingTimePoint);
}
@Override
public AdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint createdAt,
RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, List<Competitor> competitors, int passId,
AdditionalScoringInformationType informationType) {
return new AdditionalScoringInformationEventImpl(createdAt, author, logicalTimePoint, pId, competitors, passId, informationType);
}
}
@@ -153,7 +153,6 @@ public class RaceLogImpl extends TrackImpl<RaceLogEvent> implements RaceLog {
RevokeEvent revokeEvent = (RevokeEvent) newEvent;
try {
checkIfSuccessfullyRevokes(revokeEvent);
lockForWrite();
revokedEventIds.add(revokeEvent.getRevokedEventId());
unlockAfterWrite();
@@ -169,15 +168,20 @@ public class RaceLogImpl extends TrackImpl<RaceLogEvent> implements RaceLog {
unlockAfterRead();
if (revokedEvent == null) {
throw new NotRevokableException("RevokeEvent added, that refers to non-existent event to be revoked");
}
// it can happen that the event that has been revoked is not yet loaded - as we assume
// that race log events never get removed we can safely continue and assume that
// the event will be loaded later
logger.warning("RevokeEvent for "+revokeEvent.getShortInfo()+" added, that refers to non-existent event to be revoked. Could also happen that the revoke event is before the event to be revoked.");
} else {
if (! (revokedEvent instanceof Revokable)) {
throw new NotRevokableException("RevokeEvent trying to revoke non-revokable event");
}
if (! (revokedEvent instanceof Revokable)) {
throw new NotRevokableException("RevokeEvent trying to revoke non-revokable event");
}
if (getInternalRawFixes().comparator().compare(revokeEvent, revokedEvent) <= 0) {
throw new NotRevokableException("RevokeEvent does not have sufficient priority");
// make sure to compare only author priorities - assuming that revoke events are
// independent of passes and times
if (revokeEvent.getAuthor().compareTo(revokedEvent.getAuthor()) < 0) {
throw new NotRevokableException("RevokeEvent does not have sufficient priority");
}
}
}
@@ -301,7 +305,7 @@ public class RaceLogImpl extends TrackImpl<RaceLogEvent> implements RaceLog {
try {
for (RaceLogEvent event : getRawFixes()) {
if (event instanceof RevokeEvent) {
revokedEventIds.add(event.getId());
revokedEventIds.add(((RevokeEvent)event).getRevokedEventId());
}
}
} finally {
@@ -0,0 +1,68 @@
package com.sap.sailing.domain.racelog.impl;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogChangedListener;
import com.sap.sailing.domain.racelog.RaceLogEvent;
/**
* A listener that unregisters itself from the race log's listeners if its notification target is no longer strongly
* referenced.
*
* @author Axel Uhl (d043530)
*
*/
public class WeakRaceLogChangedVisitor extends AbstractRaceLogChangedVisitor {
private static final Logger logger = Logger.getLogger(WeakRaceLogChangedVisitor.class.getName());
private static ReferenceQueue<? super RaceLogChangedListener> queue = new ReferenceQueue<RaceLogChangedListener>();
private static Map<Reference<?>, WeakRaceLogChangedVisitor> referenceToVisitor = new HashMap<Reference<?>, WeakRaceLogChangedVisitor>();
static {
new Thread(WeakRaceLogChangedVisitor.class.getSimpleName() + " weak reference cleaner") {
@Override
public void run() {
while (true) {
try {
Reference<?> ref = queue.remove();
WeakRaceLogChangedVisitor visitor = referenceToVisitor.get(ref);
visitor.removeListener();
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Error trying to clean weak reference", e);
} catch (Exception e2) {
logger.severe("Shouldn't have gotten here. Continuing...");
}
}
}
}.start();
}
private final WeakReference<RaceLogChangedListener> listenerRef;
private final RaceLog removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced;
public WeakRaceLogChangedVisitor(RaceLog removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced, RaceLogChangedListener listener) {
this.removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced = removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced;
listenerRef = new WeakReference<RaceLogChangedListener>(listener, queue);
referenceToVisitor.put(listenerRef, this);
}
@Override
protected void notifyListenerAboutEventAdded(RaceLogEvent event) {
RaceLogChangedListener listener = listenerRef.get();
if (listener == null) {
removeListener();
} else {
listener.eventAdded(event);
}
}
private void removeListener() {
removeFromThisRaceLogWhenListenerNoLongerStronglyReferenced.removeListener(this);
}
}
@@ -0,0 +1,22 @@
package com.sap.sailing.domain.racelog.scoring;
import com.sap.sailing.domain.racelog.InvalidatesLeaderboardCache;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.Revokable;
/**
* Event that could possibly overwrite scoring for a leaderboard column. The idea
* is that the scoring scheme checks if such an event is available and if it applies
* extended scoring rules. The additional information contains a type that indicates
* which action should be executed.
*
* @author Simon Marcel Pamies
*/
public interface AdditionalScoringInformationEvent extends RaceLogEvent, Revokable, InvalidatesLeaderboardCache {
/**
* @return the type of information this event holds
*/
AdditionalScoringInformationType getType();
}
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.racelog.scoring;
public enum AdditionalScoringInformationType {
/**
* Indicates that for a scoring scheme adhering to a max point rule
* where the winner gets a higher number than its successors the
* maximum number of points is lowered. The concrete amount is
* decided by the scoring scheme.
*/
MAX_POINTS_DECREASE_MAX_SCORE,
/**
* Unknown information scoring type - should not be used except
* for tests
*/
UNKNOWN
}
@@ -0,0 +1,44 @@
package com.sap.sailing.domain.racelog.scoring.impl;
import java.io.Serializable;
import java.util.List;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
public class AdditionalScoringInformationEventImpl extends RaceLogEventImpl implements AdditionalScoringInformationEvent {
private static final long serialVersionUID = -7627714111951381979L;
private final AdditionalScoringInformationType informationType;
public AdditionalScoringInformationEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint,
Serializable pId, List<Competitor> competitors, int pPassId, AdditionalScoringInformationType informationType) {
super(createdAt, author, logicalTimePoint, pId, competitors, pPassId);
this.informationType = informationType;
}
@Override
public void accept(RaceLogEventVisitor visitor) {
visitor.visit(this);
}
@Override
public String getShortInfo() {
return "UUID: " + getId() + " Type: " + getType().name();
}
@Override
public String toString() {
return super.toString() + " Type: " + getType().name();
}
@Override
public AdditionalScoringInformationType getType() {
return informationType;
}
}
@@ -8,6 +8,8 @@ import com.sap.sailing.domain.racelog.CompetitorResults;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure;
import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite;
import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedurePrerequisiteAutoResolver;
@@ -138,5 +140,13 @@ public interface RaceState extends ReadonlyRaceState {
* Enters a new wind fix for this race.
*/
void setWindFix(TimePoint now, Wind wind);
/**
* Marks this race state with a new {@link AdditionalScoringInformationEvent} or revokes
* an already existing one.
*/
void setAdditionalScoringInformationEnabled(TimePoint creationTimePoint, boolean enable, AdditionalScoringInformationType informationType);
boolean isAdditionalScoringInformationEnabled(AdditionalScoringInformationType informationType);
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.racelog.state.impl;
import java.util.UUID;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.configuration.ConfigurationLoader;
import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
@@ -8,11 +10,15 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException;
import com.sap.sailing.domain.racelog.CompetitorResults;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogEventFactory;
import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.state.RaceState;
import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure;
import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory;
@@ -51,7 +57,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
private RaceStateImpl(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory eventFactory, RaceStatusAnalyzer.Clock analyzersClock,
RacingProcedureFactory procedureFactory) {
super(raceLog, analyzersClock, procedureFactory);
super(raceLog, analyzersClock, procedureFactory, /* update */ true);
this.author = author;
this.factory = eventFactory;
}
@@ -158,4 +164,29 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
raceLog.add(factory.createWindFixEvent(timePoint, author, raceLog.getCurrentPassId(), wind));
}
@Override
public void setAdditionalScoringInformationEnabled(TimePoint timePoint, boolean enable, AdditionalScoringInformationType informationType) {
final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(/*filterBy*/informationType);
if (enable) {
if (event == null) {
raceLog.add(factory.createAdditionalScoringInformationEvent(timePoint, UUID.randomUUID(), author, raceLog.getCurrentPassId(), informationType));
}
} else {
if (event != null) {
try {
// revoke the newest one
raceLog.revokeEvent(author, event);
} catch (NotRevokableException e) {
e.printStackTrace();
}
}
}
}
@Override
public boolean isAdditionalScoringInformationEnabled(AdditionalScoringInformationType informationType) {
final AdditionalScoringInformationEvent event = new AdditionalScoringInformationFinder(raceLog).analyze(/*filterBy*/informationType);
return event != null;
}
}
@@ -22,7 +22,7 @@ import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer;
import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer.Clock;
import com.sap.sailing.domain.racelog.analyzing.impl.RacingProcedureTypeAnalyzer;
import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder;
import com.sap.sailing.domain.racelog.impl.RaceLogChangedVisitor;
import com.sap.sailing.domain.racelog.impl.WeakRaceLogChangedVisitor;
import com.sap.sailing.domain.racelog.state.RaceStateChangedListener;
import com.sap.sailing.domain.racelog.state.RaceStateEvent;
import com.sap.sailing.domain.racelog.state.RaceStateEventScheduler;
@@ -105,10 +105,15 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private Wind cachedWindFix;
private ReadonlyRaceStateImpl(RaceLog raceLog, RacingProcedureFactory procedureFactory) {
this(raceLog, new RaceStatusAnalyzer.StandardClock(), procedureFactory);
this(raceLog, new RaceStatusAnalyzer.StandardClock(), procedureFactory, /* update */ true);
}
protected ReadonlyRaceStateImpl(RaceLog raceLog, Clock analyzersClock, RacingProcedureFactory procedureFactory) {
/**
* @param update
* if <code>true</code>, the race state will be updated whenever the underlying <code>raceLog</code>
* changes.
*/
protected ReadonlyRaceStateImpl(RaceLog raceLog, Clock analyzersClock, RacingProcedureFactory procedureFactory, boolean update) {
this.raceLog = raceLog;
this.procedureFactory = procedureFactory;
this.changedListeners = new RaceStateChangedListeners();
@@ -128,9 +133,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
this.cachedRacingProcedureType = determineInitialProcedureType();
this.cachedRaceStatus = RaceLogRaceStatus.UNKNOWN;
this.cachedPassId = raceLog.getCurrentPassId();
this.raceLog.addListener(new RaceLogChangedVisitor(this));
// TODO bug 2083: make sure the listener registration is "weak" in the sense that it is removed when this race state is no longer strongly referenced
this.raceLog.addListener(new WeakRaceLogChangedVisitor(this.raceLog, this));
// We known that recreateRacingProcedure calls update() when done, therefore this RaceState
// will be fully initialized after this line
recreateRacingProcedure();
@@ -30,4 +30,9 @@ public class RevokeEventImpl extends RaceLogEventImpl implements RevokeEvent {
public Serializable getRevokedEventId() {
return revokedEventId;
}
@Override
public String getShortInfo() {
return super.getShortInfo() + "Revoked: " + revokedEventId;
}
}
@@ -110,7 +110,7 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
* Callers that want to iterate over the collection returned need to synchronize on <code>this</code> object to
* avoid {@link ConcurrentModificationException}s.
*
* @return the smoothened fixes; this implementation simply delegates to {@link #getInternalRawFixes()} because for
* @return the smoothened fixes ordered by their time points; this implementation simply delegates to {@link #getInternalRawFixes()} because for
* only {@link Timed} fixes we can't know how to remove outliers. Subclasses that constrain the
* <code>FixType</code> may provide smoothening implementations.
*/
@@ -11,6 +11,23 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.sap.sse.common.Named;
/**
* To be used in conjunction with {@link LockUtil}. Provides additional naming
* for locks which is helpful in the tracing that {@link LockUtil} provides.
* Use as in
* <pre>
* NamedReentrantReadWriteLock lock = ...;
* LockUtil.lockForRead(lock);
* try {
* // do something while holding the lock
* } finally {
* LockUtil.unlockAfterRead(lock);
* }
* </pre>
*
* @author Axel Uhl (d043530)
*
*/
public class NamedReentrantReadWriteLock extends ReentrantReadWriteLock implements Named {
private static final long serialVersionUID = 2906084982209339774L;
private final String name;
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.swisstimingreplayadapter.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -25,6 +24,7 @@ import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayListener
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayRace;
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.util.ByteArrayOutputStreamWithVisibleBuffer;
public class SwissTimingReplayServiceImpl implements SwissTimingReplayService {
@@ -119,12 +119,6 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService {
return startTimeFormat;
}
private static class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream {
public byte[] getBuffer() {
return buf;
}
}
@Override
public void loadRaceData(String link, SwissTimingReplayListener replayListener) {
final URL raceDataUrl;
@@ -1,2 +1,2 @@
#!/bin/sh
#!/bin/bash
java -Dfile.encoding=en_US.UTF-8 -cp bin/com.sap.sailing.domain.test-1.0.0-SNAPSHOT.jar:../com.sap.sailing.domain.tractracadapter/bin/com.sap.sailing.domain.tractracadapter-1.0.0-SNAPSHOT.jar:../com.sap.sailing.domain/bin/com.sap.sailing.domain-1.0.0-SNAPSHOT.jar:../com.tractrac.clientmodule/bin/com.tractrac.clientmodule-1.0.0-SNAPSHOT.jar:../com.tractrac.clientmodule/lib/TracTracClientModule.jar:../com.googlecode.java-diff-utils/bin/com.googlecode.java-diff-utils-1.3.0-SNAPSHOT.jar com.sap.sailing.domain.test.UnicodeCharactersInCompetitorNamesTest
@@ -14,8 +14,11 @@ import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
@@ -72,6 +75,39 @@ public class LeaderboardDTODiffingTest {
}
return null;
}
@Test
public void testEmptyLegDetailsGetFilled() throws FileNotFoundException, IOException, ClassNotFoundException {
// remove legDetails for R9 for all competitors from previousVersion; remember which ones did contain data
Set<CompetitorDTO> hadLegDetailsInR9 = new HashSet<>();
Map<CompetitorDTO, LeaderboardRowDTO> newRows = new HashMap<>();
for (CompetitorDTO competitor : previousVersion.rows.keySet()) {
LeaderboardRowDTO competitorRow = new LeaderboardRowDTO();
cloner.clone(newVersion.rows.get(competitor), competitorRow);
Map<String, LeaderboardEntryDTO> newFieldsByRaceColumnName = new HashMap<>();
for (Entry<String, LeaderboardEntryDTO> e : competitorRow.fieldsByRaceColumnName.entrySet()) {
LeaderboardEntryDTO newEntry = new LeaderboardEntryDTO();
cloner.clone(e.getValue(), newEntry);
newFieldsByRaceColumnName.put(e.getKey(), newEntry);
}
competitorRow.fieldsByRaceColumnName = newFieldsByRaceColumnName;
newRows.put(competitor, competitorRow); // use a copy of the row so we can null out legDetails for R9 in previousVersion only
LeaderboardRowDTO previousRow = previousVersion.rows.get(competitor);
assertNotSame(previousRow, competitorRow);
if (previousRow.fieldsByRaceColumnName.get("R9").legDetails != null) {
hadLegDetailsInR9.add(competitor);
previousRow.fieldsByRaceColumnName.get("R9").legDetails = null;
assertNotNull(competitorRow.fieldsByRaceColumnName.get("R9").legDetails != null);
}
}
newVersion.rows = newRows;
newVersion.strip(previousVersion); // should contain the leg details now
LeaderboardDTO applied = newVersion.getLeaderboardDTO(previousVersion);
// now applied must contain the leg details
for (CompetitorDTO competitor : hadLegDetailsInR9) {
assertNotNull(applied.rows.get(competitor).fieldsByRaceColumnName.get("R9").legDetails);
}
}
@Test
public void testLeaderboardSuccessfullyRead() {
@@ -14,7 +14,6 @@ import java.util.logging.Logger;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.WindSourceType;
@@ -27,8 +26,8 @@ import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.domain.tractracadapter.ReceiverType;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
@@ -44,24 +43,23 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
public void leaderboardWithOneRaceTest() throws URISyntaxException, NoWindException, IOException, InterruptedException, SubscriberInitializationException, CreateModelException {
leaderboard = new FlexibleLeaderboardImpl("Kiel Week 2011 505s", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }),
new LowPoint(), null);
Fleet defaultFleet = leaderboard.getFleet(null);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
loadRace("event_20110609_KielerWoch-505_Race_2.txt", "event_20110609_KielerWoch-505_Race_2.mtb"); // 505 Race 2
Competitor kevin = getCompetitorByName("Kevin");
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */ false, defaultFleet);
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */false);
assertEquals(17, leaderboard.getTotalPoints(kevin, now), 0.000000001);
Util.Pair<Competitor, RaceColumn> key = new Util.Pair<Competitor, RaceColumn>(kevin, column);
assertEquals(17, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001);
assertEquals(17, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001);
loadRace("event_20110609_KielerWoch-505_Race_3.txt", "event_20110609_KielerWoch-505_Race_3.mtb"); // 505 Race 3
column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */ false, defaultFleet);
column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */false);
key = new Util.Pair<Competitor, RaceColumn>(kevin, column);
// In Race 3, Hasso ranked 31st
assertEquals(29, leaderboard.getTotalPoints(kevin, now), 0.000000001);
assertEquals(12, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001);
assertEquals(12, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001);
loadRace("event_20110609_KielerWoch-505_race_4.txt", "event_20110609_KielerWoch-505_race_4.mtb"); // 505 Race 4
column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */ false, defaultFleet);
column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */false);
key = new Util.Pair<Competitor, RaceColumn>(kevin, column);
// now the first race is discarded because Kevin ranked worst compared to the other two; in race 4 he ranked 10th
assertEquals(22, leaderboard.getTotalPoints(kevin, now), 0.000000001);
@@ -102,12 +102,11 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
public void ensureMedalRaceParamIsIgnoredIfRaceColumnAlreadyExists() {
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(
new int[] { 5, 8 }), new LowPoint(), null);
Fleet defaultFleet = leaderboard.getFleet(null);
final String columnName = "abc";
setupRaces(1, 0);
leaderboard.addRaceColumn(columnName, /* medalRace */ true);
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet);
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false);
assertTrue(leaderboard.getRaceColumnByName(columnName).isMedalRace());
}
@@ -119,7 +118,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
final String columnName = "abc";
setupRaces(1, 0);
leaderboard.addRaceColumn(columnName, /* medalRace */ true);
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet);
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */false);
final Set<String> emptySet = Collections.emptySet();
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
final TrackedRegattaRegistry trackedRegattaRegistry = new TrackedRegattaRegistry() {
@@ -180,7 +179,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
RaceColumn bestScoringRaceColumn = null;
for (TrackedRace race : testRaces) {
i++;
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet);
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */false);
raceColumnsInLeaderboard.put(race, raceColumn);
if (race.getRank(competitor) < bestScore) {
bestScore = race.getRank(competitor);
@@ -207,8 +206,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
testRace.addCompetitor(c3); // this makes maxPoints==4
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(
new int[] { 2 }), new LowPoint(), null);
Fleet defaultFleet = leaderboard.getFleet(null);
leaderboard.addRace(testRace, "R1", /* medalRace */ false, defaultFleet);
leaderboard.addRace(testRace, "R1", /* medalRace */false);
assertEquals(1., leaderboard.getTotalPoints(competitor, now), 0.00000001);
assertEquals(1., leaderboard.getTotalPoints(c2, now), 0.00000001);
assertEquals(1., leaderboard.getTotalPoints(c3, now), 0.00000001);
@@ -235,10 +233,9 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
testRace.addCompetitor(c3); // this makes maxPoints==4
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(
new int[] { 2 }), new LowPoint(), null);
Fleet defaultFleet = leaderboard.getFleet(null);
RaceColumn r1 = leaderboard.addRace(testRace, "R1", /* medalRace */ false, defaultFleet);
RaceColumn r1 = leaderboard.addRace(testRace, "R1", /* medalRace */false);
raceColumnsInLeaderboard.put(testRace, r1);
RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */ false, defaultFleet);
RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */false);
leaderboard.getScoreCorrection().setMaxPointsReason(competitor, r2, MaxPointsReason.DND); // non-discardable disqualification
// assert that max points were given before discarding...
assertEquals(4.0, leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001);
@@ -268,7 +265,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
RaceColumn bestScoringRaceColumn = null;
for (TrackedRace race : testRaces) {
i++;
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet);
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */false);
raceColumnsInLeaderboard.put(race, raceColumn);
if (race.getRank(competitor) < bestScore) {
bestScore = race.getRank(competitor);
@@ -294,8 +291,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
for (TrackedRace race : testRaces) {
i++;
raceColumnsInLeaderboard.put(race, leaderboard.addRace(race, "Test Race "+i,
/* medalRace */ numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size(),
defaultFleet));
/* medalRace */numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size()));
}
// add a few race columns not yet connected to a tracked race
for (int j=0; j<numberOfUntrackedRaces; j++) {
@@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.ArrayList;
import java.util.Arrays;
@@ -15,6 +16,8 @@ import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
@@ -26,7 +29,9 @@ import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
@@ -37,7 +42,9 @@ import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.racelog.tracking.NotRevokableException;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
@@ -46,14 +53,25 @@ import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10Or8AndLastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sailing.domain.racelog.scoring.impl.AdditionalScoringInformationEventImpl;
import com.sap.sailing.domain.racelog.state.RaceState;
import com.sap.sailing.domain.racelog.state.impl.RaceStateImpl;
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
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.TimedComparator;
import com.sap.sailing.util.impl.ArrayListNavigableSet;
@@ -791,6 +809,84 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1);
}
@Test
public void testAdditionalScoreInformationLeadsToChangedScoreForOneColumn() throws NoWindException {
TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class);
Regatta dummyRegatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE , "Dummy", new BoatClassImpl("Extreme40", false), trackedRegattaRegistry, new HighPointFirstGets10Or8AndLastBreaksTie(), "578876345345", new CourseAreaImpl("Humba", UUID.randomUUID()));
trackedRegattaRegistry.getOrCreateTrackedRegatta(dummyRegatta);
Competitor[] competitors = createCompetitors(10).toArray(new Competitor[0]);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
FlexibleLeaderboard leaderboardHighPoint10Or8AndLastBreaksTie = new FlexibleLeaderboardImpl("Test ESS Highpoint 10Or8AndLastBreaksTie", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPointFirstGets10Or8AndLastBreaksTie(), null);
leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R1", /* medalRace */ false);
assertTrue(leaderboardHighPoint10Or8AndLastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3
assertEquals(10, leaderboardHighPoint10Or8AndLastBreaksTie.getCompetitorsFromBestToWorst(later).size());
assertEquals(new Double(10), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(9), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later));
assertEquals(new Double(1), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R2", /* medalRace */ false);
assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later));
assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
RaceLog raceLogForRace2 = leaderboardHighPoint10Or8AndLastBreaksTie.getRaceColumnByName("R2").getRaceLog(leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null));
raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later, "12345678", Collections.<Competitor>emptyList(), 0, AdditionalScoringInformationType.UNKNOWN));
assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later));
assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
raceLogForRace2.add(new AdditionalScoringInformationEventImpl(now, new RaceLogEventAuthorImpl("Plopp", 1), later.plus(Duration.ONE_MINUTE), "123456789873773762", Collections.<Competitor>emptyList(), 0, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE));
assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(16), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later));
assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R3", /* medalRace */ false);
assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(25), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later));
assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLogForRace2);
AdditionalScoringInformationEvent event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE);
assert event != null;
try {
raceLogForRace2.revokeEvent(new RaceLogEventAuthorImpl("Plopp", 1), event);
} catch (NotRevokableException e) {
e.printStackTrace();
}
event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE);
assert event == null;
assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
RaceState raceStateForRace2 = RaceStateImpl.create(raceLogForRace2, new RaceLogEventAuthorImpl("Simon", 1));
assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE));
raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE);
assertTrue(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE));
assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(10000)), /*enable*/false, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE);
assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE));
assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later));
assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later));
LeaderboardDTO leaderboardDTO = null;
try {
leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.<String>emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
assert leaderboardDTO != null;
assertEquals(30.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001);
raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(20000)), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE);
try {
leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.<String>emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
assertEquals(28.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001);
assertEquals(25.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(1)).totalPoints, 0.000000000001);
}
@Test
public void testCompetitorsRankedEleventhOrLowerGetOnePointScore() throws NoWindException {
Competitor[] competitors = createCompetitors(16).toArray(new Competitor[0]);
@@ -798,8 +894,9 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
FlexibleLeaderboard leaderboardHighPoint10LastBreaksTie = new FlexibleLeaderboardImpl("Test ESS Highpoint 10LastBreaksTie", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPointFirstGets10LastBreaksTie(), null);
leaderboardHighPoint10LastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false,
leaderboardHighPoint10LastBreaksTie.getFleet(null));
leaderboardHighPoint10LastBreaksTie
.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */
false);
assertTrue(leaderboardHighPoint10LastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3
assertEquals(16, leaderboardHighPoint10LastBreaksTie.getCompetitorsFromBestToWorst(later).size());
@@ -808,8 +905,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
// Normal HighPoint leaderboard has no max so that winner gets as many points as there are competitors
FlexibleLeaderboard leaderboardHighPoint = new FlexibleLeaderboardImpl("Test ESS Highpoint", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPoint(), null);
leaderboardHighPoint.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false,
leaderboardHighPoint.getFleet(null));
leaderboardHighPoint.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */
false);
assertEquals(16, leaderboardHighPoint.getCompetitorsFromBestToWorst(later).size());
assertEquals(new Double(16), leaderboardHighPoint.getTotalPoints(competitors[0], later));
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("ESS", "ESS", /* displayGroupsInReverseOrder */ false,
@@ -847,26 +944,26 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Leaderboard 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPoint(), null);
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f1)), "R1", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R2", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f1)), "R1", /* medalRace */
false);
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R2", /* medalRace */
false);
assertTrue(leaderboard1.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
leaderboard1.getTotalPoints(c[0], later), leaderboard1.getTotalPoints(c[3], later)) < 0); // c0 better than c3
FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Leaderboard 3", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPoint(), null);
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f3)), "R1", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f4)), "R2", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f3)), "R1", /* medalRace */
false);
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f4)), "R2", /* medalRace */
false);
assertTrue(leaderboard2.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
leaderboard2.getTotalPoints(c[3], later), leaderboard2.getTotalPoints(c[0], later)) < 0); // c3 better than c0
FlexibleLeaderboard leaderboard3 = new FlexibleLeaderboardImpl("Leaderboard 3", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPoint(), null);
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f5)), "R1", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f6)), "R2", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f5)), "R1", /* medalRace */
false);
leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f6)), "R2", /* medalRace */
false);
assertTrue(leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[3]) <
leaderboard3.getCompetitorsFromBestToWorst(later).indexOf(c[0])); // c3 better than c0; won last race
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", false, Arrays.asList(leaderboard1,
@@ -896,18 +993,18 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Leaderboard 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPointFirstGets10LastBreaksTie(), null);
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_1)), "R1", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_1)), "R2", /* medalRace */ false,
leaderboard1.getFleet(null));
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_1)), "R1", /* medalRace */
false);
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_1)), "R2", /* medalRace */
false);
FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Leaderboard 2", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPointFirstGets10LastBreaksTie(), null);
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_2)), "R1", /* medalRace */ false,
leaderboard2.getFleet(null));
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_2)), "R2", /* medalRace */ false,
leaderboard2.getFleet(null));
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace3_2)), "R3", /* medalRace */ false,
leaderboard2.getFleet(null));
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace1_2)), "R1", /* medalRace */
false);
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace2_2)), "R2", /* medalRace */
false);
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(regattaRace3_2)), "R3", /* medalRace */
false);
// [C2, C3, C4, C5, C6, C7, C1, C11, C8, C9, C10, C12]
// [29, 25, 22, 19, 16, 12, 13, 11, 10, 7, 4, 3]
List<Competitor> rankedCompetitorsInLeaderboard = leaderboard2.getCompetitorsFromBestToWorst(later);
@@ -958,9 +1055,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
public TimePoint getEndOfRace() {
return endOfR1;
}
}, "R1", /* medalRace */ false,
leaderboard1.getFleet(null));
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */ false, new Fleet[] { new FleetImpl("Default Fleet") });
}, "R1", /* medalRace */false);
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false);
leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.);
assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column
assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column
@@ -986,15 +1082,13 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
public TimePoint getEndOfRace() {
return endOfR1;
}
}, "R1", /* medalRace */ false,
leaderboard1.getFleet(null));
}, "R1", /* medalRace */false);
RaceColumn r2 = leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(afterEndOfR1, Arrays.asList(c2)) {
private static final long serialVersionUID = 8705622361027154428L;
public TimePoint getEndOfRace() {
return afterEndOfR1;
}
}, "R2", /* medalRace */ false,
leaderboard1.getFleet(null));
}, "R2", /* medalRace */false);
leaderboard1.getScoreCorrection().correctScore(c1[0], r2, .9);
assertEquals(1. + .9, leaderboard1.getTotalPoints(c1[0], afterEndOfR1), 0.00000001);
assertEquals(1, leaderboard1.getTotalRankOfCompetitor(c1[0], afterEndOfR1));
@@ -1034,8 +1128,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
};
trackedRace.getRace().getCourse().addWaypoint(0, start);
trackedRace.getRace().getCourse().addWaypoint(1, finish);
RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */ false, leaderboard1.getFleet(null));
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */ false, new Fleet[] { new FleetImpl("Default Fleet") });
RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */false);
RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false);
leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.);
assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column
assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column
@@ -1,6 +1,5 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -11,7 +10,6 @@ import org.junit.Test;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
@@ -76,18 +74,15 @@ public class RaceLogAttachOnTrackedRaceTest {
}
@Test
public void testAttachSpecificFleets() {
Fleet[] fleets = new Fleet[] { new FleetImpl("A"), new FleetImpl("B") };
Fleet aFleet = fleets[0];
Fleet bFleet = fleets[1];
RaceColumn column = leaderboard.addRaceColumn("R1", false, fleets);
public void testAttachToDefaultFleet() {
RaceColumn column = leaderboard.addRaceColumn("R1", false);
TrackedRace trackedRace = new MyMockedTrackedRace();
column.setTrackedRace(aFleet, trackedRace);
Fleet defaultFleet = leaderboard.getFleet(null);
column.setTrackedRace(defaultFleet, trackedRace);
assertSame(column.getRaceLog(aFleet), trackedRace.getRaceLog(column.getRaceLogIdentifier(aFleet)));
assertNotSame(column.getRaceLog(bFleet), trackedRace.getRaceLog(column.getRaceIdentifier(bFleet)));
assertSame(column.getRaceLog(defaultFleet), trackedRace.getRaceLog(column.getRaceLogIdentifier(defaultFleet)));
}
@Test
@@ -0,0 +1,51 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.fail;
import java.util.Timer;
import java.util.TimerTask;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
/**
* Tests around the Timer class; e.g., does it kill a timer if a task throws an exception?
*
* @author Axel Uhl (d043530)
*
*/
public class TimerTest {
@Rule
public Timeout AbstractTracTracLiveTestTimeout = new Timeout(10 * 1000);
@Test
public void exceptionInTask() throws InterruptedException {
Timer t = new Timer("Test");
final TimerTask taskThrowingException = new TimerTask() {
@Override
public void run() {
throw new RuntimeException("An exception that may or may not kill the timer");
}
};
t.schedule(taskThrowingException, 1l);
try {
for (int i = 0; i < 100; i++) {
Thread.sleep(10);
final boolean[] result = new boolean[1];
t.schedule(new TimerTask() {
@Override
public void run() {
synchronized (result) {
result[0] = true;
result.notifyAll();
}
}
}, 1l);
}
fail("Expected IllegalStateException because task terminating abnormally cancels the timer");
} catch (IllegalStateException e) {
// expected
}
}
}
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
@@ -59,7 +60,7 @@ public abstract class TrackBasedTest {
}
public static CompetitorImpl createCompetitor(String competitorName) {
return new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton(
return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton(
new PersonImpl(competitorName, new NationalityImpl("GER"),
/* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas",
new NationalityImpl("NED"),
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.test.mock;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.TreeSet;
@@ -617,14 +618,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public Iterable<WindSource> getWindSources(WindSourceType type) {
// TODO Auto-generated method stub
return null;
return Collections.emptyList();
}
@Override
public Iterable<WindSource> getWindSources() {
// TODO Auto-generated method stub
return null;
return Collections.emptyList();
}
@Override
@@ -66,6 +66,15 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
public Serializable getId() {
return null;
}
@Override
public Competitor getCompetitorById(Serializable competitorID) {
if (competitorID.equals(MockedTrackedRaceWithFixedRank.this.competitor.getId())) {
return MockedTrackedRaceWithFixedRank.this.competitor;
} else {
return null;
}
}
}
@Override
@@ -32,6 +32,16 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
public Iterable<Competitor> getCompetitors() {
return competitors;
}
@Override
public Competitor getCompetitorById(Serializable competitorID) {
for (Competitor competitor : competitors) {
if (competitorID.equals(competitor.getId())) {
return competitor;
}
}
return null;
}
@Override
public BoatClass getBoatClass() {
return null;
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.Waypoint;
@@ -43,11 +44,12 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sse.common.Util;
/**
@@ -62,6 +64,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
private static final long serialVersionUID = 2708044935347796930L;
private final TimePoint startTime;
private final List<Competitor> competitorsFromBestToWorst;
private final Regatta regatta;
private RaceDefinition race;
/**
@@ -70,7 +73,11 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
* mocked tracked race
*/
public MockedTrackedRaceWithStartTimeAndRanks(TimePoint startTime, List<Competitor> competitorsFromBestToWorst) {
super();
this(startTime, competitorsFromBestToWorst, null);
}
public MockedTrackedRaceWithStartTimeAndRanks(TimePoint startTime, List<Competitor> competitorsFromBestToWorst, Regatta regatta) {
this.regatta = regatta;
this.startTime = startTime;
// copies the list to make sure that later modifications to the list passed to this constructor don't affect the ranking produced by this race
this.competitorsFromBestToWorst = new ArrayList<Competitor>(competitorsFromBestToWorst);
@@ -226,14 +233,12 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
@Override
public Iterable<WindSource> getWindSources(WindSourceType type) {
// TODO Auto-generated method stub
return null;
return Collections.emptyList();
}
@Override
public Iterable<WindSource> getWindSources() {
// TODO Auto-generated method stub
return null;
return Collections.emptyList();
}
@Override
@@ -328,8 +333,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
@Override
public TrackedRegatta getTrackedRegatta() {
// TODO Auto-generated method stub
return null;
return new DynamicTrackedRegattaImpl(regatta);
}
@Override
@@ -46,8 +46,8 @@ import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
import com.tractrac.model.lib.api.ModelLocator;
import com.sap.sse.common.Util;
import com.tractrac.model.lib.api.ModelLocator;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.model.lib.api.event.ICompetitor;
import com.tractrac.model.lib.api.event.IEvent;
@@ -62,6 +62,7 @@ import com.tractrac.subscription.lib.api.competitor.ICompetitorsListener;
import com.tractrac.subscription.lib.api.event.IConnectionStatusListener;
import com.tractrac.subscription.lib.api.event.ILiveDataEvent;
import com.tractrac.subscription.lib.api.event.IStoredDataEvent;
import com.tractrac.subscription.lib.api.race.IRacesListener;
public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements IConnectionStatusListener, TracTracRaceTracker, DynamicRaceDefinitionSet {
private static final Logger logger = Logger.getLogger(TracTracRaceTrackerImpl.class.getName());
@@ -343,6 +344,25 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
public void addCompetitor(ICompetitor competitor) {
}
});
eventSubscriber.subscribeRaces(new IRacesListener() {
@Override public void abandonRace(UUID raceId) {}
@Override public void addRace(IRace race) {}
@Override public void deleteRace(UUID raceId) {}
@Override public void reloadRace(UUID raceId) {}
@Override public void startTracking(UUID raceId) {}
@Override
public void updateRace(IRace race) {
if (Util.equalsWithNull(race, TracTracRaceTrackerImpl.this.tractracRace)) {
int delayToLiveInMillis = race.getLiveDelay()*1000;
for (RaceDefinition raceDefinition : getRaces()) {
DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(raceDefinition);
if (trackedRace != null) {
trackedRace.setDelayToLiveInMillis(delayToLiveInMillis);
}
}
}
}
});
// Start live and stored data streams
Regatta effectiveRegatta = regatta;
raceSubscriber = subscriberFactory.createRaceSubscriber(tractracRace, liveURI, storedURI);
@@ -57,6 +57,7 @@ public class UpdateHandler {
this.raceId = raceId;
this.updateDeserializer = new UpdateResponseDeserializer();
if (updateURI != null && !updateURI.toString().equals("")) {
logger.info("Activating TracTrac update handler "+this+" for race with ID "+raceId);
this.active = true;
} else {
this.active = false;
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.base;
import java.io.Serializable;
import com.sap.sailing.domain.common.WithID;
import com.sap.sse.common.Named;
@@ -21,4 +23,6 @@ public interface RaceDefinition extends Named, WithID {
Course getCourse();
Iterable<Competitor> getCompetitors();
Competitor getCompetitorById(Serializable competitorID);
}
@@ -20,6 +20,7 @@ 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.TimePoint;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.PlacemarkDTO;
@@ -32,9 +33,11 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10Or8AndLastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets1LastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive;
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFiveIgnoringRaceCount;
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
@@ -90,8 +93,12 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
return new LowPointWinnerGetsZero();
case HIGH_POINT_WINNER_GETS_FIVE:
return new HighPointWinnerGetsFive();
case HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT:
return new HighPointWinnerGetsFiveIgnoringRaceCount();
case HIGH_POINT_WINNER_GETS_SIX:
return new HighPointWinnerGetsSix();
case HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT:
return new HighPointFirstGets10Or8AndLastBreaksTie();
}
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
}
@@ -137,6 +144,7 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
trackedRaceDTO.timePointOfNewestEvent = trackedRace.getTimePointOfNewestEvent() == null ? null : trackedRace.getTimePointOfNewestEvent().asDate();
trackedRaceDTO.hasWindData = trackedRace.hasWindData();
trackedRaceDTO.hasGPSData = trackedRace.hasGPSData();
trackedRaceDTO.hasMeasuredWindData = Util.size(trackedRace.getWindSources(WindSourceType.EXPEDITION)) != 0;
trackedRaceDTO.delayToLiveInMs = trackedRace.getDelayToLiveInMillis();
return trackedRaceDTO;
}
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.RevokeEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.racelog.tracking.DefineMarkEvent;
import com.sap.sailing.domain.racelog.tracking.DenoteForTrackingEvent;
@@ -150,4 +151,9 @@ public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Seriali
public void visit(CloseOpenEndedDeviceMappingEvent event) {
notifyOnAdd(event);
}
@Override
public void visit(AdditionalScoringInformationEvent additionalScoringInformation) {
notifyOnAdd(additionalScoringInformation);
}
}
@@ -1,8 +1,7 @@
package com.sap.sailing.domain.base.impl;
import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.LinkedHashMap;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
@@ -13,7 +12,7 @@ import com.sap.sailing.domain.common.impl.NamedImpl;
public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
private static final long serialVersionUID = -1900955198751393727L;
private final Course course;
private final Iterable<Competitor> competitors;
private final LinkedHashMap<Serializable, Competitor> competitorsById;
private final BoatClass boatClass;
private final Serializable id;
@@ -26,11 +25,13 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
assert name != null;
this.course = course;
Set<Competitor> competitorsAsLinkedHashSet = new LinkedHashSet<Competitor>();
this.competitorsById = new LinkedHashMap<>();
for (Competitor competitor : competitors) {
competitorsAsLinkedHashSet.add(competitor);
Competitor competitorWithEqualID = competitorsById.put(competitor.getId(), competitor);
if (competitorWithEqualID != null && competitorWithEqualID != competitor) {
throw new IllegalArgumentException("Two distinct competitors with equal ID "+competitor.getId()+" are not allowed within the single race "+name);
}
}
this.competitors = competitorsAsLinkedHashSet;
this.boatClass = boatClass;
this.id = id;
}
@@ -47,7 +48,12 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
@Override
public Iterable<Competitor> getCompetitors() {
return competitors;
return competitorsById.values();
}
@Override
public Competitor getCompetitorById(Serializable competitorID) {
return competitorsById.get(competitorID);
}
@Override
@@ -35,36 +35,32 @@ public interface FlexibleLeaderboard extends Leaderboard, Renamable {
void moveRaceColumnDown(String name);
/**
* Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard.
* Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard. The default
* fleet will be used.
*
* @param name
* the name for the new race column such that none of the columns in {@link #getRaceColumns()}
* has that name yet; otherwise, an message will be logged, no race column will be added and
* the existing column will be returned for robustness reasons
* the name for the new race column such that none of the columns in {@link #getRaceColumns()} has that
* name yet; otherwise, an message will be logged, no race column will be added and the existing column
* will be returned for robustness reasons
* @param medalRace
* tells if the column to add represents a medal race which has double score and cannot be discarded
* @param fleets
* the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default
* fleet will be assigned to the race column created.
* @return the race column in the leaderboard used to represent the tracked <code>race</code>
*/
RaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets);
RaceColumn addRaceColumn(String name, boolean medalRace);
/**
* Adds a tracked race to this leaderboard. If a {@link RaceColumn} with name <code>columnName</code> already exists
* in this leaderboard, <code>race</code> is {@link RaceColumn#setTrackedRace(Fleet, TrackedRace) set as its tracked
* race} and <code>medalRace</code> is ignored. Otherwise, a new {@link RaceColumn} column, with <code>race</code>
* as its tracked race, is created and added to this leaderboard.
* as its tracked race, is created and added to this leaderboard. The default fleet will be used.
*
* @param medalRace
* tells if the column to add represents a medal race which has double score and cannot be discarded;
* ignored if the column named <code>columnName</code> already exists
* @param fleets
* the fleets to add to the {@link RaceColumn} created. If no fleets are specified, a single default
* fleet will be assigned to the race column created.
*
* @return the race column in the leaderboard used to represent the tracked <code>race</code>
*/
RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet);
RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace);
void removeRaceColumn(String columnName);
@@ -70,7 +70,7 @@ public class LeaderboardCacheManager {
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
removeFromCache(leaderboard);
}
@@ -128,7 +128,6 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
*/
@Override
public int compareByBetterScore(List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o1Scores, List<com.sap.sse.common.Util.Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter) {
assert o1Scores.size() == o2Scores.size();
final Comparator<Double> pureScoreComparator = getScoreComparator(nullScoresAreBetter);
// needs to compare net points; therefore, divide the total points by the column factor for comparison:
List<Double> o1NetScores = new ArrayList<>();
@@ -148,6 +147,11 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) {
result = pureScoreComparator.compare(o1Iter.next(), o2Iter.next());
}
if (o1Iter.hasNext() != o2Iter.hasNext()) {
// if, as may be allowed by some scoring scheme variants, competitors with different numbers of scored races are compared
// and are equal for all races of the competitor who scored fewer races, the competitor who scored more races is preferred
result = o1Iter.hasNext() ? -1 : 1;
}
return result;
}
@@ -68,6 +68,7 @@ import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
import com.sap.sailing.domain.racelog.InvalidatesLeaderboardCache;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.tracking.GPSFix;
@@ -83,8 +84,8 @@ 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.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.util.impl.LockUtil;
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
import com.sap.sailing.util.impl.RaceColumnListeners;
@@ -312,7 +313,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
invalidateCacheAndRemoveThisListenerFromTrackedRace();
}
@@ -1215,6 +1216,13 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
if (event instanceof InvalidatesLeaderboardCache) {
// make sure to invalidate the cache as this event indicates that
// it changes values the cache could still hold
if (leaderboardDTOCache != null) {
leaderboardDTOCache.invalidate(this);
}
}
}
@Override
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.leaderboard.impl;
import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory;
import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
public class DefaultFleetImpl extends FleetImpl implements IsManagedBySharedDomainFactory {
private static final long serialVersionUID = 6233489616955255401L;
public DefaultFleetImpl() {
super(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
}
@Override
public IsManagedBySharedDomainFactory resolve(SharedDomainFactory domainFactory) {
return FlexibleLeaderboardImpl.defaultFleet;
}
}
@@ -13,8 +13,6 @@ import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
@@ -39,7 +37,7 @@ import com.sap.sailing.domain.tracking.TrackedRace;
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
protected static final Fleet defaultFleet = new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
private static final long serialVersionUID = -5708971849158747846L;
private final List<FlexibleRaceColumn> races;
private final ScoringScheme scoringScheme;
@@ -96,24 +94,25 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
}
@Override
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet) {
FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */ false, fleet);
column.setTrackedRace(fleet, race); // triggers listeners because this object was registered above as race column listener on the column
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
// race column listener on the column
return column;
}
@Override
public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets) {
return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */ true, fleets);
public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
}
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn, Fleet... fleets) {
private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
FlexibleRaceColumn column = getRaceColumnByName(name);
if (column != null) {
final String msg = "Trying to create race column with duplicate name "+name+" in leaderboard +"+getName();
logger.severe(msg);
} else {
column = createRaceColumn(name, medalRace, fleets);
column = createRaceColumn(name, medalRace);
column.addRaceColumnListener(this);
races.add(column);
column.setRaceLogInformation(
@@ -159,17 +158,15 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
return Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
}
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace, Fleet... fleets) {
return new RaceColumnImpl(
column,
medalRace,
turnNullOrEmptyFleetsIntoDefaultFleet(fleets));
protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
return new RaceColumnImpl(column, medalRace);
}
protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
Iterable<Fleet> theFleets;
if (fleets == null || fleets.length == 0) {
theFleets = Collections.singleton(defaultFleet);
Fleet defaultfleetCasted = defaultFleet;
theFleets = Collections.singleton(defaultfleetCasted);
} else {
theFleets = Arrays.asList(fleets);
}
@@ -0,0 +1,65 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.List;
import java.util.concurrent.Callable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.analyzing.impl.AdditionalScoringInformationFinder;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationEvent;
import com.sap.sailing.domain.racelog.scoring.AdditionalScoringInformationType;
import com.sap.sse.common.Util;
/**
* {@link HighPointFirstGetsFixedScore} scheme that in most cases applies
* 10 points for the winner. Iff for one race column a {@link AdditionalScoringInformationEvent}
* is found then 8 points are applied for the winner.
*
* @author Simon Marcel Pamies
*/
public class HighPointFirstGets10Or8AndLastBreaksTie extends HighPointFirstGetsFixedScore {
private static final long serialVersionUID = 1L;
private final double SCORE_FOR_WINNER_IF_OVERWRITTEN = 8.0;
public HighPointFirstGets10Or8AndLastBreaksTie() {
super(10.0);
}
@Override
public ScoringSchemeType getType() {
return ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT;
}
@Override
public int compareByBetterScore(List<Util.Pair<RaceColumn, Double>> o1Scores, List<Util.Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter) {
return 0;
}
@Override
public Double getScoreForRank(RaceColumn raceColumn, Competitor competitor, int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher) {
Double effectiveScore = super.getScoreForRank(raceColumn, competitor, rank, numberOfCompetitorsInRaceFetcher, numberOfCompetitorsInLeaderboardFetcher);
return checkForOverwrittenScore(raceColumn, rank, effectiveScore);
}
private Double checkForOverwrittenScore(RaceColumn raceColumn, int rank, Double effectiveScore) {
Double result = effectiveScore;
for (Fleet fleet : raceColumn.getFleets()) {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLog);
AdditionalScoringInformationEvent event = finder.analyze(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE);
if (event != null) {
if (rank == 0) {
result = null;
} else {
result = Math.max(getMinimumScoreFromRank(), (double) (SCORE_FOR_WINNER_IF_OVERWRITTEN - rank + 1));
}
}
}
return result;
}
}
@@ -32,11 +32,11 @@ public abstract class HighPointFirstGetsFixedScore extends HighPoint {
this.minimumScoreFromRank = minimumScoreFromRank;
}
protected double getScoreForRaceWinner() {
double getScoreForRaceWinner() {
return scoreForRaceWinner;
}
private double getMinimumScoreFromRank() {
double getMinimumScoreFromRank() {
return minimumScoreFromRank;
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.leaderboard.impl;
import com.sap.sailing.domain.common.ScoringSchemeType;
public class HighPointWinnerGetsFiveIgnoringRaceCount extends HighPointWinnerGetsFive {
private static final long serialVersionUID = 3936338558024129174L;
/**
* Ignores the number of races scored, making the simple total points the first ranking criterion.
*/
@Override
public int compareByNumberOfRacesScored(int competitor1NumberOfRacesScored, int competitor2NumberOfRacesScored) {
return 0;
}
@Override
public ScoringSchemeType getType() {
return ScoringSchemeType.HIGH_POINT_WINNER_GETS_FIVE_IGNORING_RACE_COUNT;
}
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -32,6 +34,10 @@ public class LeaderboardGroupImpl extends LeaderboardGroupBaseImpl implements Le
public LeaderboardGroupImpl(String name, String description, boolean displayGroupsInReverseOrder, List<? extends Leaderboard> leaderboards) {
this(UUID.randomUUID(), name, description, displayGroupsInReverseOrder, leaderboards);
}
private synchronized void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
}
/**
* Use this constructor when loading or deserializing a leaderboard group and the ID is known and is provided to the constructor.
@@ -1,8 +1,6 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.impl.AbstractRaceColumn;
@@ -12,23 +10,12 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
private static final long serialVersionUID = -7801617988982540470L;
private String name;
/**
* All fleets for which this column can contain a race. This is the maximum set of keys possible for
* {@link #trackedRaces} and {@link #raceIdentifiers}.
*/
private final Iterable<Fleet> fleets;
private boolean medalRace;
public RaceColumnImpl(String name, boolean medalRace, Iterable<Fleet> fleets) {
public RaceColumnImpl(String name, boolean medalRace) {
super();
this.name = name;
this.medalRace = medalRace;
List<Fleet> myFleets = new ArrayList<Fleet>();
for (Fleet fleet : fleets) {
myFleets.add(fleet);
}
Collections.sort(myFleets);
this.fleets = myFleets;
}
@Override
@@ -53,8 +40,8 @@ public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceCo
}
@Override
public Iterable<Fleet> getFleets() {
return fleets;
public Iterable<? extends Fleet> getFleets() {
return Collections.singleton(FlexibleLeaderboardImpl.defaultFleet);
}
}
@@ -296,6 +296,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
case DNS:
case DNC:
case OCS:
case BFD:
result = true;
break;
default:
@@ -40,7 +40,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
queue.add(new Util.Pair<Object, GPSFix>(mark, fix));
}
@@ -3,7 +3,7 @@ package com.sap.sailing.domain.tracking;
import java.io.Serializable;
public interface GPSTrackListener<ItemType, FixType extends GPSFix> extends Serializable {
void gpsFixReceived(FixType fix, ItemType item);
void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack);
void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage);
@@ -13,7 +13,7 @@ import com.sap.sailing.domain.common.WindSource;
public interface RaceChangeListener extends CourseListener {
void competitorPositionChanged(GPSFixMoving fix, Competitor competitor);
void markPositionChanged(GPSFix fix, Mark mark);
void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack);
/**
* Invoked after the mark passings have been updated in the {@link TrackedRace}.
@@ -102,4 +102,10 @@ public interface TrackedLeg extends Serializable {
*/
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException;
/**
* The middle (traveling half the length) of the course middle line, connecting the center of gravity of the leg's start
* waypoint's position at time point <code>at</code> and the position of the leg's end waypoint at time point <code>at</code>.
*/
Position getMiddleOfLeg(TimePoint at);
}
@@ -397,7 +397,7 @@ public interface TrackedRace extends Serializable {
/**
* Computes a default wind direction based on the direction of the first leg at time <code>at</code>, with a default
* speed of one knot. Note that this wind direction can only be used if {@link #raceIsKnownToStartUpwind()} returns
* speed of zero knots. Note that this wind direction can only be used if {@link #raceIsKnownToStartUpwind()} returns
* <code>true</code>.
*
* @param at
@@ -33,7 +33,7 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener {
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
}
@Override
@@ -397,7 +397,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
TimePoint from = owner.getOrCreateTrack(mark).getEstimatedPositionTimePeriodAffectedBy(fix).getA();
final List<Competitor> shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
Collections.shuffle(shuffledCompetitors);
@@ -455,7 +455,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
/**
* Invalidates all cache contents for all competitors from the beginning of time
*/
private void invalidate() {
public void invalidate() {
final List<Competitor> shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
Collections.shuffle(shuffledCompetitors);
for (Competitor competitor : shuffledCompetitors) {
@@ -212,20 +212,20 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
/**
* In addition to creating the track which is performed by the superclass implementation, this implementation registers
* a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark) notifies the listeners}
* a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) notifies the listeners}
* about updates. The {@link #updated(TimePoint)} method is <em>not</em> called with the mark fix's time point because
* mark fixes may be received also from marks that don't belong to this race.
*/
@Override
protected DynamicGPSFixTrackImpl<Mark> createMarkTrack(Mark mark) {
DynamicGPSFixTrackImpl<Mark> result = super.createMarkTrack(mark);
final DynamicGPSFixTrackImpl<Mark> result = super.createMarkTrack(mark);
result.addListener(new GPSTrackListener<Mark, GPSFix>() {
private static final long serialVersionUID = -2855787105725103732L;
@Override
public void gpsFixReceived(GPSFix fix, Mark mark) {
public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) {
triggerManeuverCacheRecalculationForAllCompetitors();
notifyListeners(fix, mark);
notifyListeners(fix, mark, firstFixInTrack);
}
@Override
@@ -306,8 +306,10 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
GPSFixTrack<Mark, GPSFix> markTrack = getOrCreateTrack(mark);
markTrack.lockForRead();
try {
boolean firstInTrack = true;
for (GPSFix fix : markTrack.getRawFixes()) {
listener.markPositionChanged(fix, mark);
listener.markPositionChanged(fix, mark, firstInTrack);
firstInTrack = false;
}
} finally {
markTrack.unlockAfterRead();
@@ -437,14 +439,14 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
notifyListenersWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved);
}
private void notifyListeners(GPSFix fix, Mark mark) {
private void notifyListeners(GPSFix fix, Mark mark, boolean firstInTrack) {
RaceChangeListener[] listeners;
synchronized (getListeners()) {
listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]);
}
for (RaceChangeListener listener : listeners) {
try {
listener.markPositionChanged(fix, mark);
listener.markPositionChanged(fix, mark, firstInTrack);
} catch (Exception e) {
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
logger.log(Level.SEVERE, "notifyListeners(GPSFix, Competitor)", e);
@@ -579,96 +581,106 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void updateMarkPassings(Competitor competitor, Iterable<MarkPassing> markPassings) {
Map<Waypoint, MarkPassing> oldMarkPassings = new HashMap<Waypoint, MarkPassing>();
MarkPassing oldStartMarkPassing = null;
TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after updating the mark passings
boolean requiresStartTimeUpdate = true;
final NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
lockForRead(markPassingsForCompetitor);
LockUtil.lockForRead(getSerializationLock()); // keep serializer from reading the mark passings collections
try {
for (MarkPassing oldMarkPassing : markPassingsForCompetitor) {
if (oldStartMarkPassing == null) {
oldStartMarkPassing = oldMarkPassing;
}
oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing);
}
} finally {
unlockAfterRead(markPassingsForCompetitor);
}
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor);
TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0);
// Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers.
// Note that the write lock for the mark passings in order per waypoint is obtained inside clearMarkPassings(...)
// as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock for the competitor
// mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid deadlocks.
LockUtil.lockForWrite(markPassingsLock);
try {
clearMarkPassings(competitor);
for (MarkPassing markPassing : markPassings) {
// try to find corresponding old start mark passing
if (oldStartMarkPassing != null
&& markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) {
if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null
&& markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) {
requiresStartTimeUpdate = false;
Map<Waypoint, MarkPassing> oldMarkPassings = new HashMap<Waypoint, MarkPassing>();
MarkPassing oldStartMarkPassing = null;
TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after
// updating the mark passings
boolean requiresStartTimeUpdate = true;
final NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
lockForRead(markPassingsForCompetitor);
try {
for (MarkPassing oldMarkPassing : markPassingsForCompetitor) {
if (oldStartMarkPassing == null) {
oldStartMarkPassing = oldMarkPassing;
}
oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing);
}
if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) {
StringBuilder courseWaypointsWithID = new StringBuilder();
boolean first = true;
for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) {
if (first) {
first = false;
} else {
courseWaypointsWithID.append(" -> ");
} finally {
unlockAfterRead(markPassingsForCompetitor);
}
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor);
TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0);
// Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers.
// Note that the write lock for the mark passings in order per waypoint is obtained inside
// clearMarkPassings(...) as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock
// for the competitor mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid
// deadlocks.
LockUtil.lockForWrite(markPassingsLock);
try {
clearMarkPassings(competitor);
for (MarkPassing markPassing : markPassings) {
// try to find corresponding old start mark passing
if (oldStartMarkPassing != null
&& markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) {
if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null
&& markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) {
requiresStartTimeUpdate = false;
}
courseWaypointsWithID.append(courseWaypoint.toString());
courseWaypointsWithID.append(" (ID=");
courseWaypointsWithID.append(courseWaypoint.getId());
courseWaypointsWithID.append(")");
}
logger.severe("Received mark passing " + markPassing + " for race " + getRace()
+ " for waypoint ID" + markPassing.getWaypoint().getId()
+ " but the waypoint does not exist in course " + courseWaypointsWithID);
} else {
markPassingsForCompetitor.add(markPassing);
if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) {
StringBuilder courseWaypointsWithID = new StringBuilder();
boolean first = true;
for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) {
if (first) {
first = false;
} else {
courseWaypointsWithID.append(" -> ");
}
courseWaypointsWithID.append(courseWaypoint.toString());
courseWaypointsWithID.append(" (ID=");
courseWaypointsWithID.append(courseWaypoint.getId());
courseWaypointsWithID.append(")");
}
logger.severe("Received mark passing " + markPassing + " for race " + getRace()
+ " for waypoint ID" + markPassing.getWaypoint().getId()
+ " but the waypoint does not exist in course " + courseWaypointsWithID);
} else {
markPassingsForCompetitor.add(markPassing);
}
Collection<MarkPassing> markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing
.getWaypoint());
final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint);
LockUtil.lockForWrite(markPassingsLock2);
try {
// The mark passings of competitor have been removed by the call to
// clearMarkPassings(competitor) above
// from both, the collection that holds the mark passings by waypoint and the one that holds the
// mark passings per competitor; so we can simply add here:
markPassingsInOrderForWaypoint.add(markPassing);
} finally {
LockUtil.unlockAfterWrite(markPassingsLock2);
}
if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) {
timePointOfLatestEvent = markPassing.getTimePoint();
}
}
Collection<MarkPassing> markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing
.getWaypoint());
final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint);
LockUtil.lockForWrite(markPassingsLock2);
try {
// The mark passings of competitor have been removed by the call to clearMarkPassings(competitor) above
// from both, the collection that holds the mark passings by waypoint and the one that holds the
// mark passings per competitor; so we can simply add here:
markPassingsInOrderForWaypoint.add(markPassing);
} finally {
LockUtil.unlockAfterWrite(markPassingsLock2);
}
if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) {
timePointOfLatestEvent = markPassing.getTimePoint();
} finally {
LockUtil.unlockAfterWrite(markPassingsLock);
}
updated(timePointOfLatestEvent);
triggerManeuverCacheRecalculation(competitor);
// update the race times like start, end and the leg times
if (requiresStartTimeUpdate) {
TimePoint interimsStartOfRace = getStartOfRace();
invalidateStartTime();
TimePoint newStartOfRace = getStartOfRace();
if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace)
&& !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
// invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it
// already saw the new
// start of race time; we have to throw the notification here:
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
}
}
invalidateMarkPassingTimes();
invalidateEndTime();
// notify *after* all mark passings have been re-established; should avoid flicker
notifyListeners(competitor, oldMarkPassings, markPassings);
} finally {
LockUtil.unlockAfterWrite(markPassingsLock);
LockUtil.unlockAfterRead(getSerializationLock());
}
updated(timePointOfLatestEvent);
triggerManeuverCacheRecalculation(competitor);
// update the race times like start, end and the leg times
if (requiresStartTimeUpdate) {
TimePoint interimsStartOfRace = getStartOfRace();
invalidateStartTime();
TimePoint newStartOfRace = getStartOfRace();
if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) {
// invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it already saw the new
// start of race time; we have to throw the notification here:
notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace);
}
}
invalidateMarkPassingTimes();
invalidateEndTime();
// notify *after* all mark passings have been re-established; should avoid flicker
notifyListeners(competitor, oldMarkPassings, markPassings);
}
@Override
@@ -800,7 +812,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
}
@Override
public void gpsFixReceived(GPSFixMoving fix, Competitor competitor) {
public void gpsFixReceived(GPSFixMoving fix, Competitor competitor, boolean firstFixInTrack) {
updated(fix.getTimePoint());
triggerManeuverCacheRecalculation(competitor);
notifyListeners(fix, competitor);
@@ -890,6 +902,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@Override
public void onStartTimeChangedByRaceCommittee(TimePoint newStartTime) {
logger.info("Start time of race "+getRace().getName()+" updated by race committee to "+newStartTime);
try {
for (StartTimeChangedListener startTimeChangedListener : startTimeChangedListeners) {
startTimeChangedListener.startTimeChanged(newStartTime);
@@ -115,7 +115,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
}
private void analyzeStartTime(TimePoint startTimeProvidedByEvent) {
/* start time will be set by StartTimeAnalyzer in TrackedRace.getStartTime() */
/* start time will be set by StartTimeFinder in TrackedRace.getStartTime() */
trackedRace.invalidateStartTime();
TimePoint startTime = startTimeFinder.analyze();
@@ -1009,8 +1009,10 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
@Override
protected boolean add(FixType fix) {
final boolean result;
final boolean firstFixInTrack;
lockForWrite();
try {
firstFixInTrack = getRawFixes().isEmpty();
result = addWithoutLocking(fix);
invalidateValidityAndDistanceCaches(fix);
} finally {
@@ -1034,7 +1036,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
}
}
for (GPSTrackListener<ItemType, FixType> listener : getListeners()) {
listener.gpsFixReceived(fix, getTrackedItem());
listener.gpsFixReceived(fix, getTrackedItem(), firstFixInTrack);
}
return result;
}
@@ -79,7 +79,7 @@ public class MaxSpeedCache<ItemType, FixType extends GPSFix> implements GPSTrack
* outside of the invalidation interval, crop cache entry's interval such that it's overlap-free, otherwise remove.
*/
@Override
public void gpsFixReceived(FixType fix, ItemType item) {
public void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack) {
// find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that interval, produces "fix"
Util.Pair<TimePoint, TimePoint> invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix);
LockUtil.lockForWrite(lock);
@@ -51,6 +51,7 @@ public class ShortTimeWindCache {
synchronized (order) {
if (order.isEmpty()) {
cancel();
timer.cancel();
timer = null;
}
}
@@ -693,7 +693,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
assert fix != null && fix.getTimePoint() != null;
// A mark position change can mean a leg type change. The interval over which the wind estimation is affected
// depends on how the GPS track computes the estimated mark position. Ask it:
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -31,8 +33,8 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sse.common.Util;
public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
@@ -45,7 +47,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
private final Leg leg;
private final Map<Competitor, TrackedLegOfCompetitor> trackedLegsOfCompetitors;
private TrackedRaceImpl trackedRace;
private final Map<TimePoint, List<TrackedLegOfCompetitor>> competitorTracksOrderedByRank;
private transient Map<TimePoint, List<TrackedLegOfCompetitor>> competitorTracksOrderedByRank;
public TrackedLegImpl(DynamicTrackedRaceImpl trackedRace, Leg leg, Iterable<Competitor> competitors) {
super();
@@ -56,7 +58,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
trackedLegsOfCompetitors.put(competitor, new TrackedLegOfCompetitorImpl(this, competitor));
}
trackedRace.addListener(this);
competitorTracksOrderedByRank = new HashMap<TimePoint, List<TrackedLegOfCompetitor>>();
competitorTracksOrderedByRank = new HashMap<>();
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
competitorTracksOrderedByRank = new HashMap<>();
}
@Override
@@ -114,7 +121,6 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
// race may be updated while calculation is going on, but each individual calculation is properly
// synchronized, usually by read-write locks, so there is no major difference in synchronization issues
// an the asynchronous nature of how the data is being received
// TODO See bug 469; competitors already disqualified may need to be ranked worst
Collections.sort(rankedCompetitorList, new WindwardToGoComparator(this, timePoint, cache));
rankedCompetitorList = Collections.unmodifiableList(rankedCompetitorList);
synchronized (competitorTracksOrderedByRank) {
@@ -198,11 +204,10 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
* of the first mark at the beginning of the leg and moving half way to the first mark of leg's end. If either of
* the mark positions cannot be determined, <code>null</code> is returned.
*/
@Override
public Position getMiddleOfLeg(TimePoint at) {
Position approximateLegStartPosition = getTrackedRace().getOrCreateTrack(
getLeg().getFrom().getMarks().iterator().next()).getEstimatedPosition(at, false);
Position approximateLegEndPosition = getTrackedRace().getOrCreateTrack(
getLeg().getTo().getMarks().iterator().next()).getEstimatedPosition(at, false);
Position approximateLegStartPosition = getTrackedRace().getApproximatePosition(getLeg().getFrom(), at);
Position approximateLegEndPosition = getTrackedRace().getApproximatePosition(getLeg().getTo(), at);
final Position middleOfLeg;
if (approximateLegStartPosition == null || approximateLegEndPosition == null) {
middleOfLeg = null;
@@ -217,12 +222,12 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
return middleOfLeg;
}
public Position getEffectiveWindPosition(Callable<Position> positionProvider, TimePoint at, WindPositionMode mode) {
public Position getEffectiveWindPosition(Callable<Position> exactPositionProvider, TimePoint at, WindPositionMode mode) {
final Position effectivePosition;
switch (mode) {
case EXACT:
try {
effectivePosition = positionProvider.call();
effectivePosition = exactPositionProvider.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -285,7 +290,7 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
}
@Override
public void markPositionChanged(GPSFix fix, Mark mark) {
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
clearCaches();
}
@@ -445,11 +445,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* Object serialization obtains a read lock for the course so that in cannot change while serializing this object.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
LockUtil.lockForWrite(getSerializationLock());
// obtain the course's read lock because a course change during serialization could lead to
// trackedLegs being inconsistent with getRace().getCourse().getLegs()
getRace().getCourse().lockForRead();
try {
s.defaultWriteObject();
LockUtil.lockForWrite(getSerializationLock());
try {
s.defaultWriteObject();
} finally {
LockUtil.unlockAfterWrite(getSerializationLock());
}
} finally {
LockUtil.unlockAfterWrite(getSerializationLock());
getRace().getCourse().unlockAfterRead();
}
}
@@ -468,7 +475,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
competitorRankingsLocks = createCompetitorRankingsLockMap();
directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
crossTrackErrorCache = new CrossTrackErrorCache(this);
crossTrackErrorCache.invalidate();
maneuverCache = createManeuverCache();
triggerManeuverCacheRecalculationForAllCompetitors();
logger.info("Deserialized race " + getRace().getName());
shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2);
}
@@ -1371,7 +1380,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at);
Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at);
if (firstLegStart != null && firstLegEnd != null) {
result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(1.0,
result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0,
firstLegEnd.getBearingGreatCircle(firstLegStart)));
} else {
result = null;
@@ -1588,6 +1597,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
@Override
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
// expecting to hold the course's write lock
invalidateMarkPassingTimes();
LockUtil.lockForRead(getSerializationLock());
try {
@@ -2487,7 +2497,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
@Override
public void gpsFixReceived(GPSFix fix, Mark mark) {
public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) {
clearDirectionFromStartToNextMarkCache();
}
@@ -0,0 +1,9 @@
package com.sap.sailing.util;
import java.io.ByteArrayOutputStream;
public class ByteArrayOutputStreamWithVisibleBuffer extends ByteArrayOutputStream {
public byte[] getBuffer() {
return buf;
}
}
@@ -1,4 +1,4 @@
package com.sap.sailing.server.replication.impl;
package com.sap.sailing.util.impl;
import java.io.FilterOutputStream;
import java.io.IOException;
@@ -6,6 +6,12 @@ import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Stream decorator that counts the bytes sent through an {@link OutputStream}. Logs
* that number in configurable way using a {@link Logger}.
*
* @author Axel Uhl
*/
public class CountingOutputStream extends FilterOutputStream {
private static final Logger logger = Logger.getLogger(CountingOutputStream.class.getName());
@@ -37,7 +43,7 @@ public class CountingOutputStream extends FilterOutputStream {
}
private void log() {
logger.log(level, "wrote "+count+" bytes to "+name);
logger.log(level, "wrote "+count+" bytes "+(count>1000?"("+count/1000+" kB)":"")+" to "+name);
}
@Override
@@ -51,4 +57,10 @@ public class CountingOutputStream extends FilterOutputStream {
super.write(b, off, len);
count(len);
}
@Override
public void close() throws IOException {
super.close();
log();
}
}
@@ -12,7 +12,8 @@ Require-Bundle: com.sap.sailing.server,
org.objenesis;bundle-version="1.3.0",
org.hamcrest;bundle-version="1.1.0",
com.sap.sailing.domain.igtimiadapter.persistence;bundle-version="1.0.0",
com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0"
com.sap.sailing.domain.racelogtrackingadapter.testsupport;bundle-version="1.0.0",
com.rabbitmq.client;bundle-version="2.8.4"
Bundle-ClassPath: .
Import-Package: com.sap.sailing.domain.racelogtracking.impl,
javax.jms;version="1.1.0",
@@ -0,0 +1,108 @@
package com.sap.sailing.gwt.ui.common.client;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import com.sap.sailing.domain.common.Duration;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.gwt.home.client.shared.Countdown;
import com.sap.sailing.gwt.home.client.shared.Countdown.CountdownListener;
import com.sap.sailing.gwt.home.client.shared.Countdown.RemainingTime;
import com.sap.sailing.gwt.home.client.shared.Countdown.Unit;
public class CountdownTest {
@Test
public void testDaysAndHours() throws Exception {
final long days = 2;
final long hours = 13;
final long minutes = 28;
final long seconds = 2;
TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds));
CountdownListener countdownListener = createCountDownAssertionListener(days, Unit.DAYS, hours, Unit.HOURS);
new Countdown(zeroDate, countdownListener, true);
}
private CountdownListener createCountDownAssertionListener(final long majorValue, final Unit majorUnit, final long minorValue, final Unit minorUnit) {
return new CountdownListener() {
@Override
public void changed(RemainingTime major, RemainingTime minor) {
assertThat(major, is(notNullValue()));
assertThat(major.value, is(majorValue));
assertThat(major.unit, is(majorUnit));
if (minorUnit != null) {
assertThat(minor, is(notNullValue()));
assertThat(minor.value, is(minorValue));
assertThat(minor.unit, is(minorUnit));
} else {
assertThat(minor, is(nullValue()));
}
}
};
}
@Test
public void testDaysNoHours() throws Exception {
final long days = 2;
final long hours = 0;
final long minutes = 28;
final long seconds = 2;
TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds));
CountdownListener countdownListener = createCountDownAssertionListener(days, Unit.DAYS, hours, Unit.HOURS);
new Countdown(zeroDate, countdownListener, true);
}
@Test
public void testOnlyDays() throws Exception {
final long days = 3;
final long hours = 0;
final long minutes = 28;
final long seconds = 2;
TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds));
CountdownListener countdownListener = createCountDownAssertionListener(days, Unit.DAYS, 0, null);
new Countdown(zeroDate, countdownListener, true);
}
@Test
public void testHoursAndMinutes() throws Exception {
final long days = 0;
final long hours = 13;
final long minutes = 28;
final long seconds = 2;
TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds));
CountdownListener countdownListener = createCountDownAssertionListener(hours, Unit.HOURS, minutes, Unit.MINUTES);
new Countdown(zeroDate, countdownListener, true);
}
@Test
public void testMinutesAndSeconds() throws Exception {
final long days = 0;
final long hours = 0;
final long minutes = 28;
final long seconds = 2;
TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds));
CountdownListener countdownListener = createCountDownAssertionListener(minutes, Unit.MINUTES, seconds, Unit.SECONDS);
new Countdown(zeroDate, countdownListener, true);
}
@Test
public void testSecondOnly() throws Exception {
final long days = 0;
final long hours = 0;
final long minutes = 0;
final long seconds = 2;
TimePoint zeroDate = MillisecondsTimePoint.now().plus(Duration.ONE_DAY.times(days)).plus(Duration.ONE_HOUR.times(hours)).plus(Duration.ONE_MINUTE.times(minutes)).plus(Duration.ONE_SECOND.times(seconds));
CountdownListener countdownListener = createCountDownAssertionListener(seconds, Unit.SECONDS, 0, null);
new Countdown(zeroDate, countdownListener, true);
}
}
@@ -1,4 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuUmFjZUJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0UgLWNvbXBpbGVSZXBvcnQgLVhzb3ljRGV0YWlsZWRdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5SYWNlQm9hcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
+1
View File
@@ -26,6 +26,7 @@
<!-- be added before this line. -->
<!-- -->
<script type="text/javascript" src="com.sap.sailing.gwt.Home/com.sap.sailing.gwt.Home.nocache.js"></script>
<script type="text/javascript" src="js/newhome/idangerous.swiper.js"></script>
</head>
<!-- -->
+1 -1
View File
@@ -11,7 +11,7 @@
<link href='images/sap-sailing-app-icon.png' rel='apple-touch-icon'>
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts-more.js"></script>
+1 -1
View File
@@ -14,7 +14,7 @@
<!--<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="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts-more.js"></script>
@@ -29,6 +29,10 @@
margin: 9px 12px 0 0;
}
.viewerToolbar-settingsButton:last-child {
margin: 0 0px 0 1px;
}
.viewerToolbar-settingsButton {
background: url(images/settings_white.png), url(images/btn-cta-orangeleft.png);
background-position: center, center 0;
@@ -4,6 +4,17 @@
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<!-- Disable directory listing: -->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.eclipse.jetty.servlet.DefaultServlet
</servlet-class>
<init-param>
<param-name>dirAllowed</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
<filter>
<filter-name>GWTCacheControlServletFilter</filter-name>
<filter-class>com.sap.sse.gwt.server.GWTCacheControlServletFilter</filter-class>
Loaded 100 of 437 files, more files were not shown because too many files have changed in this diff. Show more