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

This commit is contained in:
D048538 committed 2012-06-12 10:09:35 +02:00
commit b0df915c81
424 files changed
+34680 -17407

No files matched your search

+1
View File
@@ -427,6 +427,7 @@ into one in the future.
"boatclass": ..., // optional; name of boat class
"competitors": [ // the event's competitors
{ "name", ..., // competitor's name
"sailID": ... // sail ID if known
"nationality": ..., // 3-letter IOC country code
"nationalityISO2": ..., // 2-letter ISO country code (may be empty)
"nationalityISO3": ..., // 3-letter ISO country code (may be empty)
@@ -57,7 +57,10 @@ public class DeclinationServiceImpl implements DeclinationService {
int year = cal.get(Calendar.YEAR);
QuadTree<Declination> set;
while ((set = getYearStore(year)) != null) {
Declination resultForYear = set.get(position);
Declination resultForYear;
synchronized (set) {
resultForYear = set.get(position);
}
Distance spatialDistance = resultForYear.getPosition().getDistance(position);
// consider result only if it's closer than maxDistance
if (spatialDistance.compareTo(maxDistance) <= 0) {
@@ -73,7 +76,9 @@ public class DeclinationServiceImpl implements DeclinationService {
if (result == null) {
QuadTree<Declination> importerCacheForYear = importerCache.get(year);
if (importerCacheForYear != null) {
result = importerCacheForYear.get(position);
synchronized (importerCacheForYear) {
result = importerCacheForYear.get(position);
}
if (result.getPosition().getDistance(position).compareTo(maxDistance) <= 0) {
return result;
// else it's further away from the requested position as demanded by maxDistance
@@ -85,7 +90,9 @@ public class DeclinationServiceImpl implements DeclinationService {
importerCacheForYear = new QuadTree<Declination>();
importerCache.put(year, importerCacheForYear);
}
importerCacheForYear.put(result.getPosition(), result);
synchronized (importerCacheForYear) {
importerCacheForYear.put(result.getPosition(), result);
}
}
}
return result;
@@ -49,7 +49,9 @@ public class DeclinationStore {
result = new QuadTree<Declination>();
BufferedReader in = new BufferedReader(new InputStreamReader(is));
while ((record = readExternal(in)) != null) {
result.put(record.getPosition(), record);
synchronized (result) {
result.put(record.getPosition(), record);
}
}
}
return result;
@@ -150,7 +152,10 @@ public class DeclinationStore {
System.out.println("Date: " + year + "/" + (month + 1) + ", Latitude: " + lat);
for (double lng = 0; lng < 180; lng += grid) {
Position point = new DegreePosition(lat, lng);
Declination existingDeclinationRecord = storedDeclinations.get(point);
Declination existingDeclinationRecord;
synchronized (storedDeclinations) {
existingDeclinationRecord = storedDeclinations.get(point);
}
if (existingDeclinationRecord == null
|| DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord
.getPosition().getDistance(point), timePoint, existingDeclinationRecord
@@ -161,7 +166,10 @@ public class DeclinationStore {
}
for (double lng = -grid; lng > -180; lng -= grid) {
Position point = new DegreePosition(lat, lng);
Declination existingDeclinationRecord = storedDeclinations.get(point);
Declination existingDeclinationRecord;
synchronized (storedDeclinations) {
existingDeclinationRecord = storedDeclinations.get(point);
}
if (existingDeclinationRecord == null
|| DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord
.getPosition().getDistance(point), timePoint, existingDeclinationRecord
@@ -175,7 +183,10 @@ public class DeclinationStore {
System.out.println("Date: " + year + "/" + (month + 1) + ", Latitude: " + lat);
for (double lng = 0; lng < 180; lng += grid) {
Position point = new DegreePosition(lat, lng);
Declination existingDeclinationRecord = storedDeclinations.get(point);
Declination existingDeclinationRecord;
synchronized (storedDeclinations) {
existingDeclinationRecord = storedDeclinations.get(point);
}
if (DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
timePoint, existingDeclinationRecord.getTimePoint()) > 0.1) {
// less than ~6 nautical miles and/or ~.6 months off
@@ -184,7 +195,10 @@ public class DeclinationStore {
}
for (double lng = -grid; lng > -180; lng -= grid) {
Position point = new DegreePosition(lat, lng);
Declination existingDeclinationRecord = storedDeclinations.get(point);
Declination existingDeclinationRecord;
synchronized (storedDeclinations) {
existingDeclinationRecord = storedDeclinations.get(point);
}
if (DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
timePoint, existingDeclinationRecord.getTimePoint()) > 0.1) {
// less than ~6 nautical miles and/or ~.6 months off
@@ -7,4 +7,5 @@ Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.quadtree
com.sap.sailing.domain.common.quadtree,
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test"
@@ -12,9 +12,12 @@ public class AbstractPosition implements Position {
}
public boolean equals(Object o) {
return o instanceof Position &&
getLatRad() == ((Position) o).getLatRad() &&
getLngRad() == ((Position) o).getLngRad();
if (o == null) {
return false;
} else {
return o instanceof Position && getLatRad() == ((Position) o).getLatRad()
&& getLngRad() == ((Position) o).getLngRad();
}
}
@Override
@@ -0,0 +1,10 @@
package com.sap.sailing.domain.common;
import java.io.Serializable;
import com.sap.sailing.domain.common.impl.Util;
public interface Color extends Serializable {
Util.Triple<Integer, Integer, Integer> getAsRGB();
String getAsHtml();
}
@@ -8,22 +8,33 @@ package com.sap.sailing.domain.common;
*
*/
public enum MaxPointsReason {
/** The competitor finished the race properly */
NONE,
NONE(true),
/** Did Not Start */
DNS,
DNS(true),
/** Did Not Finish */
DNF,
DNF(true),
/** DiSQualified */
DSQ,
DSQ(true),
/** On Course Side (jumped the gun) */
OCS,
OCS(true),
/** Disqualified, non-discardable */
DND,
DND(false),
/** Black Flag Disqualified */
BFD,
BFD(true),
/** Did Not Compete */
DNC,
DNC(true),
/** Retired After Finishing */
RAF
RAF(true);
private final boolean discardable;
private MaxPointsReason(boolean discardable) {
this.discardable = discardable;
}
public boolean isDiscardable() {
return discardable;
}
}
@@ -30,7 +30,7 @@ public class RegattaNameAndRaceName extends RegattaName implements RegattaAndRac
}
@Override
public String toString() {
return raceName;
return getRegattaName()+"/"+getRaceName();
}
@Override
@@ -0,0 +1,31 @@
package com.sap.sailing.domain.common;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.common.impl.Util.Pair;
/**
* Describes sources of official regatta results which may be imported and applied to <code>ScoreCorrection</code>
* objects to update our leaderboards from the official scoring systems where necessary and applicable.
*
* @author Axel Uhl (d043530)
*
*/
public interface ScoreCorrectionProvider extends Named {
/**
* Returns a valid map whose keys are event names such as "Kieler Woche 2011" or "IDM Travemünde 2011" and whose
* values are sets of pairs of boat class names and the times for which it has score corrections. If there are multiple
* score corrections taken at different times. Later score corrections are expected to be cumulative, meaning they
* also contain all previous corrections.
*/
public Map<String, Set<Pair<String, TimePoint>>> getHasResultsForBoatClassFromDateByEventName() throws IOException,
SAXException, ParserConfigurationException;
public ScoreCorrections getScoreCorrections(String eventName, String boatClassName, TimePoint millisecondsTimePoint);
}
@@ -0,0 +1,5 @@
package com.sap.sailing.domain.common;
public interface ScoreCorrections {
}
@@ -0,0 +1,82 @@
package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.impl.Util.Triple;
public class RGBColor implements Color {
private static final long serialVersionUID = -4091876840771631308L;
private int red;
private int green;
private int blue;
RGBColor() {} // for GWT serializability
public RGBColor(int red, int green, int blue) {
super();
this.red = ensureValidRange(red);
this.green = ensureValidRange(green);
this.blue = ensureValidRange(blue);
}
private int ensureValidRange(int value) {
int result = value;
if (value < 0) {
result = 0;
} else if (value > 255) {
result = 255;
}
return result;
}
@Override
public Triple<Integer, Integer, Integer> getAsRGB() {
return new Triple<Integer, Integer, Integer>(red, green, blue);
}
@Override
public String getAsHtml() {
return "#" + toBrowserHexValue(red) + toBrowserHexValue(green) + toBrowserHexValue(blue);
}
private static String toBrowserHexValue(int number) {
StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff));
while (builder.length() < 2) {
builder.append("0");
}
return builder.toString().toUpperCase();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + blue;
result = prime * result + green;
result = prime * result + red;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RGBColor other = (RGBColor) obj;
if (blue != other.blue)
return false;
if (green != other.green)
return false;
if (red != other.red)
return false;
return true;
}
@Override
public String toString() {
return getAsHtml();
}
}
@@ -30,6 +30,45 @@ public class Util {
}
}
public static <T> int indexOf(Iterable<? extends T> i, T t) {
int result;
if (i instanceof List<?>) {
List<?> list = (List<?>) i;
result = list.indexOf(t);
} else {
boolean found = false;
int counter = 0;
for (T it : i) {
if (it == null && t == null
|| it != null && it.equals(t)) {
result = counter;
found = true;
break;
}
counter++;
}
if (found) {
result = counter;
} else {
result = -1;
}
}
return result;
}
public static <T> boolean equals(Iterable<? extends T> a, Iterable<? extends T> b) {
Iterator<? extends T> aIter = a.iterator();
Iterator<? extends T> bIter = b.iterator();
while (aIter.hasNext() && bIter.hasNext()) {
T ao = aIter.next();
T bo = bIter.next();
if (!ao.equals(bo)) {
return false;
}
}
return !aIter.hasNext() && !bIter.hasNext();
}
public static <T> T get(Iterable<T> iterable, int i) {
if (iterable instanceof List<?>) {
List<T> l = (List<T>) iterable;
@@ -32,11 +32,16 @@ import com.sap.sailing.domain.common.quadtree.impl.Bounds;
import com.sap.sailing.domain.common.quadtree.impl.QuadTreeNode;
/**
* The QuadTree lets you organize objects in a grid, that redefines
* itself and focuses more gridding when more objects appear in a
* certain area.
* The QuadTree lets you organize objects in a grid, that redefines itself and focuses more gridding when more objects
* appear in a certain area.
* <p>
*
* @param <T> type of object stored by coordinates
* Note that this class is not thread safe. If multiple threads can access the same instance concurrently, callers have
* to ensure proper synchronization. Concurrent reads are permissible while any write should block all other operations.
*
* @param <T>
* type of object stored by coordinates
* @author Axel Uhl (D043530)
*/
public class QuadTree<T> implements Serializable {
@@ -110,7 +110,7 @@ public class QuadTreeNode<T> implements Serializable {
* into the children.
*/
@SuppressWarnings("unchecked")
protected void split() {
protected void split() {
// Make sure we're bigger than the minimum, if we care,
if (minSize != NO_MIN_SIZE) {
if (Math.abs(bounds.getNorthEast().getLatDeg() - bounds.getSouthWest().getLatDeg()) < minSize
@@ -121,7 +121,6 @@ public class QuadTreeNode<T> implements Serializable {
double nsHalf = (bounds.getNorthEast().getLatDeg() + bounds.getSouthWest().getLatDeg()) / 2.0;
double ewHalf = (bounds.getNorthEast().getLngDeg() + bounds.getSouthWest().getLngDeg()) / 2.0;
children = new QuadTreeNode[4];
children[NORTHWEST] = new QuadTreeNode<T>(new Bounds(new DegreePosition(nsHalf, bounds.getSouthWest().getLngDeg()), new DegreePosition(bounds.getNorthEast().getLatDeg(), ewHalf)), maxItems);
children[NORTHEAST] = new QuadTreeNode<T>(new Bounds(new DegreePosition(nsHalf, ewHalf), bounds.getNorthEast()), maxItems);
children[SOUTHEAST] = new QuadTreeNode<T>(new Bounds(new DegreePosition(bounds.getSouthWest().getLatDeg(), ewHalf), new DegreePosition(nsHalf, bounds.getNorthEast().getLngDeg())), maxItems);
@@ -131,7 +130,6 @@ public class QuadTreeNode<T> implements Serializable {
for (Iterator<QuadTreeLeaf<T>> i=temp.iterator(); i.hasNext(); ) {
put(i.next());
}
//items.removeAllElements();
}
/**
@@ -208,9 +206,9 @@ public class QuadTreeNode<T> implements Serializable {
this.allTheSamePoint = false;
}
}
if (this.items.size() > maxItems && !this.allTheSamePoint)
if (this.items.size() > maxItems && !this.allTheSamePoint) {
split();
}
} else {
QuadTreeNode<T> node = getChild(leaf.getPoint());
if (node != null) {
@@ -10,4 +10,4 @@ Require-Bundle: com.sap.sailing.mongodb,
com.mongodb.driver;bundle-version="2.6.2",
com.sap.sailing.domain.common
Export-Package: com.sap.sailing.domain.persistence,
com.sap.sailing.domain.persistence.impl;x-friends:="com.sap.sailing.mongodb.test"
com.sap.sailing.domain.persistence.impl;x-friends:="com.sap.sailing.mongodb.test,com.sap.sailing.domain.swisstimingadapter.test"
@@ -3,12 +3,15 @@ package com.sap.sailing.domain.persistence;
import java.util.Map;
import com.mongodb.DBObject;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaRegistry;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindTrack;
/**
@@ -20,28 +23,41 @@ import com.sap.sailing.domain.tracking.WindTrack;
public interface DomainObjectFactory {
WindTrack loadWindTrack(Regatta regatta, RaceDefinition race, WindSource windSource, long millisecondsOverWhichToAverage);
Leaderboard loadLeaderboard(String name);
/**
* @return the leaderboard loaded, if successful, or <code>null</code> if the leaderboard couldn't be loaded,
* e.g., because the regatta for a regatta leaderboard couldn't be found
*/
Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry);
Iterable<Leaderboard> getAllLeaderboards();
Iterable<Leaderboard> getAllLeaderboards(RegattaRegistry regattaRegistry);
RaceIdentifier loadRaceIdentifier(DBObject dbObject);
/**
* @return The group with the name <code>name</code>, or <code>null</code> if no such group exists.
*/
LeaderboardGroup loadLeaderboardGroup(String name);
LeaderboardGroup loadLeaderboardGroup(String name, RegattaRegistry regattaRegistry);
/**
* @return All groups in the database.
*/
Iterable<LeaderboardGroup> getAllLeaderboardGroups();
Iterable<LeaderboardGroup> getAllLeaderboardGroups(RegattaRegistry regattaRegistry);
/**
* @return All leaderboards in the database, which aren't contained by a leaderboard group
*/
Iterable<Leaderboard> getLeaderboardsNotInGroup();
Iterable<Leaderboard> getLeaderboardsNotInGroup(RegattaRegistry regattaRegistry);
Map<? extends WindSource, ? extends WindTrack> loadWindTracks(Regatta regatta, RaceDefinition race,
long millisecondsOverWhichToAverageWind);
Event loadEvent(String name);
Iterable<Event> loadAllEvents();
Regatta loadRegatta(String name, TrackedRegattaRegistry trackedRegattaRegistry);
Iterable<Regatta> loadAllRegattas(TrackedRegattaRegistry trackedRegattaRegistry);
Map<String, Regatta> loadRaceIDToRegattaAssociations(RegattaRegistry regattaRegistry);
}
@@ -1,14 +1,15 @@
package com.sap.sailing.domain.persistence;
import com.mongodb.DBObject;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
/**
* Offers methods to construct {@link DBObject MongoDB objects} from domain objects.
@@ -37,8 +38,6 @@ public interface MongoObjectFactory {
void renameLeaderboard(String oldName, String newName);
void storeRaceIdentifier(RaceIdentifier raceIdentifier, DBObject dbObject);
/**
* Stores the group, if it doesn't exist or updates it.<br />
* Leaderboards in the group, which aren't stored in the database, will be stored.
@@ -55,4 +54,21 @@ public interface MongoObjectFactory {
*/
void renameLeaderboardGroup(String oldName, String newName);
/**
* Stores the event with its name, venue and the venue's course areas. The regattas obtained by
* {@link Event#getRegattas()} are <em>not</em> stored by this call. They need to be stored separately
* by calls to {@link #storeRegatta} where a reference to their owning event is stored.
*/
void storeEvent(Event event);
/**
* Stores the regatta together with its name, {@link Series} definitions and an optional link to the
* {@link Event} to which the regatta belongs.
*/
void storeRegatta(Regatta regatta);
void removeRegatta(Regatta regatta);
void storeRegattaForRaceID(String id, Regatta regatta);
}
@@ -1,5 +1,50 @@
package com.sap.sailing.domain.persistence.impl;
/**
* Defines literals providing the names for MongoDB collections. The literal documentation described the semantics
* of the collection identified by that literal.
*
* @author Axel Uhl (D043530)
*
*/
public enum CollectionNames {
WIND_TRACKS, LEADERBOARDS, LEADERBOARD_GROUPS;
/**
* Stores the wind fixes recorded from persistent wind sources.
*/
WIND_TRACKS,
/**
* Stores the leaderboards with their names, score corrections, competitor display name overrides, race
* columns and their fleets as well as the per-fleet tracked race assignments.
*/
LEADERBOARDS,
/**
* Stores the leaderboard group configurations with references to the {@link #LEADERBOARDS} collection
*/
LEADERBOARD_GROUPS,
/**
* Top-level event information about events such as Kiel Week 2011, or IDM Travemünde 2011, including name and
* course areas.
*/
EVENTS,
/**
* Stores regatta definitions including their series layout and fleets and race columns. Regattas can reference
* the event from the {@link #EVENTS} collection to which they belong.
*/
REGATTAS,
/**
* Stores boat class-specific master data such as the class's hull length, logo, name, number of sailors, etc.
* To be implemented in future versions.
*/
BOAT_CLASSES,
/**
* Stores the mapping of {@link RaceDefinition#getId race IDs} to regatta names for automatic re-association
* when tracking races again without explicitly specifying a regatta.
*/
REGATTA_FOR_RACE_ID;
}
@@ -5,13 +5,12 @@ import java.util.Iterator;
import java.util.Map;
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.base.RaceColumnListener;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.RaceColumnImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
@@ -21,70 +20,53 @@ import com.sap.sailing.domain.tracking.TrackedRace;
* score corrections are then assigned to the {@link Competitor}s once they show up by
* {@link #addRace(TrackedRace, String, boolean) adding races} to this leaderboard.<p>
*
* TODO This class should be in the impl package. However, in order to temporarily solve bug 595, moving this class
* to the exported package may help. A better solution would involve obtaining this bundle's class loader in
* <code>ObjectInputStreamResolvingAgainstDomainFactory</code> which is able to obtain the class from a non-exported
* package.
* This object registers itself as a {@link RaceColumnListener} on the {@link Leaderboard} at construction time. It
* therefore receives all updates to the linking structure between the leaderboard's {@link RaceColumn}s and the
* {@link TrackedRace}s associated with them and can react accordingly to assign left-over corrections to the
* correct, resolved competitor objects (see {@link #trackedRaceLinked(RaceColumn, Fleet, TrackedRace)}). When
* all left-overs have been applied to the leaderboard, this object removes itself as {@link RaceColumnListener} from
* the leaderboard again.
*
* @author Axel Uhl (d043530)
*
*/
public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
private static final long serialVersionUID = -8933075542228571746L;
public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorrections {
private static final long serialVersionUID = 8824782847677232275L;
private final Map<String, Integer> carriedPointsByCompetitorName;
private final Map<String, Map<RaceColumn, MaxPointsReason>> maxPointsReasonsByCompetitorName;
private final Map<String, Map<RaceColumn, Integer>> correctedScoresByCompetitorName;
private final Map<String, String> displayNamesByCompetitorName;
private final Leaderboard leaderboard;
/**
* A wrapper for {@link RaceColumn} that, when its {@link #setTrackedRace(TrackedRace)} method is called,
* additionally calls {@link LeaderboardImplWithDelayedCarriedPoints#assignLeftOvers(TrackedRace)}.
*
* @author Axel Uhl (D043530)
*/
private class RaceInLeaderboardForDelayedCarriedPoints extends RaceColumnImpl {
private static final long serialVersionUID = -1243132535406059096L;
public RaceInLeaderboardForDelayedCarriedPoints(Leaderboard leaderboard, String name, boolean medalRace) {
super(name, medalRace);
}
@Override
public void setTrackedRace(TrackedRace trackedRace) {
super.setTrackedRace(trackedRace);
if (trackedRace != null) {
assignLeftOvers(trackedRace);
}
}
}
public LeaderboardImplWithDelayedCarriedPoints(String name, SettableScoreCorrection scoreCorrection,
ThresholdBasedResultDiscardingRule resultDiscardingRule) {
super(name, scoreCorrection, resultDiscardingRule);
public DelayedLeaderboardCorrectionsImpl(Leaderboard leaderboard) {
carriedPointsByCompetitorName = new HashMap<String, Integer>();
maxPointsReasonsByCompetitorName = new HashMap<String, Map<RaceColumn,MaxPointsReason>>();
correctedScoresByCompetitorName = new HashMap<String, Map<RaceColumn,Integer>>();
displayNamesByCompetitorName = new HashMap<String, String>();
this.leaderboard = leaderboard;
leaderboard.addRaceColumnListener(this);
}
@Override
public Leaderboard getLeaderboard() {
return leaderboard;
}
private void assertNoTrackedRaceAssociatedYet() {
for (RaceColumn raceColumn : getRaceColumns()) {
if (raceColumn.getTrackedRace() != null) {
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
if (raceColumn.hasTrackedRaces()) {
throw new IllegalStateException("Can't enqueue competitor name-based state while tracked races are already associated with leaderboard");
}
}
}
@Override
protected RaceColumnImpl createRaceColumn(String columnName, boolean medalRace) {
return new RaceInLeaderboardForDelayedCarriedPoints(this, columnName, medalRace);
}
public void setCarriedPoints(String competitorName, int carriedPoints) {
assertNoTrackedRaceAssociatedYet();
carriedPointsByCompetitorName.put(competitorName, carriedPoints);
}
@Override
public void setMaxPointsReason(String competitorName, RaceColumn raceColumn, MaxPointsReason maxPointsReason) {
assertNoTrackedRaceAssociatedYet();
Map<RaceColumn, MaxPointsReason> map = maxPointsReasonsByCompetitorName.get(competitorName);
@@ -95,6 +77,7 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
map.put(raceColumn, maxPointsReason);
}
@Override
public void correctScore(String competitorName, RaceColumn raceColumn, int correctedScore) {
assertNoTrackedRaceAssociatedYet();
Map<RaceColumn, Integer> map = correctedScoresByCompetitorName.get(competitorName);
@@ -105,18 +88,6 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
map.put(raceColumn, correctedScore);
}
/**
* Performs the regular {@link LeaderboardImpl#addRace(TrackedRace, String, boolean)} and then checks if
* there are any carried points etc. left over to assign to the competitor objects (see
* {@link #assignLeftOvers(TrackedRace)}).
*/
@Override
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
RaceColumn result = super.addRace(race, columnName, medalRace);
assignLeftOvers(race);
return result;
}
/**
* Checks if there are any carried points, max points reasons or corrected scores left over that may now receive
* their competitor record. If so, {@link #setCarriedPoints(com.sap.sailing.domain.base.Competitor, int)},
@@ -133,7 +104,7 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
for (Iterator<Map.Entry<String, Integer>> carryEntryIter = carriedPointsByCompetitorName.entrySet().iterator(); carryEntryIter.hasNext(); ) {
Map.Entry<String, Integer> carryEntry = carryEntryIter.next();
if (competitorsByName.containsKey(carryEntry.getKey())) {
setCarriedPoints(competitorsByName.get(carryEntry.getKey()), carryEntry.getValue());
leaderboard.setCarriedPoints(competitorsByName.get(carryEntry.getKey()), carryEntry.getValue());
carryEntryIter.remove();
}
}
@@ -142,7 +113,7 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
java.util.Map.Entry<String, Map<RaceColumn, MaxPointsReason>> maxPointsReasonEntries = maxPointsReasonsEntryIter.next();
if (competitorsByName.containsKey(maxPointsReasonEntries.getKey())) {
for (Map.Entry<RaceColumn, MaxPointsReason> maxPointsReasonEntry : maxPointsReasonEntries.getValue().entrySet()) {
getScoreCorrection().setMaxPointsReason(competitorsByName.get(maxPointsReasonEntries.getKey()),
leaderboard.getScoreCorrection().setMaxPointsReason(competitorsByName.get(maxPointsReasonEntries.getKey()),
maxPointsReasonEntry.getKey(), maxPointsReasonEntry.getValue());
}
maxPointsReasonsEntryIter.remove();
@@ -153,7 +124,7 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
java.util.Map.Entry<String, Map<RaceColumn, Integer>> correctedScoresEntries = correctedScoresEntryIter.next();
if (competitorsByName.containsKey(correctedScoresEntries.getKey())) {
for (java.util.Map.Entry<RaceColumn, Integer> correctedScoreEntry : correctedScoresEntries.getValue().entrySet()) {
getScoreCorrection().correctScore(competitorsByName.get(correctedScoresEntries.getKey()),
leaderboard.getScoreCorrection().correctScore(competitorsByName.get(correctedScoresEntries.getKey()),
correctedScoreEntry.getKey(), correctedScoreEntry.getValue());
}
correctedScoresEntryIter.remove();
@@ -163,14 +134,32 @@ public class LeaderboardImplWithDelayedCarriedPoints extends LeaderboardImpl {
displayNamesByCompetitorName.entrySet().iterator(); displayNamesEntryIter.hasNext();) {
java.util.Map.Entry<String, String> displayNamesEntry = displayNamesEntryIter.next();
if (competitorsByName.containsKey(displayNamesEntry.getKey())) {
setDisplayName(competitorsByName.get(displayNamesEntry.getKey()), displayNamesEntry.getValue());
leaderboard.setDisplayName(competitorsByName.get(displayNamesEntry.getKey()), displayNamesEntry.getValue());
displayNamesEntryIter.remove();
}
}
removeAsListenerIfNoLeftOvers();
}
private void removeAsListenerIfNoLeftOvers() {
if (carriedPointsByCompetitorName.isEmpty() && maxPointsReasonsByCompetitorName.isEmpty() &&
correctedScoresByCompetitorName.isEmpty() && displayNamesByCompetitorName.isEmpty()) {
getLeaderboard().removeRaceColumnListener(this);
}
}
@Override
public void setDisplayName(String competitorName, String displayName) {
assertNoTrackedRaceAssociatedYet();
displayNamesByCompetitorName.put(competitorName, displayName);
}
@Override
public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
assignLeftOvers(trackedRace);
}
@Override
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
}
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.persistence.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@@ -15,31 +16,55 @@ import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaRegistry;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
import com.sap.sailing.domain.base.impl.EventImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.base.impl.VenueImpl;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.RGBColor;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
import com.sap.sailing.domain.leaderboard.impl.LowerScoreIsBetter;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.WindImpl;
@@ -113,14 +138,14 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Leaderboard loadLeaderboard(String name) {
public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry) {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
Leaderboard result = null;
try {
BasicDBObject query = new BasicDBObject();
query.put(FieldNames.LEADERBOARD_NAME.name(), name);
for (DBObject o : leaderboardCollection.find(query)) {
result = loadLeaderboard(o);
result = loadLeaderboard(o, regattaRegistry);
}
} catch (Throwable t) {
// something went wrong during DB access; report, then use empty new wind track
@@ -131,12 +156,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Iterable<Leaderboard> getAllLeaderboards() {
public Iterable<Leaderboard> getAllLeaderboards(RegattaRegistry regattaRegistry) {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
Set<Leaderboard> result = new HashSet<Leaderboard>();
try {
for (DBObject o : leaderboardCollection.find()) {
result.add(loadLeaderboard(o));
final Leaderboard loadedLeaderboard = loadLeaderboard(o, regattaRegistry);
if (loadedLeaderboard != null) {
result.add(loadedLeaderboard);
}
}
} catch (Throwable t) {
// something went wrong during DB access; report, then use empty new wind track
@@ -146,68 +174,165 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result;
}
private Leaderboard loadLeaderboard(DBObject o) {
/**
* If the DBObject has a field {@link FieldNames#REGATTA_NAME} then the object represents a
* {@link RegattaLeaderboard}. Otherwise, a {@link FlexibleLeaderboard} will be loaded.
*
* @return <code>null</code> in case the leaderboard couldn't be loaded, e.g., because the regatta referenced by a
* {@link RegattaLeaderboard} cannot be found; the leaderboard loaded, otherwise
*/
private Leaderboard loadLeaderboard(DBObject dbLeaderboard, RegattaRegistry regattaRegistry) {
SettableScoreCorrection scoreCorrection = new ScoreCorrectionImpl();
BasicDBList dbDiscardIndexResultsStartingWithHowManyRaces = (BasicDBList) o.get(FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS.name());
BasicDBList dbDiscardIndexResultsStartingWithHowManyRaces = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS.name());
int[] discardIndexResultsStartingWithHowManyRaces = new int[dbDiscardIndexResultsStartingWithHowManyRaces.size()];
int i=0;
for (Object discardingThresholdAsObject : dbDiscardIndexResultsStartingWithHowManyRaces) {
discardIndexResultsStartingWithHowManyRaces[i++] = (Integer) discardingThresholdAsObject;
}
ThresholdBasedResultDiscardingRule resultDiscardingRule = new ResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces);
LeaderboardImplWithDelayedCarriedPoints result = new LeaderboardImplWithDelayedCarriedPoints(
(String) o.get(FieldNames.LEADERBOARD_NAME.name()), scoreCorrection, resultDiscardingRule);
BasicDBList dbRaceColumns = (BasicDBList) o.get(FieldNames.LEADERBOARD_COLUMNS.name());
String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name());
Leaderboard result;
if (regattaName == null) {
result = loadFlexibleLeaderboard(dbLeaderboard, scoreCorrection, resultDiscardingRule);
} else {
result = loadRegattaLeaderboard(regattaName, dbLeaderboard, scoreCorrection, resultDiscardingRule, regattaRegistry);
}
if (result != null) {
DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result);
loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections);
}
return result;
}
/**
* @return <code>null</code> if the regatta cannot be resolved; otherwise the leaderboard for the regatta specified
*/
private RegattaLeaderboard loadRegattaLeaderboard(String regattaName, DBObject dbLeaderboard, SettableScoreCorrection scoreCorrection,
ThresholdBasedResultDiscardingRule resultDiscardingRule, RegattaRegistry regattaRegistry) {
RegattaLeaderboard result = null;
Regatta regatta = regattaRegistry.getRegatta(new RegattaName(regattaName));
if (regatta == null) {
logger.info("Couldn't find regatta "+regattaName+" for corresponding regatta leaderboard. Not loading regatta leaderboard.");
} else {
result = new RegattaLeaderboardImpl(regatta, scoreCorrection, resultDiscardingRule, new LowerScoreIsBetter());
}
return result;
}
private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard,
SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
FlexibleLeaderboardImpl result = new FlexibleLeaderboardImpl(
(String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name()), scoreCorrection, resultDiscardingRule, new LowerScoreIsBetter());
BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name());
// 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 (Object dbRaceColumnAsObject : dbRaceColumns) {
BasicDBObject dbRaceColumn = (BasicDBObject) dbRaceColumnAsObject;
Map<String, RaceIdentifier> raceIdentifiers = loadRaceIdentifiers(dbRaceColumn);
RaceIdentifier defaultFleetRaceIdentifier = raceIdentifiers.get(null);
if (defaultFleetRaceIdentifier != null) {
Fleet defaultFleet = result.getFleet(null);
if (defaultFleet != null) {
raceIdentifiers.put(defaultFleet.getName(), 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));
}
RaceColumn raceColumn = result.addRaceColumn((String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()),
(Boolean) dbRaceColumn.get(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name()));
raceColumn.setRaceIdentifier(loadRaceIdentifier(dbRaceColumn));
(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());
}
}
DBObject carriedPoints = (DBObject) o.get(FieldNames.LEADERBOARD_CARRIED_POINTS.name());
return result;
}
private void loadLeaderboardCorrections(DBObject dbLeaderboard, DelayedLeaderboardCorrections correctionsToUpdate) {
DBObject carriedPoints = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_CARRIED_POINTS.name());
if (carriedPoints != null) {
for (String competitorName : carriedPoints.keySet()) {
Integer carriedPointsForCompetitor = (Integer) carriedPoints.get(competitorName);
if (carriedPointsForCompetitor != null) {
result.setCarriedPoints(MongoUtils.unescapeDollarAndDot(competitorName), carriedPointsForCompetitor);
correctionsToUpdate.setCarriedPoints(MongoUtils.unescapeDollarAndDot(competitorName), carriedPointsForCompetitor);
}
}
}
DBObject dbScoreCorrection = (DBObject) o.get(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name());
DBObject dbScoreCorrection = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name());
for (String raceName : dbScoreCorrection.keySet()) {
DBObject dbScoreCorrectionForRace = (DBObject) dbScoreCorrection.get(raceName);
for (String competitorName : dbScoreCorrectionForRace.keySet()) {
RaceColumn raceColumn = result.getRaceColumnByName(raceName);
RaceColumn raceColumn = correctionsToUpdate.getLeaderboard().getRaceColumnByName(raceName);
DBObject dbScoreCorrectionForCompetitorInRace = (DBObject) dbScoreCorrectionForRace.get(competitorName);
if (dbScoreCorrectionForCompetitorInRace.containsField(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())) {
result.setMaxPointsReason(MongoUtils.unescapeDollarAndDot(competitorName), raceColumn, MaxPointsReason
correctionsToUpdate.setMaxPointsReason(MongoUtils.unescapeDollarAndDot(competitorName), raceColumn, MaxPointsReason
.valueOf((String) dbScoreCorrectionForCompetitorInRace
.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name())));
}
if (dbScoreCorrectionForCompetitorInRace.containsField(FieldNames.LEADERBOARD_CORRECTED_SCORE.name())) {
result.correctScore(MongoUtils.unescapeDollarAndDot(competitorName), raceColumn, (Integer) dbScoreCorrectionForCompetitorInRace
correctionsToUpdate.correctScore(MongoUtils.unescapeDollarAndDot(competitorName), raceColumn, (Integer) dbScoreCorrectionForCompetitorInRace
.get(FieldNames.LEADERBOARD_CORRECTED_SCORE.name()));
}
}
}
DBObject competitorDisplayNames = (DBObject) o.get(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name());
DBObject competitorDisplayNames = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name());
if (competitorDisplayNames != null) {
for (String escapedCompetitorName : competitorDisplayNames.keySet()) {
result.setDisplayName(MongoUtils.unescapeDollarAndDot(escapedCompetitorName), (String) competitorDisplayNames.get(escapedCompetitorName));
correctionsToUpdate.setDisplayName(MongoUtils.unescapeDollarAndDot(escapedCompetitorName), (String) competitorDisplayNames.get(escapedCompetitorName));
}
}
}
/**
* Expects a DBObject under the key {@link FieldNames#RACE_IDENTIFIERS} whose keys are the fleet names and whose
* values are the race identifiers as DBObjects (see {@link #loadRaceIdentifier(DBObject)}). If legacy DB instances
* have a {@link RaceIdentifier} that is not associated with a fleet name, it may be stored directly in the
* <code>dbRaceColumn</code>. In this case, it is returned with <code>null</code> as the fleet name key.
*
* @return a map with fleet names as key and the corresponding fleet's race identifier as value; the special
* <code>null</code> key is used to identify a "default fleet" for backward compatibility with stored
* leaderboards which don't know about fleets yet; this key should be mapped to the leaderboard's default
* fleet.
*/
private Map<String, RaceIdentifier> loadRaceIdentifiers(DBObject dbRaceColumn) {
Map<String, RaceIdentifier> result = new HashMap<String, RaceIdentifier>();
// try to load a deprecated single race identifier to associate with the default fleet:
RaceIdentifier singleLegacyRaceIdentifier = loadRaceIdentifier(dbRaceColumn);
if (singleLegacyRaceIdentifier != null) {
result.put(null, singleLegacyRaceIdentifier);
}
DBObject raceIdentifiersPerFleet = (DBObject) dbRaceColumn.get(FieldNames.RACE_IDENTIFIERS.name());
if (raceIdentifiersPerFleet != null) {
for (String fleetName : raceIdentifiersPerFleet.keySet()) {
result.put(fleetName, loadRaceIdentifier((DBObject) raceIdentifiersPerFleet.get(fleetName)));
}
}
return result;
}
@Override
public LeaderboardGroup loadLeaderboardGroup(String name) {
public LeaderboardGroup loadLeaderboardGroup(String name, RegattaRegistry regattaRegistry) {
DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
LeaderboardGroup leaderboardGroup = null;
try {
BasicDBObject query = new BasicDBObject();
query.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), name);
leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query));
leaderboardGroup = loadLeaderboardGroup(leaderboardGroupCollection.findOne(query), regattaRegistry);
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard group "+name+".");
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadLeaderboardGroup", t);
@@ -217,13 +342,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
@Override
public Iterable<LeaderboardGroup> getAllLeaderboardGroups() {
public Iterable<LeaderboardGroup> getAllLeaderboardGroups(RegattaRegistry regattaRegistry) {
DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
Set<LeaderboardGroup> leaderboardGroups = new HashSet<LeaderboardGroup>();
try {
for (DBObject o : leaderboardGroupCollection.find()) {
leaderboardGroups.add(loadLeaderboardGroup(o));
leaderboardGroups.add(loadLeaderboardGroup(o, regattaRegistry));
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard groups.");
@@ -233,34 +358,36 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return leaderboardGroups;
}
private LeaderboardGroup loadLeaderboardGroup(DBObject o) {
private LeaderboardGroup loadLeaderboardGroup(DBObject o, RegattaRegistry regattaRegistry) {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
String name = (String) o.get(FieldNames.LEADERBOARD_GROUP_NAME.name());
String description = (String) o.get(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name());
ArrayList<Leaderboard> leaderboards = new ArrayList<Leaderboard>();
BasicDBList dbLeaderboardIds = (BasicDBList) o.get(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name());
for (Object object : dbLeaderboardIds) {
ObjectId dbLeaderboardId = (ObjectId) object;
DBObject dbLeaderboard = leaderboardCollection.findOne(dbLeaderboardId);
leaderboards.add(loadLeaderboard(dbLeaderboard));
final Leaderboard loadedLeaderboard = loadLeaderboard(dbLeaderboard, regattaRegistry);
if (loadedLeaderboard != null) {
leaderboards.add(loadedLeaderboard);
}
}
return new LeaderboardGroupImpl(name, description, leaderboards);
}
@Override
public Iterable<Leaderboard> getLeaderboardsNotInGroup() {
public Iterable<Leaderboard> getLeaderboardsNotInGroup(RegattaRegistry regattaRegistry) {
DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
Set<Leaderboard> result = new HashSet<Leaderboard>();
try {
//Don't change the query object, unless you know what you're doing
BasicDBObject query = new BasicDBObject("$where", "function() { return db." + CollectionNames.LEADERBOARD_GROUPS.name() + ".find({ "
+ FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name() + ": this._id }).count() == 0; }");
for (DBObject o : leaderboardCollection.find(query)) {
result.add(loadLeaderboard(o));
final Leaderboard loadedLeaderboard = loadLeaderboard(o, regattaRegistry);
if (loadedLeaderboard != null) {
result.add(loadedLeaderboard);
}
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboards.");
@@ -304,4 +431,195 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result;
}
@Override
public Event loadEvent(String name) {
Event result;
BasicDBObject query = new BasicDBObject();
query.put(FieldNames.EVENT_NAME.name(), name);
DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
DBObject eventDBObject = eventCollection.findOne(query);
if (eventDBObject != null) {
result = loadEvent(eventDBObject);
} else {
result = null;
}
return result;
}
@Override
public Iterable<Event> loadAllEvents() {
ArrayList<Event> result = new ArrayList<Event>();
DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
try {
for (DBObject o : eventCollection.find()) {
result.add(loadEvent(o));
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events.");
logger.throwing(DomainObjectFactoryImpl.class.getName(), "loadEvents", t);
}
return result;
}
/**
* An event doesn't store its regattas; it's the regatta that stores a reference to its event; the regatta
* needs to add itself to the event when loaded or instantiated.
*/
private Event loadEvent(DBObject eventDBObject) {
String name = (String) eventDBObject.get(FieldNames.EVENT_NAME.name());
Venue venue = loadVenue((DBObject) eventDBObject.get(FieldNames.VENUE.name()));
Event result = new EventImpl(name, venue);
return result;
}
private Venue loadVenue(DBObject dbObject) {
String name = (String) dbObject.get(FieldNames.VENUE_NAME.name());
BasicDBList dbCourseAreas = (BasicDBList) dbObject.get(FieldNames.COURSE_AREAS.name());
Venue result = new VenueImpl(name);
for (Object courseAreaDBObject : dbCourseAreas) {
CourseArea courseArea = loadCourseArea((DBObject) courseAreaDBObject);
result.addCourseArea(courseArea);
}
return result;
}
private CourseArea loadCourseArea(DBObject courseAreaDBObject) {
String name = (String) courseAreaDBObject.get(FieldNames.COURSE_AREA_NAME.name());
return new CourseAreaImpl(name);
}
@Override
public Iterable<Regatta> loadAllRegattas(TrackedRegattaRegistry trackedRegattaRegistry) {
List<Regatta> result = new ArrayList<Regatta>();
DBCollection regattaCollection = database.getCollection(CollectionNames.REGATTAS.name());
for (DBObject dbRegatta : regattaCollection.find()) {
result.add(loadRegatta(dbRegatta, trackedRegattaRegistry));
}
return result;
}
@Override
public Regatta loadRegatta(String name, TrackedRegattaRegistry trackedRegattaRegistry) {
DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), name);
DBCollection regattaCollection = database.getCollection(CollectionNames.REGATTAS.name());
DBObject dbRegatta = regattaCollection.findOne(query);
Regatta result = loadRegatta(dbRegatta, trackedRegattaRegistry);
assert result == null || result.getName().equals(name);
return result;
}
private Regatta loadRegatta(DBObject dbRegatta, TrackedRegattaRegistry trackedRegattaRegistry) {
Regatta result = null;
if (dbRegatta != null) {
String baseName = (String) dbRegatta.get(FieldNames.REGATTA_BASE_NAME.name());
String boatClassName = (String) dbRegatta.get(FieldNames.BOAT_CLASS_NAME.name());
BoatClass boatClass = null;
if (boatClassName != null) {
boolean typicallyStartsUpwind = (Boolean) dbRegatta.get(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name());
boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass(boatClassName, typicallyStartsUpwind);
}
BasicDBList dbSeries = (BasicDBList) dbRegatta.get(FieldNames.REGATTA_SERIES.name());
Iterable<Series> series = loadSeries(dbSeries, trackedRegattaRegistry);
result = new RegattaImpl(baseName, boatClass, series, /* persistent */ true);
}
return result;
}
private Iterable<Series> loadSeries(BasicDBList dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) {
List<Series> result = new ArrayList<Series>();
for (Object o : dbSeries) {
DBObject oneDBSeries = (DBObject) o;
Series series = loadSeries(oneDBSeries, trackedRegattaRegistry);
result.add(series);
}
return result;
}
private Series loadSeries(DBObject dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) {
String name = (String) dbSeries.get(FieldNames.SERIES_NAME.name());
boolean isMedal = (Boolean) dbSeries.get(FieldNames.SERIES_IS_MEDAL.name());
final BasicDBList dbFleets = (BasicDBList) dbSeries.get(FieldNames.SERIES_FLEETS.name());
Map<String, Fleet> fleetsByName = loadFleets(dbFleets);
BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name());
Iterable<String> raceColumnNames = loadRaceColumnNames(dbRaceColumns, fleetsByName);
Series series = new SeriesImpl(name, isMedal, fleetsByName.values(), raceColumnNames, trackedRegattaRegistry);
loadRaceColumnRaceLinks(dbRaceColumns, series);
return series;
}
/**
* @param fleetsByName used to ensure the {@link RaceColumn#getFleets()} points to the same {@link Fleet} objects also
* used in the {@link Series#getFleets()} collection.
*/
private Iterable<String> loadRaceColumnNames(BasicDBList dbRaceColumns, Map<String, Fleet> fleetsByName) {
List<String> result = new ArrayList<String>();
for (Object o : dbRaceColumns) {
DBObject dbRaceColumn = (DBObject) o;
result.add((String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name()));
}
return result;
}
private void loadRaceColumnRaceLinks(BasicDBList dbRaceColumns, Series series) {
for (Object o : dbRaceColumns) {
DBObject dbRaceColumn = (DBObject) o;
String name = (String) dbRaceColumn.get(FieldNames.LEADERBOARD_COLUMN_NAME.name());
Map<String, RaceIdentifier> raceIdentifiersPerFleetName = loadRaceIdentifiers(dbRaceColumn);
for (Map.Entry<String, RaceIdentifier> e : raceIdentifiersPerFleetName.entrySet()) {
// null key for "default" fleet is not acceptable here
if (e.getKey() == null) {
logger.warning("Ignoring null fleet name while loading RaceColumn " + name);
} else {
series.getRaceColumnByName(name).setRaceIdentifier(series.getFleetByName(e.getKey()), e.getValue());
}
}
}
}
private Map<String, Fleet> loadFleets(BasicDBList dbFleets) {
Map<String, Fleet> result = new HashMap<String, Fleet>();
for (Object o : dbFleets) {
DBObject dbFleet = (DBObject) o;
Fleet fleet = loadFleet(dbFleet);
result.put(fleet.getName(), fleet);
}
return result;
}
private Fleet loadFleet(DBObject dbFleet) {
Fleet result;
String name = (String) dbFleet.get(FieldNames.FLEET_NAME.name());
Integer ordering = (Integer) dbFleet.get(FieldNames.FLEET_ORDERING.name());
if (ordering == null) {
ordering = 0;
}
Integer colorAsInt = (Integer) dbFleet.get(FieldNames.FLEET_COLOR.name());
Color color = null;
if (colorAsInt != null) {
int r = colorAsInt % 256;
int g = (colorAsInt / 256 ) % 256;
int b = (colorAsInt / 256 / 256) % 256;
color = new RGBColor(r, g, b);
}
result = new FleetImpl(name, ordering, color);
return result;
}
@Override
public Map<String, Regatta> loadRaceIDToRegattaAssociations(RegattaRegistry regattaRegistry) {
DBCollection raceIDToRegattaCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
Map<String, Regatta> result = new HashMap<String, Regatta>();
for (DBObject o : raceIDToRegattaCollection.find()) {
Regatta regatta = regattaRegistry.getRegattaByName((String) o.get(FieldNames.REGATTA_NAME.name()));
if (regatta != null) {
result.put((String) o.get(FieldNames.RACE_ID_AS_STRING.name()), regatta);
} else {
logger.warning("Couldn't find regatta " + o.get(FieldNames.REGATTA_NAME.name())
+ ". Cannot restore race associations with this regatta.");
}
}
return result;
}
}
@@ -4,13 +4,35 @@ public enum FieldNames {
// general:
TIME_AS_MILLIS, KNOT_SPEED, DEGREE_BEARING, LAT_DEG, LNG_DEG,
FLEET, RACE_IDENTIFIERS,
/**
* TODO this will have to be renamed to REGATTA_NAME, but compatibility concerns keep us from doing so at the moment
*/
EVENT_NAME,
REGATTA_NAME,
REGATTA_BASE_NAME,
REGATTA_SERIES,
SERIES_NAME,
SERIES_IS_MEDAL,
SERIES_FLEETS,
SERIES_RACE_COLUMNS,
FLEET_NAME,
FLEET_ORDERING,
FLEET_COLOR,
RACE_NAME,
VENUE,
VENUE_NAME,
COURSE_AREAS,
COURSE_AREA_NAME,
BOAT_CLASS_NAME,
BOAT_CLASS_TYPICALLY_STARTS_UPWIND,
// wind:
WIND_SOURCE_NAME, WIND_SOURCE_ID, WIND,
@@ -22,5 +44,8 @@ public enum FieldNames {
LEADERBOARD_CARRIED_POINTS, LEADERBOARD_SCORE_CORRECTIONS, LEADERBOARD_DISCARDING_THRESHOLDS, LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON, LEADERBOARD_CORRECTED_SCORE,
// leaderboard group
LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS
LEADERBOARD_GROUP_NAME, LEADERBOARD_GROUP_DESCRIPTION, LEADERBOARD_GROUP_LEADERBOARDS,
// race ID to regatta name
RACE_ID_AS_STRING
}
@@ -10,23 +10,32 @@ import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.tracking.Positioned;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindTrack;
@@ -96,8 +105,20 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
return result;
}
@Override
public void storeRaceIdentifier(RaceIdentifier raceIdentifier, DBObject dbObject) {
private void storeRaceIdentifiers(RaceColumn raceColumn, DBObject dbObject) {
BasicDBObject raceIdentifiersPerFleet = new BasicDBObject();
for (Fleet fleet : raceColumn.getFleets()) {
RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet);
if (raceIdentifier != null) {
DBObject raceIdentifierForFleet = new BasicDBObject();
storeRaceIdentifier(raceIdentifierForFleet, raceIdentifier);
raceIdentifiersPerFleet.put(fleet.getName(), raceIdentifierForFleet);
}
}
dbObject.put(FieldNames.RACE_IDENTIFIERS.name(), raceIdentifiersPerFleet);
}
private void storeRaceIdentifier(DBObject dbObject, RaceIdentifier raceIdentifier) {
if (raceIdentifier != null) {
dbObject.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
dbObject.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
@@ -114,32 +135,46 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
logger.throwing(MongoObjectFactoryImpl.class.getName(), "storeLeaderboard", npe);
}
BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
BasicDBObject result = new BasicDBObject();
result.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
BasicDBObject dbLeaderboard = new BasicDBObject();
dbLeaderboard.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
if (leaderboard instanceof FlexibleLeaderboard) {
storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
} else if (leaderboard instanceof RegattaLeaderboard) {
storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
}
storeLeaderboardCorrections(leaderboard, dbLeaderboard);
leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false);
}
private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
}
private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, BasicDBObject dbLeaderboard) {
BasicDBList dbRaceColumns = new BasicDBList();
result.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns);
dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns);
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
BasicDBObject dbRaceColumn = new BasicDBObject();
dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
storeRaceIdentifier(raceColumn.getRaceIdentifier(), dbRaceColumn);
BasicDBObject dbRaceColumn = storeRaceColumn(raceColumn);
dbRaceColumns.add(dbRaceColumn);
}
}
private void storeLeaderboardCorrections(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
if (leaderboard.hasCarriedPoints()) {
BasicDBObject dbCarriedPoints = new BasicDBObject();
result.put(FieldNames.LEADERBOARD_CARRIED_POINTS.name(), dbCarriedPoints);
dbLeaderboard.put(FieldNames.LEADERBOARD_CARRIED_POINTS.name(), dbCarriedPoints);
for (Competitor competitor : leaderboard.getCompetitors()) {
dbCarriedPoints.put(MongoUtils.escapeDollarAndDot(competitor.getName()), leaderboard.getCarriedPoints(competitor));
}
}
BasicDBObject dbScoreCorrections = new BasicDBObject();
storeScoreCorrections(leaderboard, dbScoreCorrections);
result.put(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name(), dbScoreCorrections);
dbLeaderboard.put(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name(), dbScoreCorrections);
BasicDBList dbResultDiscardingThresholds = new BasicDBList();
for (int threshold : leaderboard.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces()) {
dbResultDiscardingThresholds.add(threshold);
}
result.put(FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS.name(), dbResultDiscardingThresholds);
dbLeaderboard.put(FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS.name(), dbResultDiscardingThresholds);
BasicDBObject competitorDisplayNames = new BasicDBObject();
for (Competitor competitor : leaderboard.getCompetitors()) {
String displayNameForCompetitor = leaderboard.getDisplayName(competitor);
@@ -147,8 +182,15 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
competitorDisplayNames.put(MongoUtils.escapeDollarAndDot(competitor.getName()), displayNameForCompetitor);
}
}
result.put(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name(), competitorDisplayNames);
leaderboardCollection.update(query, result, /* upsrt */ true, /* multi */ false);
dbLeaderboard.put(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name(), competitorDisplayNames);
}
private BasicDBObject storeRaceColumn(RaceColumn raceColumn) {
BasicDBObject dbRaceColumn = new BasicDBObject();
dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
storeRaceIdentifiers(raceColumn, dbRaceColumn);
return dbRaceColumn;
}
private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) {
@@ -240,7 +282,103 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName));
leaderboardGroupCollection.update(query, update);
}
@Override
public void storeEvent(Event event) {
DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
eventCollection.ensureIndex(FieldNames.EVENT_NAME.name());
DBObject query = new BasicDBObject();
query.put(FieldNames.EVENT_NAME.name(), event.getName());
DBObject eventDBObject = new BasicDBObject();
eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false);
}
private DBObject getVenueAsDBObject(Venue venue) {
DBObject result = new BasicDBObject();
result.put(FieldNames.VENUE_NAME.name(), venue.getName());
BasicDBList courseAreaList = new BasicDBList();
result.put(FieldNames.COURSE_AREAS.name(), courseAreaList);
for (CourseArea courseArea : venue.getCourseAreas()) {
DBObject dbCourseArea = new BasicDBObject();
courseAreaList.add(dbCourseArea);
dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName());
}
return result;
}
@Override
public void storeRegatta(Regatta regatta) {
DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
regattasCollection.ensureIndex(FieldNames.REGATTA_NAME.name());
DBObject dbRegatta = new BasicDBObject();
DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
dbRegatta.put(FieldNames.REGATTA_BASE_NAME.name(), regatta.getBaseName());
if (regatta.getBoatClass() != null) {
dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName());
dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind());
}
dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries()));
regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false);
}
@Override
public void removeRegatta(Regatta regatta) {
DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
regattasCollection.remove(query);
}
private BasicDBList storeSeries(Iterable<? extends Series> series) {
BasicDBList dbSeries = new BasicDBList();
for (Series s : series) {
dbSeries.add(storeSeries(s));
}
return dbSeries;
}
private DBObject storeSeries(Series s) {
DBObject dbSeries = new BasicDBObject();
dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName());
dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal());
BasicDBList dbFleets = new BasicDBList();
for (Fleet fleet : s.getFleets()) {
dbFleets.add(storeFleet(fleet));
}
dbSeries.put(FieldNames.SERIES_FLEETS.name(), dbFleets);
BasicDBList dbRaceColumns = new BasicDBList();
for (RaceColumn raceColumn : s.getRaceColumns()) {
dbRaceColumns.add(storeRaceColumn(raceColumn));
}
dbSeries.put(FieldNames.SERIES_RACE_COLUMNS.name(), dbRaceColumns);
return dbSeries;
}
private DBObject storeFleet(Fleet fleet) {
DBObject dbFleet = new BasicDBObject(FieldNames.FLEET_NAME.name(), fleet.getName());
if (fleet instanceof FleetImpl) {
dbFleet.put(FieldNames.FLEET_ORDERING.name(), ((FleetImpl) fleet).getOrdering());
if(fleet.getColor() != null) {
Triple<Integer, Integer, Integer> colorAsRGB = fleet.getColor().getAsRGB();
// we save the color as a integer value representing the RGB values
int colorAsInt = (256 * 256 * colorAsRGB.getC()) + colorAsRGB.getB() * 256 + colorAsRGB.getA();
dbFleet.put(FieldNames.FLEET_COLOR.name(), colorAsInt);
} else {
dbFleet.put(FieldNames.FLEET_COLOR.name(), null);
}
}
return dbFleet;
}
@Override
public void storeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
DBObject entry = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
entry.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
regattaForRaceIDCollection.update(query, entry, /* upsrt */ true, /* multi */ false);
}
}
@@ -13,7 +13,8 @@ Require-Bundle: com.sap.sailing.domain.swisstimingadapter.persistence,
com.sap.sailing.mongodb,
com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.server,
com.sap.sailing.domain.common
com.sap.sailing.domain.common,
com.sap.sailing.domain.persistence
Bundle-ClassPath: resources/,
.
Import-Package: com.sap.sailing.server,
@@ -69,6 +69,7 @@ public class EndToEndListeningStoreAndFowardTest {
logger.info("EndToEndListeningStoreAndFowardTest.setUp");
MongoDBService mongoDBService = MongoDBService.INSTANCE;
db = mongoDBService.getDB();
db.getCollection(com.sap.sailing.domain.persistence.impl.CollectionNames.REGATTAS.name()).drop();
swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE;
swissTimingAdapterPersistence.dropAllMessageData();
swissTimingAdapterPersistence.dropAllRaceMasterData();
@@ -83,7 +84,7 @@ public class EndToEndListeningStoreAndFowardTest {
lastMessageCountCollection.update(new BasicDBObject(),
new BasicDBObject().append(FieldNames.LAST_MESSAGE_COUNT.name(), 0l),
/* upsert */true, /* multi */false);
racingEventService = new RacingEventServiceImpl();
racingEventService = new RacingEventServiceImpl(mongoDBService);
raceHandles = new ArrayList<RacesHandle>();
}
@@ -105,9 +106,9 @@ public class EndToEndListeningStoreAndFowardTest {
setUpUsingScript(racesToTrack, scriptName);
Set<TrackedRace> allTrackedRaces = new HashSet<TrackedRace>();
Iterable<Regatta> allEvents = racingEventService.getAllRegattas();
for (Regatta event : allEvents) {
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(event);
Iterable<Regatta> allRegattas = racingEventService.getAllRegattas();
for (Regatta regatta : allRegattas) {
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(regatta);
Iterable<TrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
for (TrackedRace trackedRace : trackedRaces) {
allTrackedRaces.add(trackedRace);
@@ -134,9 +135,9 @@ public class EndToEndListeningStoreAndFowardTest {
setUpUsingScript(racesToTrack, scriptName1, scriptName2);
Set<TrackedRace> allTrackedRaces = new HashSet<TrackedRace>();
Iterable<Regatta> allEvents = racingEventService.getAllRegattas();
for (Regatta event : allEvents) {
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(event);
Iterable<Regatta> allRegattas = racingEventService.getAllRegattas();
for (Regatta regatta : allRegattas) {
DynamicTrackedRegatta trackedRegatta = racingEventService.getTrackedRegatta(regatta);
Iterable<TrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
for (TrackedRace trackedRace : trackedRaces) {
allTrackedRaces.add(trackedRace);
@@ -290,8 +291,9 @@ public class EndToEndListeningStoreAndFowardTest {
private void setUpUsingScript(String[] racesToTrack, String... scriptNames) throws Exception {
for (String raceToTrack : racesToTrack) {
RacesHandle raceHandle = racingEventService.addSwissTimingRace(raceToTrack, "localhost", CLIENT_PORT, /* canSendRequests */
false, emptyWindStore, -1);
RacesHandle raceHandle = racingEventService.addSwissTimingRace(/* regattaToAddTo */ null /* use a default regatta */,
raceToTrack, "localhost", /* canSendRequests */
CLIENT_PORT, false, emptyWindStore, -1);
raceHandles.add(raceHandle);
if (connector == null) {
connector = racingEventService.getSwissTimingFactory().getOrCreateSailMasterConnector("localhost",
@@ -9,8 +9,8 @@ import java.util.List;
import org.junit.Test;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.common.impl.Util;
@@ -24,6 +24,7 @@ import com.sap.sailing.domain.swisstimingadapter.impl.CourseImpl;
import com.sap.sailing.domain.swisstimingadapter.impl.MarkImpl;
import com.sap.sailing.domain.swisstimingadapter.impl.RaceImpl;
import com.sap.sailing.domain.swisstimingadapter.impl.StartListImpl;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import difflib.PatchFailedException;
@@ -31,7 +32,7 @@ public class SimpleDomainFactoryTest {
@Test
public void testCourseConfigForBuoy() throws PatchFailedException {
DomainFactory domainFactory = DomainFactory.INSTANCE;
Regatta regatta = new RegattaImpl("TestEvent", /* boatClass */ null);
Regatta regatta = new RegattaImpl("TestEvent", /* boatClass */ null, new RacingEventServiceImpl());
Race race = new RaceImpl("1234", "Race 1234");
Iterable<Competitor> competitors = Collections.emptyList();
StartList startList = new StartListImpl("1234", competitors);
@@ -55,7 +56,7 @@ public class SimpleDomainFactoryTest {
@Test
public void testCourseConfigForGate() throws PatchFailedException {
DomainFactory domainFactory = DomainFactory.INSTANCE;
Regatta regatta = new RegattaImpl("TestEvent", /* boatClass */ null);
Regatta regatta = new RegattaImpl("TestEvent", /* boatClass */ null, new RacingEventServiceImpl());
Race race = new RaceImpl("1234", "Race 1234");
Iterable<Competitor> competitors = Collections.emptyList();
StartList startList = new StartListImpl("1234", competitors);
@@ -13,6 +13,7 @@ import com.sap.sailing.domain.swisstimingadapter.impl.DomainFactoryImpl;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import difflib.PatchFailedException;
@@ -20,7 +21,7 @@ import difflib.PatchFailedException;
public interface DomainFactory {
final static DomainFactory INSTANCE = new DomainFactoryImpl(com.sap.sailing.domain.base.DomainFactory.INSTANCE);
Regatta getOrCreateEvent(String raceID);
Regatta getOrCreateRegatta(String raceID, TrackedRegattaRegistry trackedRegattaRegistry);
Nationality getOrCreateNationality(String nationalityName);
@@ -40,7 +41,8 @@ public interface DomainFactory {
void removeRace(String raceID);
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests,
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID,
boolean canSendRequests, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, WindStore windStore,
RaceSpecificMessageLoader messageLoader);
}
@@ -4,8 +4,10 @@ import java.io.IOException;
import java.net.UnknownHostException;
import java.text.ParseException;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.swisstimingadapter.impl.SwissTimingFactoryImpl;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
@@ -49,9 +51,15 @@ public interface SwissTimingFactory {
SwissTimingConfiguration createSwissTimingConfiguration(String name, String hostname, int port, boolean canSendRequests);
SwissTimingRaceTracker createRaceTracker(String raceID, String hostname, int port, boolean canSendRequests, WindStore windStore, RaceSpecificMessageLoader messageLoader, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, IOException, ParseException;
SwissTimingRaceTracker createRaceTracker(String raceID, String hostname, int port, boolean canSendRequests, long delayToLiveInMillis, WindStore windStore, RaceSpecificMessageLoader messageLoader, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, IOException, ParseException;
RaceTracker createRaceTracker(Regatta regatta, String raceID, String hostname, int port, boolean canSendRequests, long delayToLiveInMillis,
WindStore windStore, RaceSpecificMessageLoader messageLoader, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, InterruptedException,
IOException, ParseException;
Race createRace(String raceId, String description, TimePoint startTime);
SailMasterMessage createMessage(String message, Long sequenceNumber);
}
@@ -7,24 +7,25 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Boat;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.Person;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
@@ -40,6 +41,7 @@ import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
@@ -54,7 +56,8 @@ import difflib.PatchFailedException;
*
*/
public class DomainFactoryImpl implements DomainFactory {
private final Map<String, Regatta> raceIDToEventCache;
private final static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
private final Map<String, Regatta> raceIDToRegattaCache;
private final Map<Iterable<String>, ControlPoint> controlPointCache;
private final Map<String, BoatClass> olympicClassesByID;
private final BoatClass unknownBoatClass;
@@ -62,7 +65,7 @@ public class DomainFactoryImpl implements DomainFactory {
public DomainFactoryImpl(com.sap.sailing.domain.base.DomainFactory baseDomainFactory) {
this.baseDomainFactory = baseDomainFactory;
raceIDToEventCache = new HashMap<String, Regatta>();
raceIDToRegattaCache = new HashMap<String, Regatta>();
controlPointCache = new HashMap<Iterable<String>, ControlPoint>();
olympicClassesByID = new HashMap<String, BoatClass>();
/*
@@ -89,11 +92,15 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public Regatta getOrCreateEvent(String raceID) {
Regatta result = raceIDToEventCache.get(raceID);
public Regatta getOrCreateRegatta(String raceID, TrackedRegattaRegistry trackedRegattaRegistry) {
Regatta result = trackedRegattaRegistry.getRememberedRegattaForRace(raceID);
if (result == null) {
result = new RegattaImpl(raceID, null);
raceIDToEventCache.put(raceID, result);
result = raceIDToRegattaCache.get(raceID);
}
if (result == null) {
result = new RegattaImpl(raceID, null, trackedRegattaRegistry);
logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+")");
raceIDToRegattaCache.put(raceID, result);
}
return result;
}
@@ -124,6 +131,7 @@ public class DomainFactoryImpl implements DomainFactory {
com.sap.sailing.domain.base.Course domainCourse = createCourse(race.getDescription(), course);
BoatClass boatClass = getOrCreateBoatClassFromRaceID(race.getRaceID());
Iterable<Competitor> competitors = createCompetitorList(startList, boatClass);
logger.info("Creating RaceDefinitionImpl for race "+race.getRaceID());
RaceDefinition result = new RaceDefinitionImpl(race.getRaceID(), domainCourse,
boatClass, competitors);
regatta.addRace(result);
@@ -228,9 +236,9 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public void removeRace(String raceID) {
Regatta regatta = getOrCreateEvent(raceID);
Set<RaceDefinition> toRemove = new HashSet<RaceDefinition>();
Regatta regatta = raceIDToRegattaCache.get(raceID);
if (regatta != null) {
Set<RaceDefinition> toRemove = new HashSet<RaceDefinition>();
for (RaceDefinition race : regatta.getAllRaces()) {
if (race.getName().equals(raceID)) {
toRemove.add(race);
@@ -240,7 +248,7 @@ public class DomainFactoryImpl implements DomainFactory {
regatta.removeRace(raceToRemove);
}
if (Util.isEmpty(regatta.getAllRaces())) {
raceIDToEventCache.remove(raceID);
raceIDToRegattaCache.remove(raceID);
}
}
}
@@ -251,10 +259,11 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests,
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID,
boolean canSendRequests, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, WindStore windStore,
RaceSpecificMessageLoader messageLoader) {
return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, canSendRequests,
return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, canSendRequests, delayToLiveInMillis,
swissTimingFactory, domainFactory, windStore, messageLoader);
}
@@ -6,6 +6,7 @@ import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
@@ -18,6 +19,7 @@ import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingMessageParser;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingRaceTracker;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.WindStore;
@@ -75,12 +77,21 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory {
}
@Override
public SwissTimingRaceTracker createRaceTracker(String raceID, String hostname, int port, boolean canSendRequests,
public SwissTimingRaceTracker createRaceTracker(String raceID, String hostname, int port, boolean canSendRequests, long delayToLiveInMillis,
WindStore windStore, RaceSpecificMessageLoader messageLoader, DomainFactory domainFactory,
TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, IOException,
ParseException {
return new SwissTimingRaceTrackerImpl(raceID, hostname, port, windStore, domainFactory, this, messageLoader,
trackedRegattaRegistry, canSendRequests);
trackedRegattaRegistry, canSendRequests, delayToLiveInMillis);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, String raceID, String hostname, int port,
boolean canSendRequests, long delayToLiveInMillis, WindStore windStore, RaceSpecificMessageLoader messageLoader,
DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException,
InterruptedException, IOException, ParseException {
return new SwissTimingRaceTrackerImpl(regatta, raceID, hostname, port, windStore, domainFactory, this,
messageLoader, trackedRegattaRegistry, canSendRequests, delayToLiveInMillis);
}
@Override
@@ -64,14 +64,25 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
private Course course;
private StartList startList;
private DynamicTrackedRace trackedRace;
private final DynamicTrackedRegatta trackedRegatta;
private boolean loggedIgnore;
private final long delayToLiveInMillis;
protected SwissTimingRaceTrackerImpl(String raceID, String hostname, int port, WindStore windStore,
DomainFactory domainFactory, SwissTimingFactory factory, RaceSpecificMessageLoader messageLoader,
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests) throws InterruptedException,
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests, long delayToLiveInMillis) throws InterruptedException,
UnknownHostException, IOException, ParseException {
this(domainFactory.getOrCreateRegatta(raceID, trackedRegattaRegistry), raceID, hostname, port, windStore, domainFactory, factory,
messageLoader, trackedRegattaRegistry, canSendRequests, delayToLiveInMillis);
}
protected SwissTimingRaceTrackerImpl(Regatta regatta, String raceID, String hostname, int port, WindStore windStore,
DomainFactory domainFactory, SwissTimingFactory factory, RaceSpecificMessageLoader messageLoader,
TrackedRegattaRegistry trackedRegattaRegistry, boolean canSendRequests, long delayToLiveInMillis) throws InterruptedException,
UnknownHostException, IOException, ParseException {
super();
this.regatta = regatta;
this.connector = factory.getOrCreateSailMasterConnector(hostname, port, messageLoader, canSendRequests);
this.domainFactory = domainFactory;
this.raceID = raceID;
@@ -79,11 +90,16 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
this.windStore = windStore;
this.id = createID(raceID, hostname, port);
connector.addSailMasterListener(raceID, this);
regatta = domainFactory.getOrCreateEvent(raceID);
setTrackedRegatta(trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta));
trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta);
this.delayToLiveInMillis = delayToLiveInMillis;
connector.trackRace(raceID);
}
@Override
public DynamicTrackedRegatta getTrackedRegatta() {
return trackedRegatta;
}
static Triple<String, String, Integer> createID(String raceID, String hostname, int port) {
return new Triple<String, String, Integer>(raceID, hostname, port);
}
@@ -118,7 +134,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
}
@Override
public DynamicTrackedRegatta getTrackedEvent() {
public DynamicTrackedRegatta getTrackedRegatta() {
return SwissTimingRaceTrackerImpl.this.getTrackedRegatta();
}
@@ -249,7 +265,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
// now we can create the RaceDefinition and most other things
Race swissTimingRace = messageLoader.getRace(raceID);
race = domainFactory.createRaceDefinition(regatta, swissTimingRace, startList, course);
trackedRace = getTrackedRegatta().createTrackedRace(race, windStore,
trackedRace = getTrackedRegatta().createTrackedRace(race, windStore, delayToLiveInMillis,
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND,
/* time over which to average speed */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
new DynamicRaceDefinitionSet() {
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.swisstimingadapter.impl;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory;
@@ -17,8 +18,9 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo
private final DomainFactory domainFactory;
private final WindStore windStore;
private final RaceSpecificMessageLoader messageLoader;
private final long delayToLiveInMillis;
public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests,
public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, boolean canSendRequests, long delayToLiveInMillis,
SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, WindStore windStore,
RaceSpecificMessageLoader messageLoader) {
super();
@@ -26,6 +28,7 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo
this.port = port;
this.raceID = raceID;
this.canSendRequests = canSendRequests;
this.delayToLiveInMillis = delayToLiveInMillis;
this.swissTimingFactory = swissTimingFactory;
this.domainFactory = domainFactory;
this.windStore = windStore;
@@ -34,7 +37,14 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo
@Override
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry) throws Exception {
return swissTimingFactory.createRaceTracker(raceID, hostname, port, canSendRequests, windStore, messageLoader,
return swissTimingFactory.createRaceTracker(raceID, hostname, port, canSendRequests, delayToLiveInMillis, windStore, messageLoader,
domainFactory, trackedRegattaRegistry);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry)
throws Exception {
return swissTimingFactory.createRaceTracker(regatta, raceID, hostname, port, canSendRequests, delayToLiveInMillis, windStore, messageLoader,
domainFactory, trackedRegattaRegistry);
}
@@ -43,4 +53,8 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo
return SwissTimingRaceTrackerImpl.createID(raceID, hostname, port);
}
@Override
public long getDelayToLiveInMillis() {
return delayToLiveInMillis;
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.test;
import java.util.Collections;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.base.impl.NationalityImpl;
import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
public abstract class AbstractLeaderboardTest {
public static CompetitorImpl createCompetitor(String competitorName) {
return new CompetitorImpl(123, competitorName, 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"),
/* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat",
new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null));
}
}
@@ -1,8 +1,10 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -80,4 +82,46 @@ public class CourseTest {
assertEquals(0, Util.size(course.getWaypoints()));
assertEquals(0, Util.size(course.getLegs()));
}
@Test
public void testInsertWaypointToCourseWithTwoWaypoints() {
List<Waypoint> waypoints = new ArrayList<Waypoint>();
final WaypointImpl wp1 = new WaypointImpl(new BuoyImpl("Test Buoy 1"));
waypoints.add(wp1);
final WaypointImpl wp2 = new WaypointImpl(new BuoyImpl("Test Buoy 2"));
waypoints.add(wp2);
Course course = new CourseImpl("Test Course", waypoints);
assertEquals(2, Util.size(course.getWaypoints()));
assertEquals(1, Util.size(course.getLegs()));
final WaypointImpl wp1_5 = new WaypointImpl(new BuoyImpl("Test Buoy 1.5"));
course.addWaypoint(1, wp1_5);
assertEquals(3, Util.size(course.getWaypoints()));
assertEquals(2, Util.size(course.getLegs()));
assertTrue(Util.equals(Arrays.asList(new Waypoint[] { wp1, wp1_5, wp2 }), course.getWaypoints()));
assertEquals(0, course.getIndexOfWaypoint(wp1));
assertEquals(1, course.getIndexOfWaypoint(wp1_5));
assertEquals(2, course.getIndexOfWaypoint(wp2));
}
@Test
public void testRemovetWaypointFromCourseWithThreeWaypoints() {
List<Waypoint> waypoints = new ArrayList<Waypoint>();
final WaypointImpl wp1 = new WaypointImpl(new BuoyImpl("Test Buoy 1"));
waypoints.add(wp1);
final WaypointImpl wp2 = new WaypointImpl(new BuoyImpl("Test Buoy 2"));
waypoints.add(wp2);
final WaypointImpl wp3 = new WaypointImpl(new BuoyImpl("Test Buoy 3"));
waypoints.add(wp3);
Course course = new CourseImpl("Test Course", waypoints);
assertEquals(3, Util.size(course.getWaypoints()));
assertEquals(2, Util.size(course.getLegs()));
course.removeWaypoint(1);
assertEquals(2, Util.size(course.getWaypoints()));
assertEquals(1, Util.size(course.getLegs()));
assertTrue(Util.equals(Arrays.asList(new Waypoint[] { wp1, wp3 }), course.getWaypoints()));
assertEquals(0, course.getIndexOfWaypoint(wp1));
assertEquals(-1, course.getIndexOfWaypoint(wp2));
assertEquals(1, course.getIndexOfWaypoint(wp3));
}
}
@@ -67,15 +67,15 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
public void setUp() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
super.setUp();
domainFactory = new DomainFactoryImpl(new com.sap.sailing.domain.base.impl.DomainFactoryImpl());
domainRegatta = domainFactory.getOrCreateEvent(getTracTracEvent());
domainRegatta = domainFactory.getOrCreateDefaultRegatta(getTracTracEvent(), /* trackedRegattaRegistry */ null);
trackedRegatta = new DynamicTrackedRegattaImpl(domainRegatta);
ArrayList<Receiver> receivers = new ArrayList<Receiver>();
receivers.add(new RaceCourseReceiver(domainFactory, trackedRegatta, getTracTracEvent(), /* millisecondsOverWhichToAverageWind */
receivers.add(new RaceCourseReceiver(domainFactory, trackedRegatta, getTracTracEvent(),
EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {}
},
30000) {
}, /* delayToLiveInMillis */ 0l,
/* millisecondsOverWhichToAverageWind */ 30000) {
@Override
protected void handleEvent(Triple<Route, RouteData, Race> event) {
synchronized (routeData) {
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.test;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@@ -37,4 +38,10 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry {
public void removeTrackedRegatta(Regatta regatta) {
regattaTrackingCache.remove(regatta);
}
@Override
public Regatta getRememberedRegattaForRace(Serializable race) {
return null;
}
}
@@ -60,7 +60,7 @@ public class FetchTracksAndStoreLocallyTest extends OnlineTracTracBasedTest {
track.addGPSFix((GPSFixMoving) fix);
}
};
DynamicTrackedRegatta trackedRegatta = getTrackedEvent();
DynamicTrackedRegatta trackedRegatta = getTrackedRegatta();
trackedRegatta.addRaceListener(new RaceListener() {
@Override
public void raceAdded(TrackedRace trackedRace) {
@@ -10,6 +10,8 @@ import java.util.GregorianCalendar;
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.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.NoWindException;
@@ -17,8 +19,8 @@ import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowerScoreIsBetter;
import com.sap.sailing.domain.leaderboard.impl.ResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
@@ -26,7 +28,7 @@ import com.sap.sailing.domain.tractracadapter.ReceiverType;
public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
private LeaderboardImpl leaderboard;
private FlexibleLeaderboardImpl leaderboard;
public LeaderboardForKielWeekTest() throws MalformedURLException, URISyntaxException {
super();
@@ -34,24 +36,26 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest {
@Test
public void leaderboardWithOneRaceTest() throws URISyntaxException, NoWindException, IOException, InterruptedException {
leaderboard = new LeaderboardImpl("Kiel Week 2011 505s", new ScoreCorrectionImpl(), new ResultDiscardingRuleImpl(new int[] { 3, 6 }));
leaderboard = new FlexibleLeaderboardImpl("Kiel Week 2011 505s", new ScoreCorrectionImpl(),
new ResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowerScoreIsBetter());
Fleet defaultFleet = leaderboard.getFleet(null);
MillisecondsTimePoint now = MillisecondsTimePoint.now();
loadRace("357c700a-9d9a-11e0-85be-406186cbf87c"); // 505 Race 2
Competitor hasso = getCompetitorByName("Dr.Plattner");
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */ false);
RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */ false, defaultFleet);
assertEquals(21, leaderboard.getTotalPoints(hasso, now));
Pair<Competitor, RaceColumn> key = new Pair<Competitor, RaceColumn>(hasso, column);
assertEquals(21, leaderboard.getContent(now).get(key).getTotalPoints());
assertEquals(21, leaderboard.getEntry(hasso, column, now).getTotalPoints());
loadRace("e876c3a0-9da8-11e0-85be-406186cbf87c"); // 505 Race 3
column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */ false);
column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */ false, defaultFleet);
key = new Pair<Competitor, RaceColumn>(hasso, column);
// In Race 3, Hasso ranked 31st
assertEquals(52, leaderboard.getTotalPoints(hasso, now));
assertEquals(31, leaderboard.getContent(now).get(key).getTotalPoints());
assertEquals(31, leaderboard.getEntry(hasso, column, now).getTotalPoints());
loadRace("7c666e50-9dde-11e0-85be-406186cbf87c"); // 505 Race 4
column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */ false);
column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */ false, defaultFleet);
key = new Pair<Competitor, RaceColumn>(hasso, column);
// now the second race is discarded because Hasso ranked worst compared to the other two; in race 4 he ranked 11th
assertEquals(32, leaderboard.getTotalPoints(hasso, now));
@@ -15,26 +15,22 @@ import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.base.impl.NationalityImpl;
import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardImpl;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowerScoreIsBetter;
import com.sap.sailing.domain.leaderboard.impl.ResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
public class LeaderboardOfflineTest {
public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
private Set<TrackedRace> testRaces;
private Map<TrackedRace, RaceColumn> raceColumnsInLeaderboard;
private Competitor competitor;
@@ -44,15 +40,6 @@ public class LeaderboardOfflineTest {
competitor = createCompetitor("Wolfgang Hunger");
}
private CompetitorImpl createCompetitor(String competitorName) {
return new CompetitorImpl(123, competitorName, 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"),
/* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat",
new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null));
}
public void setupRaces(int numberOfStartedRaces, int numberOfNotStartedRaces) {
testRaces = new HashSet<TrackedRace>();
raceColumnsInLeaderboard = new HashMap<TrackedRace, RaceColumn>();
@@ -98,12 +85,13 @@ public class LeaderboardOfflineTest {
@Test
public void ensureMedalRaceParamIsIgnoredIfRaceColumnAlreadyExists() {
Leaderboard leaderboard = new LeaderboardImpl("Test Leaderboard", new ScoreCorrectionImpl(), new ResultDiscardingRuleImpl(
new int[] { 5, 8 }));
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ScoreCorrectionImpl(), new ResultDiscardingRuleImpl(
new int[] { 5, 8 }), new LowerScoreIsBetter());
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);
leaderboard.addRace(testRaces.iterator().next(), columnName, /* medalRace */ false, defaultFleet);
assertTrue(leaderboard.getRaceColumnByName(columnName).isMedalRace());
}
@@ -120,14 +108,15 @@ public class LeaderboardOfflineTest {
testRaces.add(r); // hash set should take care of more or less randomly permuting the races
}
ScoreCorrectionImpl scoreCorrection = new ScoreCorrectionImpl();
Leaderboard leaderboard = new LeaderboardImpl("Test Leaderboard", scoreCorrection, new ResultDiscardingRuleImpl(
new int[] { 1 }));
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", scoreCorrection, new ResultDiscardingRuleImpl(
new int[] { 1 }), new LowerScoreIsBetter());
Fleet defaultFleet = leaderboard.getFleet(null);
int i=0;
int bestScore = Integer.MAX_VALUE;
RaceColumn bestScoringRaceColumn = null;
for (TrackedRace race : testRaces) {
i++;
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false);
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet);
raceColumnsInLeaderboard.put(race, raceColumn);
if (race.getRank(competitor) < bestScore) {
bestScore = race.getRank(competitor);
@@ -136,22 +125,60 @@ public class LeaderboardOfflineTest {
}
scoreCorrection.setMaxPointsReason(competitor, bestScoringRaceColumn, MaxPointsReason.DSQ);
// assert that best scoring but now disqualified race gets max net points because of disqualification:
assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace().getRace().getCompetitors())+1,
assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1,
leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getNetPoints());
// now assert that it gets discarded because due to disqualification it scores worse than all others:
assertEquals(0, leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getTotalPoints());
}
@Test
public void testNoDNDDiscard() throws NoWindException {
testRaces = new HashSet<TrackedRace>();
raceColumnsInLeaderboard = new HashMap<TrackedRace, RaceColumn>();
Competitor c2 = createCompetitor("Marcus Baur");
Competitor c3 = createCompetitor("Robert Stanjek");
for (int i=0; i<3; i++) {
MockedTrackedRaceWithFixedRankAndManyCompetitors r = new MockedTrackedRaceWithFixedRankAndManyCompetitors(competitor, i+1, /* started */ true);
r.addCompetitor(c2);
r.addCompetitor(c3); // this makes maxPoints==4
testRaces.add(r); // hash set should take care of more or less randomly permuting the races
}
ScoreCorrectionImpl scoreCorrection = new ScoreCorrectionImpl();
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", scoreCorrection, new ResultDiscardingRuleImpl(
new int[] { 1 }), new LowerScoreIsBetter());
Fleet defaultFleet = leaderboard.getFleet(null);
int i=0;
int bestScore = Integer.MAX_VALUE;
RaceColumn bestScoringRaceColumn = null;
for (TrackedRace race : testRaces) {
i++;
RaceColumn raceColumn = leaderboard.addRace(race, "Test Race " + i, /* medalRace */ false, defaultFleet);
raceColumnsInLeaderboard.put(race, raceColumn);
if (race.getRank(competitor) < bestScore) {
bestScore = race.getRank(competitor);
bestScoringRaceColumn = raceColumn;
}
}
scoreCorrection.setMaxPointsReason(competitor, bestScoringRaceColumn, MaxPointsReason.DND);
// assert that best scoring but now disqualified race gets max net points because of disqualification:
assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1,
leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getNetPoints());
// now assert that it does not get discarded because it's a DND
assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1,
leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getTotalPoints());
}
protected void testLeaderboard(int numberOfStartedRaces, int numberOfNotStartedRaces, int firstDiscardingThreshold,
int secondDiscardingThreshold, Integer carry, boolean addOneMedalRace, int numberOfUntrackedRaces) throws NoWindException {
setupRaces(numberOfStartedRaces, numberOfNotStartedRaces);
Leaderboard leaderboard = new LeaderboardImpl("Test Leaderboard", new ScoreCorrectionImpl(), new ResultDiscardingRuleImpl(
new int[] { firstDiscardingThreshold, secondDiscardingThreshold }));
FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ScoreCorrectionImpl(), new ResultDiscardingRuleImpl(
new int[] { firstDiscardingThreshold, secondDiscardingThreshold }), new LowerScoreIsBetter());
Fleet defaultFleet = leaderboard.getFleet(null);
int i=0;
for (TrackedRace race : testRaces) {
i++;
raceColumnsInLeaderboard.put(race, leaderboard.addRace(race, "Test Race "+i,
/* medalRace */ numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size()));
/* medalRace */ numberOfUntrackedRaces == 0 && addOneMedalRace && i == testRaces.size(), defaultFleet));
}
// add a few race columns not yet connected to a tracked race
for (int j=0; j<numberOfUntrackedRaces; j++) {
@@ -164,21 +191,21 @@ public class LeaderboardOfflineTest {
List<Integer> ranksOfNonMedalStartedRaces = new ArrayList<Integer>();
TimePoint now = MillisecondsTimePoint.now();
for (RaceColumn column : raceColumnsInLeaderboard.values()) {
if (!column.isMedalRace() && column.getTrackedRace() != null && column.getTrackedRace().hasStarted(now)) {
ranksOfNonMedalStartedRaces.add(column.getTrackedRace().getRank(competitor, now));
if (!column.isMedalRace() && column.getTrackedRace(defaultFleet) != null && column.getTrackedRace(defaultFleet).hasStarted(now)) {
ranksOfNonMedalStartedRaces.add(column.getTrackedRace(defaultFleet).getRank(competitor, now));
}
}
Collections.sort(ranksOfNonMedalStartedRaces);
int carryInt = (carry == null ? 0 : carry);
int totalPoints = carryInt;
int medalRacePoints = getMedalRacePoints(competitor, now);
int medalRacePoints = getMedalRacePoints(competitor, now, defaultFleet);
int numberOfRacesFromWhichToDiscard = ranksOfNonMedalStartedRaces.size();
for (TrackedRace race : testRaces) {
RaceColumn raceColumn = raceColumnsInLeaderboard.get(race);
Pair<Competitor, RaceColumn> key = new Pair<Competitor, RaceColumn>(competitor, raceColumn);
if (race.hasStarted(now)) {
int rank = race.getRank(competitor, now);
assertEquals(rank, leaderboard.getTrackedPoints(competitor, raceColumn, now));
assertEquals(rank, leaderboard.getTrackedRank(competitor, raceColumn, now));
assertEquals(rank, leaderboard.getContent(now).get(key).getTrackedPoints());
assertEquals(rank, leaderboard.getEntry(competitor, raceColumn, now).getTrackedPoints());
assertEquals(rank, leaderboard.getNetPoints(competitor, raceColumn, now));
@@ -195,7 +222,7 @@ public class LeaderboardOfflineTest {
assertEquals(expected, leaderboard.getEntry(competitor, raceColumn, now).getTotalPoints());
totalPoints += leaderboard.getContent(now).get(key).getTotalPoints();
} else {
assertEquals(0, leaderboard.getTrackedPoints(competitor, raceColumn, now));
assertEquals(0, leaderboard.getTrackedRank(competitor, raceColumn, now));
assertEquals(0, leaderboard.getNetPoints(competitor, raceColumn, now));
assertEquals(0, leaderboard.getContent(now).get(key).getTrackedPoints());
assertEquals(0, leaderboard.getContent(now).get(key).getNetPoints());
@@ -207,11 +234,11 @@ public class LeaderboardOfflineTest {
assertEquals(totalPoints, leaderboard.getTotalPoints(competitor, now));
}
private int getMedalRacePoints(Competitor competitor, TimePoint at) throws NoWindException {
private int getMedalRacePoints(Competitor competitor, TimePoint at, Fleet fleet) throws NoWindException {
for (TrackedRace r : testRaces) {
if (raceColumnsInLeaderboard.get(r) != null && raceColumnsInLeaderboard.get(r).isMedalRace() &&
raceColumnsInLeaderboard.get(r).getTrackedRace().hasStarted(at)) {
return raceColumnsInLeaderboard.get(r).getTrackedRace().getRank(competitor, at);
raceColumnsInLeaderboard.get(r).getTrackedRace(fleet).hasStarted(at)) {
return raceColumnsInLeaderboard.get(r).getTrackedRace(fleet).getRank(competitor, at);
}
}
return 0;
@@ -0,0 +1,325 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.impl.LowerScoreIsBetter;
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.ResultDiscardingRuleImpl;
import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
private ArrayList<Series> series;
private Leaderboard createLeaderboard(Regatta regatta, int[] discardingThresholds) {
ScoreCorrectionImpl scoreCorrections = new ScoreCorrectionImpl();
ResultDiscardingRuleImpl discardingRules = new ResultDiscardingRuleImpl(discardingThresholds);
return new RegattaLeaderboardImpl(regatta, scoreCorrections, discardingRules, new LowerScoreIsBetter());
}
@Test
public void testOneStartedRaceWithDifferentScores() {
List<Competitor> competitors = createCompetitors(10);
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1,
new String[] { "Default" },
/* medal */false, "testOneStartedRaceWithDifferentScores",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors);
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(competitors, rankedCompetitors);
}
/**
* Asserts that the competitors ranking worse than the disqualified competitor advance by one in the
* {@link Leaderboard#getCompetitorsFromBestToWorst(TimePoint)} ordering. Note that this does not test
* the total points given for those competitors.
*/
@Test
public void testOneStartedRaceWithDifferentScoresAndDisqualification() throws NoWindException {
List<Competitor> competitors = createCompetitors(10);
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1,
new String[] { "Default" },
/* medal */false, "testOneStartedRaceWithDifferentScores",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
Series finalSeries;
Iterator<? extends Series> seriesIter = regatta.getSeries().iterator();
seriesIter.next();
finalSeries = seriesIter.next();
leaderboard.getScoreCorrection().setMaxPointsReason(competitors.get(5), finalSeries.getRaceColumnByName("F1"), MaxPointsReason.DSQ);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors);
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(competitors.subList(0, 5), rankedCompetitors.subList(0, 5));
assertEquals(competitors.subList(6, 10), rankedCompetitors.subList(5, 9));
assertEquals(competitors.get(5), rankedCompetitors.get(9));
// Now test the total points and make sure the other competitors advanced by one, too
assertEquals(11, leaderboard.getTotalPoints(competitors.get(5), f1Column, now));
for (int i=0; i<5; i++) {
assertEquals(i+1, leaderboard.getTotalPoints(competitors.get(i), f1Column, now));
}
for (int i=6; i<10; i++) {
assertEquals(i, leaderboard.getTotalPoints(competitors.get(i), f1Column, now));
}
}
@Test
public void testDistributionAcrossQualifyingFleetsWithDifferentScores() {
List<Competitor> competitors = createCompetitors(10);
List<Competitor> yellow = new ArrayList<Competitor>();
List<Competitor> blue = new ArrayList<Competitor>();
for (int i=0; i<5; i++) {
yellow.add(competitors.get(2*i));
blue.add(competitors.get(2*i+1));
}
Regatta regatta = createRegatta(/* qualifying */1, new String[] { "Yellow", "Blue" }, /* final */0,
new String[] { "Default" },
/* medal */false, "testAllTrackedAndStartedWithDifferentScores",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
RaceColumn q1Column = series.get(0).getRaceColumnByName("Q1");
TrackedRace q1Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, yellow);
q1Column.setTrackedRace(q1Column.getFleetByName("Yellow"), q1Yellow);
TrackedRace q1Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, blue);
q1Column.setTrackedRace(q1Column.getFleetByName("Blue"), q1Blue);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
for (int i=0; i<5; i++) {
Competitor first = rankedCompetitors.get(2*i);
Competitor second = rankedCompetitors.get(2*i+1);
assertTrue(first == yellow.get(i) || first == blue.get(i));
assertTrue(second == yellow.get(i) || second == blue.get(i));
}
}
@Test
public void testDistributionAcrossFinalFleetsWithDifferentScores() {
List<Competitor> competitors = createCompetitors(10);
List<Competitor> gold = new ArrayList<Competitor>();
List<Competitor> silver = new ArrayList<Competitor>();
for (int i=0; i<5; i++) {
gold.add(competitors.get(2*i));
silver.add(competitors.get(2*i+1));
}
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1, new String[] {
"Gold", "Silver" },
/* medal */false, "testDistributionAcrossFinalFleetsWithDifferentScores",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
TrackedRace f1Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, gold);
f1Column.setTrackedRace(f1Column.getFleetByName("Gold"), f1Gold);
TrackedRace f1Silver = new MockedTrackedRaceWithStartTimeAndRanks(now, silver);
f1Column.setTrackedRace(f1Column.getFleetByName("Silver"), f1Silver);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
for (int i=0; i<5; i++) {
assertSame(gold.get(i), rankedCompetitors.get(i));
assertSame(silver.get(i), rankedCompetitors.get(i+5));
}
}
@Test
public void testMedalTakesPrecedence() {
final int firstMedalCompetitorIndex = 3;
List<Competitor> competitors = createCompetitors(20);
List<Competitor> medal = competitors.subList(firstMedalCompetitorIndex, firstMedalCompetitorIndex+10);
Regatta regatta = createRegatta(/* qualifying */ 0, new String[] { "Default" }, /* final */ 1, new String[] { "Default" },
/* medal */ true, "testMedalTakesPrecedence",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */ true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
TrackedRace q1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors);
f1Column.setTrackedRace(f1Column.getFleetByName("Default"), q1Default);
TrackedRace medalTrackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, medal);
RaceColumn medalColumn = series.get(2).getRaceColumnByName("M");
medalColumn.setTrackedRace(medalColumn.getFleetByName("Medal"), medalTrackedRace);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
// medalists rank top
for (int i=0; i<10; i++) {
assertSame(medal.get(i), rankedCompetitors.get(i));
}
// others rank according to their non-medal ranking in the final round
for (int i=10; i<competitors.size(); i++) {
if (i<10+firstMedalCompetitorIndex) {
assertSame(competitors.get(i-10), rankedCompetitors.get(i));
} else {
assertSame(competitors.get(i), rankedCompetitors.get(i));
}
}
}
@Test
public void testTieBreakWithTwoVersusOneWins() throws NoWindException {
Competitor[] c = createCompetitors(3).toArray(new Competitor[0]);
Competitor[] f1 = new Competitor[] { c[0], c[1], c[2] };
Competitor[] f2 = new Competitor[] { c[0], c[1], c[2] };
Competitor[] f3 = new Competitor[] { c[1], c[2], c[0] };
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */3, new String[] { "Default" },
/* medal */ false, "testTieBreakWithTwoVersusOneWins",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later));
assertEquals(Arrays.asList(new Competitor[] { c[0], c[1], c[2] }), rankedCompetitors);
}
@Test
public void testTieBreakWithTwoVersusOneSeconds() throws NoWindException {
Competitor[] c = createCompetitors(4).toArray(new Competitor[0]);
Competitor[] f1 = new Competitor[] { c[2], c[0], c[1], c[3] }; // c[0] scores 18 points altogether
Competitor[] f2 = new Competitor[] { c[2], c[0], c[1], c[3] }; // c[1] scores 18 points altogether but has only one second rank
Competitor[] f3 = new Competitor[] { c[2], c[1], c[0], c[3] };
Competitor[] f4 = new Competitor[] { c[3], c[2], c[0], c[1] };
Competitor[] f5 = new Competitor[] { c[3], c[2], c[1], c[0] };
Competitor[] f6 = new Competitor[] { c[3], c[2], c[1], c[0] };
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */6, new String[] { "Default" },
/* medal */ false, "testTieBreakWithTwoVersusOneSeconds",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3, f4, f5, f6);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later));
assertTrue(rankedCompetitors.indexOf(c[0]) == rankedCompetitors.indexOf(c[1])-1);
}
@Test
public void testTieBreakByMedalRaceScoreOnlyIfEqualTotalScore() throws NoWindException {
Competitor[] c = createCompetitors(2).toArray(new Competitor[0]);
Competitor[] f1 = new Competitor[] { c[1], c[0] };
Competitor[] f2 = new Competitor[] { c[1], c[0] };
Competitor[] m1 = new Competitor[] { c[0], c[1] };
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */2, new String[] { "Default" },
/* medal */ true, "testTieBreakWithTwoVersusOneSeconds",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2);
createAndAttachTrackedRaces(series.get(2), "Medal", m1);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
// assert that both have equal score
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later));
// assert that c[0] ranks better than c[1] (reason: c[0] ranked better in medal race)
assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1);
}
@Test
public void testTieBreakWithEqualWinsAndTwoVersusOneSeconds() throws NoWindException {
Competitor[] c = createCompetitors(4).toArray(new Competitor[0]);
Competitor[] f1 = new Competitor[] { c[2], c[0], c[1], c[3] }; // c[0] scores 16
Competitor[] f2 = new Competitor[] { c[2], c[0], c[1], c[3] }; // c[1] scores 16 points altogether but has only one second rank
Competitor[] f3 = new Competitor[] { c[2], c[1], c[3], c[0] }; // c[2] scores 9
Competitor[] f4 = new Competitor[] { c[3], c[2], c[0], c[1] }; // c[3] scores 29
Competitor[] f5 = new Competitor[] { c[0], c[2], c[1], c[3] };
Competitor[] f6 = new Competitor[] { c[1], c[2], c[3], c[0] };
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */6, new String[] { "Default" },
/* medal */ false, "testTieBreakWithEqualWinsAndTwoVersusOneSeconds",
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true));
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", f1, f2, f3, f4, f5, f6);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later));
assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1);
}
private TimePoint createAndAttachTrackedRaces(Series theSeries, String fleetName, Competitor[]... competitorLists) {
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
Iterator<? extends RaceColumn> columnIter = theSeries.getRaceColumns().iterator();
for (Competitor[] competitorList : competitorLists) {
RaceColumn raceColumn = columnIter.next();
TrackedRace trackedRace = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitorList));
raceColumn.setTrackedRace(raceColumn.getFleetByName(fleetName), trackedRace);
}
return later;
}
private List<Competitor> createCompetitors(int numberOfCompetitorsToCreate) {
List<Competitor> result = new ArrayList<Competitor>();
for (int i=1; i<=numberOfCompetitorsToCreate; i++) {
result.add(createCompetitor("C"+i));
}
return result;
}
private Regatta createRegatta(final int numberOfQualifyingRaces, String[] qualifyingFleetNames, final int numberOfFinalRaces,
String[] finalFleetNames, boolean medalRaceAndSeries, final String regattaBaseName, BoatClass boatClass) {
series = new ArrayList<Series>();
// -------- qualifying series ------------
List<Fleet> qualifyingFleets = new ArrayList<Fleet>();
for (String qualifyingFleetName : qualifyingFleetNames) {
qualifyingFleets.add(new FleetImpl(qualifyingFleetName));
}
List<String> qualifyingRaceColumnNames = new ArrayList<String>();
for (int i=1; i<=numberOfQualifyingRaces; i++) {
qualifyingRaceColumnNames.add("Q"+i);
}
Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets,
qualifyingRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(qualifyingSeries);
// -------- final series ------------
List<Fleet> finalFleets = new ArrayList<Fleet>();
int fleetOrdering = 1;
for (String finalFleetName : finalFleetNames) {
finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++));
}
List<String> finalRaceColumnNames = new ArrayList<String>();
for (int i=1; i<=numberOfFinalRaces; i++) {
finalRaceColumnNames.add("F"+i);
}
Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(finalSeries);
if (medalRaceAndSeries) {
// ------------ medal --------------
List<Fleet> medalFleets = new ArrayList<Fleet>();
medalFleets.add(new FleetImpl("Medal"));
List<String> medalRaceColumnNames = new ArrayList<String>();
medalRaceColumnNames.add("M");
Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(medalSeries);
}
Regatta regatta = new RegattaImpl(regattaBaseName, boatClass, series, /* persistent */ false);
return regatta;
}
}
@@ -1,6 +1,8 @@
package com.sap.sailing.domain.test;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
@@ -16,31 +18,52 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
private final boolean started;
private final RaceDefinition raceDefinition;
private final Competitor competitor;
private final BoatClass boatClass;
public MockedTrackedRaceWithFixedRank(Competitor competitor, int rank, boolean started) {
public MockedTrackedRaceWithFixedRank(Competitor competitor, int rank, boolean started, BoatClass boatClass) {
this.rank = rank;
this.started = started;
this.competitor = competitor;
this.raceDefinition = new RaceDefinition() {
private static final long serialVersionUID = 6812543850545870357L;
@Override
public String getName() {
return null;
}
@Override
public Course getCourse() {
return null;
}
@Override
public Iterable<Competitor> getCompetitors() {
return Collections.singleton(MockedTrackedRaceWithFixedRank.this.competitor);
}
@Override
public BoatClass getBoatClass() {
return null;
}
};
this.raceDefinition = new MockedRaceDefinition();
this.boatClass = boatClass;
}
public MockedTrackedRaceWithFixedRank(Competitor competitor, int rank, boolean started) {
this(competitor, rank, started, /* boatClass */ null);
}
private class MockedRaceDefinition implements RaceDefinition {
private static final long serialVersionUID = 6812543850545870357L;
@Override
public String getName() {
return null;
}
@Override
public Course getCourse() {
return null;
}
@Override
public Iterable<Competitor> getCompetitors() {
return Collections.singleton(MockedTrackedRaceWithFixedRank.this.competitor);
}
@Override
public BoatClass getBoatClass() {
return boatClass;
}
@Override
public Serializable getId() {
return null;
}
}
@Override
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
return Collections.singletonList(competitor);
}
@Override
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.test;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@@ -35,6 +36,10 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
public BoatClass getBoatClass() {
return null;
}
@Override
public Serializable getId() {
return null;
}
};
}
@@ -0,0 +1,420 @@
package com.sap.sailing.domain.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
/**
* Simple mock for {@link TrackedRace} for leaderboard testing; the leaderboard only requests {@link #hasStarted(TimePoint)} and
* {@link #getRank(Competitor)} and {@link #getRank(Competitor, TimePoint)}. Additionally, a mocked {@link RaceDefinition} is produced
* from the competitor list.
*
* @author Axel Uhl (D043530)
*
*/
public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
private static final long serialVersionUID = 2708044935347796930L;
private final TimePoint startTime;
private final List<Competitor> competitorsFromBestToWorst;
private RaceDefinition race;
public MockedTrackedRaceWithStartTimeAndRanks(TimePoint startTime, List<Competitor> competitorsFromBestToWorst) {
super();
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);
final List<Waypoint> waypoints = Collections.emptyList();
this.race = new RaceDefinitionImpl("Mocked Race", new CourseImpl("Mock Course", waypoints), /* boat class */ null,
competitorsFromBestToWorst);
}
@Override
public RaceDefinition getRace() {
return race;
}
@Override
public RegattaAndRaceIdentifier getRaceIdentifier() {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getStartOfRace() {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getEndOfRace() {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<Pair<Waypoint, Pair<TimePoint, TimePoint>>> getMarkPassingsTimes() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean hasStarted(TimePoint at) {
return at.compareTo(startTime) >= 0;
}
@Override
public Iterable<TrackedLeg> getTrackedLegs() {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLeg getTrackedLeg(Leg leg) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLeg getCurrentLeg(TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLeg getTrackedLegFinishingAt(Waypoint endOfLeg) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLeg getTrackedLegStartingAt(Waypoint startOfLeg) {
// TODO Auto-generated method stub
return null;
}
@Override
public GPSFixTrack<Competitor, GPSFixMoving> getTrack(Competitor competitor) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, TimePoint at) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedLegOfCompetitor getTrackedLeg(Competitor competitor, Leg leg) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getUpdateCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getRankDifference(Competitor competitor, Leg leg, TimePoint timePoint) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getRank(Competitor competitor) throws NoWindException {
return competitorsFromBestToWorst.indexOf(competitor) + 1;
}
@Override
public int getRank(Competitor competitor, TimePoint timePoint) throws NoWindException {
return competitorsFromBestToWorst.indexOf(competitor) + 1;
}
@Override
public Distance getStartAdvantage(Competitor competitor, double secondsIntoTheRace) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public MarkPassing getMarkPassing(Competitor competitor, Waypoint waypoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public GPSFixTrack<Buoy, GPSFix> getOrCreateTrack(Buoy buoy) {
// TODO Auto-generated method stub
return null;
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public Wind getWind(Position p, TimePoint at) {
// TODO Auto-generated method stub
return null;
}
@Override
public Wind getWind(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<WindSource> getWindSources(WindSourceType type) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<WindSource> getWindSources() {
// TODO Auto-generated method stub
return null;
}
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource) {
// TODO Auto-generated method stub
return null;
}
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) {
// TODO Auto-generated method stub
return null;
}
@Override
public void waitForNextUpdate(int sinceUpdate) throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public TimePoint getStartOfTracking() {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getEndOfTracking() {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getTimePointOfNewestEvent() {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getTimePointOfOldestEvent() {
// TODO Auto-generated method stub
return null;
}
@Override
public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) {
// TODO Auto-generated method stub
return null;
}
@Override
public TimePoint getTimePointOfLastEvent() {
// TODO Auto-generated method stub
return null;
}
@Override
public long getMillisecondsOverWhichToAverageSpeed() {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getMillisecondsOverWhichToAverageWind() {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getDelayToLiveInMillis() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Wind getEstimatedWindDirection(Position position, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public Tack getTack(Competitor competitor, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public TrackedRegatta getTrackedRegatta() {
// TODO Auto-generated method stub
return null;
}
@Override
public Wind getDirectionFromStartToNextMark(TimePoint at) {
// TODO Auto-generated method stub
return null;
}
@Override
public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
// TODO Auto-generated method stub
return null;
}
@Override
public List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to) throws NoWindException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean raceIsKnownToStartUpwind() {
// TODO Auto-generated method stub
return false;
}
@Override
public void addListener(RaceChangeListener listener) {
// TODO Auto-generated method stub
}
@Override
public void removeListener(RaceChangeListener listener) {
// TODO Auto-generated method stub
}
@Override
public Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint)
throws NoWindException {
// TODO Auto-generated method stub
return null;
}
@Override
public WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterable<WindSource> getWindSourcesToExclude() {
// TODO Auto-generated method stub
return null;
}
@Override
public WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at,
Iterable<WindSource> windSourcesToExclude) {
// TODO Auto-generated method stub
return null;
}
@Override
public WindWithConfidence<TimePoint> getEstimatedWindDirectionWithConfidence(Position position, TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
@Override
public void setWindSourcesToExclude(Iterable<? extends WindSource> windSourcesToExclude) {
// TODO Auto-generated method stub
}
@Override
public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint) throws NoWindException {
// TODO Auto-generated method stub
return null;
}
@Override
public WindStore getWindStore() {
// TODO Auto-generated method stub
return null;
}
@Override
public Competitor getOverallLeader(TimePoint timePoint) throws NoWindException {
// TODO Auto-generated method stub
return null;
}
@Override
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
return competitorsFromBestToWorst;
}
}
@@ -49,25 +49,25 @@ public class MultipleClassesInRegattaTest {
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
kiwotest2 = domainFactory
.createRaceTracker(
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
kiwotest3 = domainFactory
.createRaceTracker(
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
weym470may112014_2 = domainFactory
.createRaceTracker(
new URL(
httpAndHost+"/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"),
new URI(liveURI), new URI(storedURI),
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
assertEquals("STG", kiwotest1.getRegatta().getBoatClass().getName());
assertEquals("505", kiwotest2.getRegatta().getBoatClass().getName());
@@ -90,11 +90,11 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest {
setStoredDataLoaded(false);
ArrayList<Receiver> receivers = new ArrayList<Receiver>();
for (Receiver r : domainFactory.getUpdateReceivers(trackedRegatta, getTracTracEvent(), EmptyWindStore.INSTANCE,
/* startOfTracking */ null, /* endOfTracking */ null, new DynamicRaceDefinitionSet() {
/* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {
}
}, receiverTypes)) {
}, /* trackedRegattaRegistry */ null, receiverTypes)) {
receivers.add(r);
}
addListenersForStoredDataAndStartController(receivers);
@@ -114,7 +114,7 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest {
for (Receiver receiver : receivers) {
receiver.join();
}
trackedRace = getTrackedEvent().getTrackedRace(race);
trackedRace = getTrackedRegatta().getTrackedRace(race);
}
@@ -131,7 +131,7 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest {
if (domainFactory == null) {
domainFactory = new DomainFactoryImpl(new com.sap.sailing.domain.base.impl.DomainFactoryImpl());
}
domainEvent = domainFactory.getOrCreateEvent(getTracTracEvent());
domainEvent = domainFactory.getOrCreateDefaultRegatta(getTracTracEvent(), /* trackedRegattaRegistry */ null);
trackedRegatta = new DynamicTrackedRegattaImpl(domainEvent);
}
@@ -205,7 +205,7 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest {
return domainEvent;
}
protected DynamicTrackedRegatta getTrackedEvent() {
protected DynamicTrackedRegatta getTrackedRegatta() {
return trackedRegatta;
}
@@ -74,7 +74,7 @@ public class ParseTracTracJSONServiceOutputTest {
@Test
public void testWeymouthOnline() throws MalformedURLException, IOException, java.text.ParseException, ParseException, URISyntaxException {
JSONService jsonService = DomainFactory.INSTANCE.parseJSONURL(new URL(
"http://" + TracTracConnectionConstants.HOST_NAME + "/events/event_20110505_SailingTea/jsonservice.php"));
"http://" + TracTracConnectionConstants.HOST_NAME + "/events/event_20110505_SailingTea/jsonservice.php?trickOurProxy=true"));
assertEquals("Academy Tracking 2011", jsonService.getEventName());
List<RaceRecord> races = jsonService.getRaceRecords();
assertTrue(races.size()>=28);
@@ -1,12 +1,14 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.quadtree.QuadTree;
import com.sap.sailing.domain.common.quadtree.impl.QuadTreeNode;
public class QuadTreeTest {
private class GLatLngQuadTree extends QuadTree<Position> {
@@ -21,6 +23,53 @@ public class QuadTreeTest {
}
}
private static class QuadTreeWithPublicGetTop<T> extends QuadTree<T> {
private static final long serialVersionUID = -783622065160380333L;
@Override
public QuadTreeNode<T> getTop() {
return super.getTop();
}
}
@Test
public void testNoNPEDuringSecondPutInSameLeaf() {
final QuadTreeWithPublicGetTop<Object> qt = new QuadTreeWithPublicGetTop<Object>();
final Position p = new DegreePosition(0, 0);
final NullPointerException[] npe = new NullPointerException[1];
final boolean[] stop = new boolean[1];
Runnable r = new Runnable() {
@Override
public void run() {
while (!stop[0]) {
synchronized (qt) {
try {
qt.wait();
// if the following try/catch is moved outside the synchronized block, occasional NPEs result
try {
qt.put(p, p);
} catch (NullPointerException e) {
npe[0] = e;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
};
new Thread(r, "1").start();
new Thread(r, "2").start();
new Thread(r, "3").start();
new Thread(r, "4").start();
for (int i=0; i<10000000; i++) {
synchronized(qt) {
qt.notifyAll();
}
assertNull("NullPointerException "+(npe[0]==null?"":npe[0].getMessage())+" during iteration "+i, npe[0]);
}
stop[0] = true;
}
@Test
public void testDistance() {
GLatLngQuadTree quadtree = new GLatLngQuadTree(new DegreePosition(49.29,
@@ -80,7 +80,7 @@ public class ReachingLegTest extends TrackBasedTest {
protected DynamicTrackedRace createTestTrackedRace(String regattaName, String raceName, String boatClassName,
Iterable<Competitor> competitors, TimePoint timePointForFixes) {
BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true);
Regatta regatta = new RegattaImpl(regattaName, boatClass);
Regatta regatta = new RegattaImpl(regattaName, boatClass, /* trackedRegattaRegistry */ null);
TrackedRegatta trackedRegatta = new TrackedRegattaImpl(regatta);
List<Waypoint> waypoints = new ArrayList<Waypoint>();
// create a two-lap upwind/downwind course:
@@ -96,6 +96,7 @@ public class ReachingLegTest extends TrackBasedTest {
Course course = new CourseImpl(raceName, waypoints);
RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors);
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, EmptyWindStore.INSTANCE,
/* delayToLiveInMillis */ 0,
/* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000,
/* delay for wind estimation cache invalidation */ 0);
// in this simplified artificial course, the top mark is exactly north of the right leeward gate, the offset
@@ -93,12 +93,13 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest {
List<Receiver> receivers = new ArrayList<Receiver>();
receivers.add(receiver);
for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers(
new DynamicTrackedRegattaImpl(DomainFactory.INSTANCE.getOrCreateEvent(getTracTracEvent())),
getTracTracEvent(), EmptyWindStore.INSTANCE, /* startOfTracking */ null, /* endOfTracking */ null,
new DynamicTrackedRegattaImpl(DomainFactory.INSTANCE.getOrCreateDefaultRegatta(getTracTracEvent(), /* trackedRegattaRegistry */ null)),
getTracTracEvent(), EmptyWindStore.INSTANCE, /* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l,
new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {}
}, ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
}, /* trackedRegattaRegistry */ null,
ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
receivers.add(r);
}
addListenersForStoredDataAndStartController(receivers);
@@ -65,7 +65,7 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
}
};
List<TypeController> listeners = new ArrayList<TypeController>();
Regatta regatta = domainFactory.getOrCreateEvent(getTracTracEvent());
Regatta regatta = domainFactory.getOrCreateDefaultRegatta(getTracTracEvent(), /* trackedRegattaRegistry */ null);
DynamicTrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta);
trackedRegatta.addRaceListener(new RaceListener() {
@Override
@@ -78,21 +78,21 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
}
});
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedRegatta, getTracTracEvent(), /* start of tracking */ null,
/* end of tracking */ null, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
/* end of tracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {
}
})) {
}, /* trackedRegattaRegistry */ null)) {
for (TypeController raceListener : receiver.getTypeControllersAndStart()) {
listeners.add(raceListener);
}
}
addListenersForStoredDataAndStartController(domainFactory.getUpdateReceivers(trackedRegatta, getTracTracEvent(),
/* start of tracking */ null, /* end of tracking */ null, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
/* start of tracking */ null, /* end of tracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() {
@Override
public void addRaceDefinition(RaceDefinition race) {
}
}));
}, /* trackedRegattaRegistry */ null));
}
@Test
@@ -90,7 +90,7 @@ public abstract class TrackBasedTest {
protected DynamicTrackedRace createTestTrackedRace(String regattaName, String raceName, String boatClassName,
Iterable<Competitor> competitors, TimePoint timePointForFixes) {
BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true);
Regatta regatta = new RegattaImpl(regattaName, boatClass);
Regatta regatta = new RegattaImpl(regattaName, boatClass, /* trackedRegattaRegistry */ null);
TrackedRegatta trackedRegatta = new TrackedRegattaImpl(regatta);
List<Waypoint> waypoints = new ArrayList<Waypoint>();
// create a two-lap upwind/downwind course:
@@ -106,6 +106,7 @@ public abstract class TrackBasedTest {
Course course = new CourseImpl(raceName, waypoints);
RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors);
DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, EmptyWindStore.INSTANCE,
/* delayToLiveInMillis */ 0,
/* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000,
/* delay for wind estimation cache invalidation */ 0);
// in this simplified artificial course, the top mark is exactly north of the right leeward gate
@@ -45,7 +45,7 @@ public class UnicodeCharactersInCompetitorNamesTest {
"http://" + TracTracConnectionConstants.HOST_NAME + "/events/event_20110609_KielerWoch/clientparams.php?event=event_20110609_KielerWoch&race=5b08a9ee-9933-11e0-85be-406186cbf87c"),
tractracTunnel ? new URI("tcp://"+tractracTunnelHost+":"+TracTracConnectionConstants.PORT_TUNNEL_LIVE) : new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_LIVE),
tractracTunnel ? new URI("tcp://"+tractracTunnelHost+":"+TracTracConnectionConstants.PORT_TUNNEL_STORED) : new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_STORED),
/* startOfTracking */ null, /* endOfTracking */ null, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
/* startOfTracking */ null, /* endOfTracking */ null, /* delayToLiveInMillis */ 0l, EmptyWindStore.INSTANCE, new DummyTrackedRegattaRegistry());
Iterable<Competitor> competitors = fourtyninerYellow_2.getRacesHandle().getRaces().iterator().next().getCompetitors();
for (Competitor competitor : competitors) {
@@ -6,6 +6,7 @@ import java.util.NavigableSet;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaListener;
import com.sap.sailing.domain.base.Leg;
@@ -273,7 +274,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public DynamicTrackedRegatta getTrackedEvent() {
public DynamicTrackedRegatta getTrackedRegatta() {
return new DynamicTrackedRegatta() {
private static final long serialVersionUID = 2651590861333064588L;
@@ -284,7 +285,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public String getName() {
return "A Mocked Test Event";
return "A Mocked Test Regatta";
}
@Override
@@ -350,6 +351,30 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
return null;
}
@Override
public Series getSeriesByName(String seriesName) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isPersistent() {
// TODO Auto-generated method stub
return false;
}
@Override
public void addRaceColumnListener(RaceColumnListener listener) {
// TODO Auto-generated method stub
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
// TODO Auto-generated method stub
}
};
}
@@ -409,7 +434,7 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, WindStore windStore,
long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
// TODO Auto-generated method stub
return null;
@@ -602,4 +627,28 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub
return null;
}
@Override
public long getDelayToLiveInMillis() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void setDelayToLiveInMillis(long delayToLiveInMillis) {
// TODO Auto-generated method stub
}
@Override
public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) {
// TODO Auto-generated method stub
}
@Override
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
// TODO Auto-generated method stub
return null;
}
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tractracadapter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -16,6 +17,7 @@ import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.Person;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.TimePoint;
@@ -24,6 +26,7 @@ import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
@@ -84,8 +87,9 @@ public interface DomainFactory {
* A new {@link com.sap.sailing.domain.base.Regatta} is created if no event by
* an equal name with a boat class with an equal name as the <code>event</code>'s
* boat class exists yet.
* @param trackedRegattaRegistry TODO
*/
com.sap.sailing.domain.base.Regatta getOrCreateEvent(Event event);
com.sap.sailing.domain.base.Regatta getOrCreateDefaultRegatta(Event event, TrackedRegattaRegistry trackedRegattaRegistry);
/**
* Creates a race tracked for the specified URL/URIs and starts receiving all available existing and future push
@@ -118,7 +122,15 @@ public interface DomainFactory {
* available but loses track of the wind, e.g., during server restarts.
*/
TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, TimePoint startOfTracking,
TimePoint endOfTracking, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry)
TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry)
throws MalformedURLException, FileNotFoundException, URISyntaxException;
/**
* Same as {@link #createRaceTracker(URL, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)}, only that
* a predefined {@link Regatta} is used to hold the resulting races.
*/
RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, TimePoint startOfTracking,
TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry)
throws MalformedURLException, FileNotFoundException, URISyntaxException;
BoatClass getOrCreateBoatClass(CompetitorClass competitorClass);
@@ -142,12 +154,13 @@ public interface DomainFactory {
* if <code>null</code>, all stored data until the "end of time" will be loaded that the event has to
* provide, particularly for the mark positions which are stored per event, not per race; otherwise,
* particularly the mark position loading will be constrained to this end time.
* @param trackedRegattaRegistry TODO
* @param tokenToRetrieveAssociatedRace
* used to update the set of{@link RaceDefinition}s received by the
* {@link RaceCourseReceiver} created by this call
*/
Iterable<Receiver> getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, Event tractracEvent, TimePoint startOfTracking,
TimePoint endOfTracking, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry);
/**
* Creates a {@link RaceDefinition} from a TracTrac {@link Race} and a domain {@link Course} definition. The
@@ -166,8 +179,8 @@ public interface DomainFactory {
* if not <code>null</code>, after creating the {@link TrackedRace}, the {@link RaceDefinition} is
* {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition) added} to that object.
*/
Pair<RaceDefinition, TrackedRace> getOrCreateRaceDefinitionAndTrackedRace(TrackedRegatta trackedRegatta, Race race,
Course course, WindStore windStore, long millisecondsOverWhichToAverageWind,
void getOrCreateRaceDefinitionAndTrackedRace(TrackedRegatta trackedRegatta, Race race,
Course course, WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
/**
@@ -182,7 +195,7 @@ public interface DomainFactory {
MarkPassing createMarkPassing(TimePoint timePoint, Waypoint passed, com.sap.sailing.domain.base.Competitor competitor);
Iterable<Receiver> getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, Event tractracEvent, WindStore windStore,
TimePoint startOfTracking, TimePoint endOfTracking, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, ReceiverType... types);
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, ReceiverType... types);
JSONService parseJSONURL(URL jsonURL) throws IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException;
@@ -217,16 +230,21 @@ public interface DomainFactory {
Pair<List<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(Race race);
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore);
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore);
/**
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
* {@link com.sap.sailing.domain.base.Regatta} and, if a {@link TrackedRace} for the corresponding
* {@link RaceDefinition} exists, from the {@link TrackedRegatta}. If removing the race from the event, the event is
* removed from the event cache such that {@link #getOrCreateEvent(Event)} will have to create a new one. Similarly,
* removed from the event cache such that {@link #getOrCreateDefaultRegatta(Event, TrackedRegattaRegistry)} will have to create a new one. Similarly,
* if the {@link TrackedRace} that was removed from the {@link TrackedRegatta} was the last one, the
* {@link TrackedRegatta} is removed such that {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)}
* will have to create a new one.
*/
void removeRace(Event tractracEvent, Race tractracRace, TrackedRegattaRegistry trackedRegattaRegistry);
/**
* Computes an ID to use for a {@link RaceDefinition} based on the TracTrac race.
*/
Serializable getRaceID(Race tractracRace);
}
@@ -99,7 +99,7 @@ public class RaceRecord {
public String getName() {
return name;
}
public String getEventName() {
return regattaName;
}
@@ -6,10 +6,10 @@ import java.util.logging.Logger;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.Receiver;
import com.tractrac.clientmodule.Event;
@@ -38,7 +38,8 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
*/
private boolean receivedEventDuringTimeout;
public AbstractReceiverWithQueue(DomainFactory domainFactory, Event tractracEvent, DynamicTrackedRegatta trackedRegatta) {
public AbstractReceiverWithQueue(DomainFactory domainFactory, Event tractracEvent,
DynamicTrackedRegatta trackedRegatta) {
super();
this.tractracEvent = tractracEvent;
this.trackedRegatta = trackedRegatta;
@@ -59,7 +60,7 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
return tractracEvent;
}
protected DynamicTrackedRegatta getTrackedEvent() {
protected DynamicTrackedRegatta getTrackedRegatta() {
return trackedRegatta;
}
@@ -140,8 +141,8 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
protected DynamicTrackedRace getTrackedRace(Race race) {
DynamicTrackedRace result = null;
RaceDefinition raceDefinition = getDomainFactory().getAndWaitForRaceDefinition(race);
com.sap.sailing.domain.base.Regatta domainEvent = getDomainFactory().getOrCreateEvent(getTracTracEvent());
if (domainEvent.getRaceByName(raceDefinition.getName()) != null) {
com.sap.sailing.domain.base.Regatta domainRegatta = trackedRegatta.getRegatta();
if (domainRegatta.getRaceByName(raceDefinition.getName()) != null) {
result = trackedRegatta.getTrackedRace(raceDefinition);
}
return result;
@@ -2,20 +2,18 @@ package com.sap.sailing.domain.tractracadapter.impl;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.Boat;
@@ -23,19 +21,19 @@ import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Nationality;
import com.sap.sailing.domain.base.Person;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Team;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.base.impl.PersonImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.TeamImpl;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
@@ -47,10 +45,11 @@ import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.RaceTracker;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
@@ -94,15 +93,12 @@ public class DomainFactoryImpl implements DomainFactory {
* class needs to be determined for an regatta. Synchronization for additions / removals is tied to the
* synchronization for {@link #regattaCache}.
*/
private final WeakIdentityHashMap<com.tractrac.clientmodule.Event, Regatta> weakEventCache = new WeakIdentityHashMap<>();
private final WeakIdentityHashMap<com.tractrac.clientmodule.Event, Regatta> weakRegattaCache = new WeakIdentityHashMap<>();
private final Map<Race, RaceDefinition> raceCache = new HashMap<Race, RaceDefinition>();
private final Set<String> mayStartWithNoUpwindLeg;
public DomainFactoryImpl(com.sap.sailing.domain.base.DomainFactory baseDomainFactory) {
this.baseDomainFactory = baseDomainFactory;
mayStartWithNoUpwindLeg = new HashSet<String>(Arrays.asList(new String[] { "extreme40", "ess", "ess40" }));
}
@Override
@@ -204,9 +200,7 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public BoatClass getOrCreateBoatClass(CompetitorClass competitorClass) {
return baseDomainFactory.getOrCreateBoatClass(competitorClass == null ? "" : competitorClass.getName(),
/* typicallyStartsUpwind */ !mayStartWithNoUpwindLeg.contains(competitorClass == null ? "" : competitorClass.getName()
.toLowerCase()));
return baseDomainFactory.getOrCreateBoatClass(competitorClass == null ? "" : competitorClass.getName());
}
@Override
@@ -243,7 +237,7 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public Regatta getOrCreateEvent(com.tractrac.clientmodule.Event event) {
public Regatta getOrCreateDefaultRegatta(com.tractrac.clientmodule.Event event, TrackedRegattaRegistry trackedRegattaRegistry) {
synchronized (regattaCache) {
// FIXME Dialog with Lasse by Skype on 2011-06-17:
// [6:20:04 PM] Axel Uhl: Lasse, can Event.getCompetitorClassList() ever produce more than one result?
@@ -258,7 +252,7 @@ public class DomainFactoryImpl implements DomainFactory {
// try a quick look-up in the weak cache using the TracTrac event as key; only if that delivers no result,
// compute the dominant boat class which requires a lot more effort
Regatta result = weakEventCache.get(event);
Regatta result = weakRegattaCache.get(event);
if (result == null) {
Collection<CompetitorClass> competitorClassList = new ArrayList<CompetitorClass>();
for (com.tractrac.clientmodule.Competitor c : event.getCompetitorList()) {
@@ -268,10 +262,14 @@ public class DomainFactoryImpl implements DomainFactory {
Pair<String, String> key = new Pair<String, String>(event.getName(), boatClass == null ? null
: boatClass.getName());
result = regattaCache.get(key);
// FIXME When a Regatta is removed from RacingEventService, it isn't removed here. We use a "stale" regatta here.
// This is particularly bad if a persistent regatta was loaded but a default regatta was accidentally created.
// Then, there is no way but restart the server to get rid of this stale cache entry here.
if (result == null) {
result = new RegattaImpl(event.getName(), boatClass);
result = new RegattaImpl(event.getName(), boatClass, trackedRegattaRegistry);
regattaCache.put(key, result);
weakEventCache.put(event, result);
weakRegattaCache.put(event, result);
logger.info("Created regatta "+result.getName()+" ("+result.hashCode()+") because none found for key "+key);
}
}
return result;
@@ -281,14 +279,15 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public Iterable<Receiver> getUpdateReceivers(DynamicTrackedRegatta trackedRegatta,
com.tractrac.clientmodule.Event tractracEvent, WindStore windStore, TimePoint startOfTracking,
TimePoint endOfTracking, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, ReceiverType... types) {
TimePoint endOfTracking, long delayToLiveInMillis, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, ReceiverType... types) {
Collection<Receiver> result = new ArrayList<Receiver>();
for (ReceiverType type : types) {
switch (type) {
case RACECOURSE:
result.add(new RaceCourseReceiver(
this, trackedRegatta, tractracEvent, windStore,
raceDefinitionSetToUpdate, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND));
raceDefinitionSetToUpdate, delayToLiveInMillis,
WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND));
break;
case MARKPOSITIONS:
result.add(new MarkPositionReceiver(
@@ -313,12 +312,17 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public Iterable<Receiver> getUpdateReceivers(DynamicTrackedRegatta trackedRegatta,
com.tractrac.clientmodule.Event tractracEvent, TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
return getUpdateReceivers(trackedRegatta, tractracEvent, windStore, startOfTracking, endOfTracking,
raceDefinitionSetToUpdate, ReceiverType.RACECOURSE, ReceiverType.MARKPASSINGS,
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS);
com.tractrac.clientmodule.Event tractracEvent, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry) {
return getUpdateReceivers(trackedRegatta, tractracEvent, windStore, startOfTracking, endOfTracking, delayToLiveInMillis,
raceDefinitionSetToUpdate, trackedRegattaRegistry, ReceiverType.RACECOURSE,
ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS);
}
@Override
public Serializable getRaceID(Race tractracRace) {
return tractracRace.getId();
}
@Override
public void removeRace(com.tractrac.clientmodule.Event tractracEvent, Race tractracRace, TrackedRegattaRegistry trackedRegattaRegistry) {
RaceDefinition raceDefinition;
@@ -326,6 +330,7 @@ public class DomainFactoryImpl implements DomainFactory {
raceDefinition = getExistingRaceDefinitionForRace(tractracRace);
if (raceDefinition != null) { // otherwise, this domain factory doesn't seem to know about the race
raceCache.remove(tractracRace);
logger.info("Removed race "+raceDefinition.getName()+" from TracTrac DomainFactoryImpl");
}
}
if (raceDefinition != null) {
@@ -346,8 +351,9 @@ public class DomainFactoryImpl implements DomainFactory {
int oldSize = Util.size(regatta.getAllRaces());
regatta.removeRace(raceDefinition);
if (oldSize > 0 && Util.size(regatta.getAllRaces()) == 0) {
logger.info("Removing regatta "+regatta.getName()+" ("+regatta.hashCode()+") from TracTrac DomainFactoryImpl");
regattaCache.remove(key);
weakEventCache.remove(tractracEvent);
weakRegattaCache.remove(tractracEvent);
}
TrackedRegatta trackedRegatta = trackedRegattaRegistry.getTrackedRegatta(regatta);
if (trackedRegatta != null) {
@@ -364,30 +370,33 @@ public class DomainFactoryImpl implements DomainFactory {
}
@Override
public Pair<RaceDefinition, TrackedRace> getOrCreateRaceDefinitionAndTrackedRace(TrackedRegatta trackedRegatta,
Race race, Course course, WindStore windStore, long millisecondsOverWhichToAverageWind,
public void getOrCreateRaceDefinitionAndTrackedRace(TrackedRegatta trackedRegatta,
Race race, Course course, WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
synchronized (raceCache) {
RaceDefinition raceDefinition = raceCache.get(race);
if (raceDefinition == null) {
Pair<List<Competitor>, BoatClass> competitorsAndDominantBoatClass = getCompetitorsAndDominantBoatClass(race);
logger.info("Creating RaceDefinitionImpl for race "+race.getName());
raceDefinition = new RaceDefinitionImpl(race.getName(), course, competitorsAndDominantBoatClass.getB(),
competitorsAndDominantBoatClass.getA());
// add to domain Event only if boat class matches
competitorsAndDominantBoatClass.getA(), getRaceID(race));
// add to existing regatta only if boat class matches
if (raceDefinition.getBoatClass() == trackedRegatta.getRegatta().getBoatClass()) {
trackedRegatta.getRegatta().addRace(raceDefinition);
createTrackedRace(trackedRegatta, raceDefinition, windStore,
delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate);
logger.info("Added race "+raceDefinition+" to regatta "+trackedRegatta.getRegatta());
synchronized (raceCache) {
raceCache.put(race, raceDefinition);
raceCache.notifyAll();
}
} else {
logger.warning("Not adding race "+raceDefinition+" to regatta "+trackedRegatta.getRegatta()+
" because boat class "+raceDefinition.getBoatClass()+" doesn't match regatta's boat class "+
trackedRegatta.getRegatta().getBoatClass());
}
TrackedRace trackedRace = createTrackedRace(trackedRegatta, raceDefinition, windStore,
millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate);
synchronized (raceCache) {
raceCache.put(race, raceDefinition);
raceCache.notifyAll();
}
return new Pair<RaceDefinition, TrackedRace>(raceDefinition, trackedRace);
} else {
throw new RuntimeException("Race "+race.getName()+" already exists");
}
@@ -395,9 +404,9 @@ public class DomainFactoryImpl implements DomainFactory {
}
private TrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race, WindStore windStore,
long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
return trackedRegatta.createTrackedRace(race,
windStore, millisecondsOverWhichToAverageWind,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) {
return trackedRegatta.createTrackedRace(race,
windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
raceDefinitionSetToUpdate);
}
@@ -463,9 +472,17 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, TimePoint startOfTracking,
TimePoint endOfTracking, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry)
TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry)
throws MalformedURLException, FileNotFoundException, URISyntaxException {
return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, startOfTracking, endOfTracking,
return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, startOfTracking, endOfTracking, delayToLiveInMillis,
windStore, trackedRegattaRegistry);
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore,
TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException {
return new TracTracRaceTrackerImpl(regatta, this, paramURL, liveURI, storedURI, startOfTracking, endOfTracking, delayToLiveInMillis,
windStore, trackedRegattaRegistry);
}
@@ -481,8 +498,8 @@ public class DomainFactoryImpl implements DomainFactory {
@Override
public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
URI storedURI, TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore) {
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, startOfTracking, endOfTracking, windStore, this);
URI storedURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore) {
return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, startOfTracking, endOfTracking, delayToLiveInMillis, windStore, this);
}
}
@@ -36,15 +36,17 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
private final static Logger logger = Logger.getLogger(RaceCourseReceiver.class.getName());
private final long millisecondsOverWhichToAverageWind;
private final long delayToLiveInMillis;
private final WindStore windStore;
private final DynamicRaceDefinitionSet raceDefinitionSetToUpdate;
public RaceCourseReceiver(DomainFactory domainFactory, DynamicTrackedRegatta trackedRegatta,
com.tractrac.clientmodule.Event tractracEvent, WindStore windStore,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, long delayToLiveInMillis,
long millisecondsOverWhichToAverageWind) {
super(domainFactory, tractracEvent, trackedRegatta);
this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind;
this.delayToLiveInMillis = delayToLiveInMillis;
this.windStore = windStore;
this.raceDefinitionSetToUpdate = raceDefinitionSetToUpdate;
}
@@ -81,7 +83,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
// Therefore, don't create TrackedRace again because it already exists.
try {
getDomainFactory().updateCourseWaypoints(existingRaceDefinitionForRace.getCourse(), event.getB().getPoints());
if (getTrackedEvent().getExistingTrackedRace(existingRaceDefinitionForRace) == null) {
if (getTrackedRegatta().getExistingTrackedRace(existingRaceDefinitionForRace) == null) {
createTrackedRace(existingRaceDefinitionForRace);
}
} catch (PatchFailedException e) {
@@ -91,14 +93,14 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
} else {
logger.log(Level.INFO, "Received course for non-existing race "+event.getC().getName()+". Creating RaceDefinition.");
// create race definition and add to event
getDomainFactory().getOrCreateRaceDefinitionAndTrackedRace(getTrackedEvent(), event.getC(), course,
windStore, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate);
getDomainFactory().getOrCreateRaceDefinitionAndTrackedRace(getTrackedRegatta(), event.getC(), course,
windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate);
}
}
private void createTrackedRace(RaceDefinition race) {
getTrackedEvent().createTrackedRace(race,
windStore, millisecondsOverWhichToAverageWind,
getTrackedRegatta().createTrackedRace(race,
windStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
raceDefinitionSetToUpdate);
}
@@ -26,7 +26,7 @@ public class RaceHandleImpl implements RacesHandle {
@Override
public com.sap.sailing.domain.base.Regatta getRegatta() {
return domainFactory.getOrCreateEvent(tractracEvent);
return trackedRegatta.getRegatta();
}
@Override
@@ -39,7 +39,7 @@ public class RaceHandleImpl implements RacesHandle {
}
@Override
public DynamicTrackedRegatta getTrackedEvent() {
public DynamicTrackedRegatta getTrackedRegatta() {
return trackedRegatta;
}
@@ -6,6 +6,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.tracking.RaceTracker;
@@ -22,15 +23,17 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
private final TimePoint endOfTracking;
private final WindStore windStore;
private final DomainFactory domainFactory;
private final long delayToLiveInMillis;
public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore, DomainFactory domainFactory) {
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore, DomainFactory domainFactory) {
super();
this.paramURL = paramURL;
this.liveURI = liveURI;
this.storedURI = storedURI;
this.startOfTracking = startOfTracking;
this.endOfTracking = endOfTracking;
this.delayToLiveInMillis = delayToLiveInMillis;
this.windStore = windStore;
this.domainFactory = domainFactory;
}
@@ -39,7 +42,15 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException,
FileNotFoundException, URISyntaxException {
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, startOfTracking,
endOfTracking, windStore, trackedRegattaRegistry);
endOfTracking, delayToLiveInMillis, windStore, trackedRegattaRegistry);
return tracker;
}
@Override
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry)
throws Exception {
RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI, startOfTracking,
endOfTracking, delayToLiveInMillis, windStore, trackedRegattaRegistry);
return tracker;
}
@@ -48,4 +59,9 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
return TracTracRaceTrackerImpl.createID(paramURL, liveURI, storedURI);
}
@Override
public long getDelayToLiveInMillis() {
return delayToLiveInMillis;
}
}
@@ -1,12 +1,19 @@
package com.sap.sailing.domain.tractracadapter.impl;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
@@ -18,7 +25,10 @@ import java.util.logging.Logger;
import com.maptrack.client.io.TypeController;
import com.sap.sailing.domain.base.Buoy;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreePosition;
@@ -27,11 +37,12 @@ import com.sap.sailing.domain.tracking.AbstractRaceTrackerImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.RacesHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
@@ -56,14 +67,16 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final Event tractracEvent;
private final com.sap.sailing.domain.base.Regatta domainEvent;
private final com.sap.sailing.domain.base.Regatta regatta;
private final Thread ioThread;
private final DataController controller;
private final Set<Receiver> receivers;
private final DomainFactory domainFactory;
private final WindStore windStore;
private final Set<RaceDefinition> races;
private final DynamicTrackedRegatta trackedRegatta;
private final static SimpleDateFormat tracTracDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
/**
* paramURL, liveURI and storedURI for TracTrac connection
*/
@@ -104,16 +117,51 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
* used to create the {@link TrackedRegatta} for the domain event
*/
protected TracTracRaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, WindStore windStore,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore,
TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException,
FileNotFoundException {
this(KeyValue.setup(paramURL), domainFactory, paramURL, liveURI, storedURI, startOfTracking, endOfTracking, delayToLiveInMillis, windStore,
trackedRegattaRegistry);
}
private TracTracRaceTrackerImpl(Event tractracEvent, DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore,
TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException,
FileNotFoundException {
this(tractracEvent, null, domainFactory, paramURL, liveURI, storedURI,
startOfTracking, endOfTracking, delayToLiveInMillis, windStore, trackedRegattaRegistry);
}
/**
* Use this constructor if the {@link Regatta} in which to arrange the {@link RaceDefinition}s created by this
* tracker is already known up-front, particularly if it has a specific configuration to use. Other constructors
* may create a default {@link Regatta} with only a single default {@link Series} and {@link Fleet} which may not
* always be what you want.
*/
protected TracTracRaceTrackerImpl(Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, WindStore windStore,
TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException,
FileNotFoundException {
this(KeyValue.setup(paramURL), regatta, domainFactory, paramURL, liveURI, storedURI, startOfTracking,
endOfTracking, delayToLiveInMillis, windStore, trackedRegattaRegistry);
}
/**
*
* @param regatta if <code>null</code>, then <code>domainFactory.getOrCreateRegatta(tractracEvent)</code> will be used to
* obtain a default regatta
*/
private TracTracRaceTrackerImpl(Event tractracEvent, final Regatta regatta, DomainFactory domainFactory,
URL paramURL, URI liveURI, URI storedURI, TimePoint startOfTracking, TimePoint endOfTracking,
long delayToLiveInMillis, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry)
throws URISyntaxException, MalformedURLException, FileNotFoundException {
super();
this.tractracEvent = tractracEvent;
urls = createID(paramURL, liveURI, storedURI);
this.races = new HashSet<RaceDefinition>();
this.windStore = windStore;
this.domainFactory = domainFactory;
// Read event data from configuration file
tractracEvent = KeyValue.setup(paramURL);
controlPointPositionPoller = scheduleControlPointPositionPoller(paramURL);
// can happen that TracTrac event is null (occurs when there is no Internet connection)
// so lets raise some meaningful exception
@@ -125,18 +173,26 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
controller = new DataController(liveURI, storedURI, this);
// Start live and stored data streams
ioThread = new Thread(controller, "I/O for event "+tractracEvent.getName()+", race URL "+paramURL);
Regatta effectiveRegatta = regatta;
for (Race tractracRace : tractracEvent.getRaceList()) {
// removeRace may detach the domain event from the domain factory if that
// Try to find a pre-associated event based on the Race ID
if (effectiveRegatta == null) {
Serializable raceID = domainFactory.getRaceID(tractracRace);
effectiveRegatta = trackedRegattaRegistry.getRememberedRegattaForRace(raceID);
}
// removeRace may detach the domain regatta from the domain factory if that
// removed the last race; therefore, it's important to getOrCreate the
// domainEvent *after* calling removeRace
// domain regatta *after* calling removeRace
domainFactory.removeRace(tractracEvent, tractracRace, trackedRegattaRegistry);
}
domainEvent = domainFactory.getOrCreateEvent(tractracEvent);
setTrackedRegatta(trackedRegattaRegistry.getOrCreateTrackedRegatta(domainEvent));
// if regatta is still null, no previous assignment of any of the races in this TracTrac event to a Regatta was found;
// in this case, create a default regatta based on the TracTrac event data
this.regatta = effectiveRegatta == null ? domainFactory.getOrCreateDefaultRegatta(tractracEvent, trackedRegattaRegistry) : effectiveRegatta;
trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(this.regatta);
receivers = new HashSet<Receiver>();
Set<TypeController> typeControllers = new HashSet<TypeController>();
for (Receiver receiver : domainFactory.getUpdateReceivers(getTrackedRegatta(), tractracEvent, startOfTracking,
endOfTracking, windStore, this)) {
endOfTracking, delayToLiveInMillis, windStore, this, trackedRegattaRegistry)) {
receivers.add(receiver);
for (TypeController typeController : receiver.getTypeControllersAndStart()) {
typeControllers.add(typeController);
@@ -145,6 +201,11 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
addListenersForStoredDataAndStartController(typeControllers);
}
@Override
public DynamicTrackedRegatta getTrackedRegatta() {
return trackedRegatta;
}
static Triple<URL, URI, URI> createID(URL paramURL, URI liveURI, URI storedURI) {
return new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
}
@@ -167,6 +228,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
if (raceDefinitions != null && !raceDefinitions.isEmpty()) {
logger.info("fetching paramURL to check for new ControlPoint positions...");
Event event = KeyValue.setup(paramURL);
updateStartStopTimesAndLiveDelay(paramURL);
for (ControlPoint controlPoint : event.getControlPointList()) {
com.sap.sailing.domain.base.ControlPoint domainControlPoint = domainFactory.getOrCreateControlPoint(controlPoint);
boolean first = true;
@@ -190,6 +252,72 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
return task;
}
private void updateStartStopTimesAndLiveDelay(URL paramURL) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader((InputStream) paramURL.getContent()));
RaceDefinition currentRace = null;
String line = br.readLine();
Integer delayInSeconds = null;
while (line != null) {
int colonIndex = line.indexOf(':');
if (colonIndex >= 0) {
String propertyName = line.substring(0, colonIndex);
String propertyValue = line.substring(colonIndex+1);
if (propertyName.equals("RaceName")) {
RaceDefinition race = getRegatta().getRaceByName(propertyValue);
if (race != null) {
currentRace = race;
if (delayInSeconds != null) {
final DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(currentRace);
if (trackedRace != null) {
trackedRace.setDelayToLiveInMillis(1000*delayInSeconds);
}
}
}
} else if (propertyName.equals("LiveDelaySecs")) {
try {
delayInSeconds = Integer.valueOf(propertyValue.trim());
} catch (NumberFormatException nfe) {
logger.throwing(TracTracRaceTrackerImpl.class.getName(), "updateStartStopTimesAndLiveDelay", nfe);
}
} else {
final DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(currentRace);
if (trackedRace != null) {
if (propertyName.equals("RaceTrackingStartTime")) {
TimePoint startOfTracking = parseTimePoint(propertyValue);
if (startOfTracking != null) {
trackedRace.setStartOfTrackingReceived(startOfTracking);
}
} else if (propertyName.equals("RaceTrackingEndTime")) {
TimePoint endOfTracking = parseTimePoint(propertyValue);
if (endOfTracking != null) {
trackedRace.setEndOfTrackingReceived(endOfTracking);
}
}
}
}
}
line = br.readLine();
}
br.close();
} catch (IOException e) {
logger.throwing(TracTracRaceTrackerImpl.class.getName(), "updateStartStopTimes", e);
}
}
/**
* @return <code>null</code> if the <code>dateTimeString</code> doesn't parse into a date according to
* {@link #tracTracDateFormat}, or the time point parsed from the string otherwise
*/
private TimePoint parseTimePoint(String dateTimeString) {
try {
Date date = tracTracDateFormat.parse(dateTimeString + " UTC");
return new MillisecondsTimePoint(date);
} catch (ParseException e) {
return null;
}
}
@Override
public Triple<URL, URI, URI> getID() {
return urls;
@@ -219,7 +347,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
@Override
public com.sap.sailing.domain.base.Regatta getRegatta() {
return domainEvent;
return regatta;
}
/**
@@ -15,6 +15,10 @@ import difflib.PatchFailedException;
*
*/
public interface Course extends Named {
/**
* Clients can safely iterate over the resulting list because it's a copy which therefore won't reflect
* waypoint additions and removals.
*/
List<Leg> getLegs();
Iterable<Waypoint> getWaypoints();
@@ -9,6 +9,6 @@ import com.sap.sailing.domain.common.Named;
* @author Axel Uhl (D043530)
*
*/
public interface RaceArea extends Named {
public interface CourseArea extends Named {
}
@@ -31,13 +31,21 @@ public interface DomainFactory {
/**
* Caches an existing waypoint. This is useful, e.g., after de-serialization of a waypoint that has a yet unknown ID.
*
* @param waypoint a waypoint that hasn't been cached in this domain factory before
*/
void cacheWaypoint(Waypoint waypoint);
void cacheWaypoint(Waypoint waypoint);
MarkPassing createMarkPassing(TimePoint timePoint, Waypoint waypoint, Competitor competitor);
BoatClass getOrCreateBoatClass(String name, boolean typicallyStartsUpwind);
/**
* Like {@link #getOrCreateBoatClass(String, boolean)}, only that a default for <code>typicallyStartsUpwind</code> based
* on the boat class name is calculated.
*/
BoatClass getOrCreateBoatClass(String name);
Competitor getExistingCompetitorById(Serializable competitorId);
Competitor createCompetitor(Serializable id, String name, Team team, Boat boat);
@@ -1,13 +1,24 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Named;
/**
* An event is a group of {@link Regatta regattas} carried out at a common venue within a common time frame. For
* example, Kiel Week is an event, and the International German Championship held, e.g., in Travemünde, is an event,
* example, Kiel Week 2011 is an event, and the International German Championship 2011 held, e.g., in Travemünde, is an event,
* too.
*
* @author Axel Uhl (D043530)
*
*/
public interface Event {
public interface Event extends Named {
/**
* @return a non-<code>null</code> venue for this event
*/
Venue getVenue();
Iterable<Regatta> getRegattas();
void addRegatta(Regatta regatta);
void removeRegatta(Regatta regatta);
}
@@ -1,15 +1,34 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.Named;
/**
* For each fleet in a {@link Series} there is one {@link RaceDefinition race} per "race column." Competitor to fleet assignment
* may vary per race in case of the fleets being unordered in their {@link Series}, or may be fixed in case the fleets are
* ordered in their {@link Series}, as usually the case in finals and medal series.
* For each fleet in a {@link Series} there is one {@link RaceDefinition race} per "race column." Competitor to fleet
* assignment may vary per race in case of the fleets being unordered in their {@link Series}, or may be fixed in case
* the fleets are ordered in their {@link Series}, as usually the case in finals and medal series.
* <p>
*
* Fleets within the same {@link Series} as well as all fleets within the same {@link RaceColumn#getFleets() RaceColumn}
* can be mutually compared to each other. Comparing fleets from different series produces undefined results. For a
* {@link Series}, either all distinct fleets compare different (a series with ordered fleets such as Gold, Silver), or
* all distinct fleets compare equal (e.g., for a qualifying series with fleets Yellow and Blue).
*
* @author Axel Uhl (D043530)
*
*
*/
public interface Fleet extends Named {
public interface Fleet extends Named, Comparable<Fleet> {
/**
* The comparability is implemented by an integer field. For fleets of a series to compare equal, the constructor
* without this ordering criterion should be chosen (implicitly setting it to 0).
*/
int getOrdering();
/**
* The color associated with the fleet.
* Normally each fleet gets a color code to distinguish between the fleets like 'blue', 'yellow' etc.
* @return
*/
Color getColor();
}
@@ -0,0 +1,121 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Named;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* One or more races that would be noted together in a single column in a {@link Leaderboard}. If the number of
* competitors of a regatta is too big to have a single start, the set of competitors can be split into several
* {@link Fleet}s. Still, the regatta has a sequence of "races", only that each such "race" actually consists of several
* individual races, each tracked separately, with a separate start and a separate field of competitors which are
* distinct subsets of the regatta's competitors. These races are grouped in a {@link RaceColumn}. The
* {@link RaceColumn} therefore provides access to the {@link TrackedRace}s by {@link Fleet} and by {@link Competitor}.
* The {@link TrackedRace}s represent the data of a race. Over the life time of this object it can be assigned one or more
* {@link TrackedRace}s which then act(s) as data provider to this column. If no tracked race has been assigned, the
* scores reported by this column will all default to zero.<p>
*
* A {@link RaceColumnListener} can be {@link #addRaceColumnListener added} to receive notifications about tracked races
* being {@link #setTrackedRace(Fleet, TrackedRace) linked} to this column or unlinked from this column.
*
* @author Axel Uhl (D043530)
*
*/
public interface RaceColumn extends Named {
void addRaceColumnListener(RaceColumnListener listener);
void removeRaceColumnListener(RaceColumnListener listener);
/**
* @return the fleets for each of which this column has a single race and therefore optionally a {@link TrackedRace}, in
* ascending order; best fleets first
*/
Iterable<? extends Fleet> getFleets();
Fleet getFleetByName(String fleetName);
/**
* By looking at the tracked races linked to this race column, identify the {@link Fleet} in which <code>competitor</code>
* races in this column. If the competitor is not found, caused by no tracked races being associated with this race column
* in which <code>competitor</code> competes, <code>null</code> is returned.
*/
Fleet getFleetOfCompetitor(Competitor competitor);
/**
* This does also update the {@link #getRaceIdentifier(Fleet) race identifier} by setting it to <code>null</code> if <code>race</code>
* is <code>null</code>, and <code>race.getRaceIdentifier()</code> otherwise.
*
* @param fleet the fleet within this column with which to associate <code>race</code>
*/
void setTrackedRace(Fleet fleet, TrackedRace race);
/**
* Tells if at least one {@link TrackedRace} is associated with this race column. Short for
* {@link Util#isEmpty(Iterable) isEmpty(}{@link #getFleets() getFleets())}.
*/
boolean hasTrackedRaces();
/**
* @param fleet
* the fleet whose associated tracked race to obtain
*
* @return <code>null</code> if this column does not currently have a tracked race associated for <code>fleet</code>
* ; otherwise the tracked race for <code>fleet</code> from where all information relevant to this column
* can be obtained. See also {@link #getRaceIdentifier(Fleet)}.
*/
TrackedRace getTrackedRace(Fleet fleet);
/**
* Tries to find a tracked race whose {@link RaceDefinition#getCompetitors() competitors} contain <code>competitor</code>. If
* no such {@link TrackedRace} is currently associated with this race column, <code>null</code> is returned. No two
* {@link TrackedRace}s may result because a single competitor can be part of only one fleet and therefore not occur
* twice in a single {@link RaceColumn}.
*/
TrackedRace getTrackedRace(Competitor competitor);
/**
* If this column ever was assigned to a tracked race, that race's identifier can be obtained using this method;
* otherwise, <code>null</code> is returned.
*
* @param fleet
* the fleet for which to obtain the race identifier
*/
RaceIdentifier getRaceIdentifier(Fleet fleet);
/**
* Records that this leaderboard column is to be associated with the race identified by <code>raceIdentifier</code>.
* This does not automatically load the tracked race, but the information may be used to re-associate a tracked race
* with this column based on its {@link TrackedRace#getRaceIdentifier() race identifier}.
*
* @param fleet
* the fleet for which to associate a race by its identifier
*/
void setRaceIdentifier(Fleet fleet, RaceIdentifier raceIdentifier);
/**
* A "medal race" cannot be discarded. It's score is doubled during score aggregation.
*/
boolean isMedalRace();
void setName(String newName);
/**
* Constructs a key for maps storing corrections such as score corrections and max points reasons.
*/
Pair<Competitor, RaceColumn> getKey(Competitor competitor);
/**
* Releases the {@link TrackedRace} previously set by {@link #setTrackedRace(Fleet, TrackedRace)} but leaves the
* {@link #getRaceIdentifier(Fleet) race identifier} untouched. Therefore, the {@link TrackedRace} may be garbage
* collected but may be re-resolved for this column using the race identifier at a later time.
*
* @param fleet
* the fleet for which to release its tracked race
*/
void releaseTrackedRace(Fleet fleet);
}
@@ -0,0 +1,7 @@
package com.sap.sailing.domain.base;
public interface RaceColumnInSeries extends RaceColumn {
Series getSeries();
Regatta getRegatta();
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.base;
import java.io.Serializable;
import com.sap.sailing.domain.tracking.TrackedRace;
public interface RaceColumnListener extends Serializable {
void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace);
void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace);
}
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.Named;
* @author Axel Uhl (D043530)
*
*/
public interface RaceDefinition extends Named {
public interface RaceDefinition extends Named, WithID {
BoatClass getBoatClass();
Course getCourse();
@@ -20,15 +20,19 @@ public interface Regatta extends Named {
*/
Iterable<? extends Series> getSeries();
/**
* @return the first series from {@link #getSeries} whose {@link Series#getName() name} equals
* <code>seriesName<code>,
* or <code>null</code> if no such series exists
*/
Series getSeriesByName(String seriesName);
/**
* Please note that the {@link RaceDefinition}s of the {@link Regatta} are not necessarily in sync with the
* {@link TrackedRace}s of the {@link TrackedRegatta} whose {@link TrackedRegatta#getRegatta() regatta} is this regatta.
* For example, it may be the case that a {@link RaceDefinition} is returned by this method for which no
* {@link TrackedRace} exists in the corresponding {@link TrackedRegatta}. This could be the case, e.g., during
* the initialization of the tracker as well as during removing a race from the server.<p>
*
* Callers iterating over the result that anticipate concurrent modifications shall synchronize the iteration
* on the result.
*/
Iterable<RaceDefinition> getAllRaces();
@@ -60,4 +64,16 @@ public interface Regatta extends Named {
* may require the base name to which the boat class name will be appended. This method emits the base name.
*/
String getBaseName();
/**
* Regattas may be constructed as implicit default regattas in which case they won't need to be stored
* durably and don't contain valuable information worth being preserved; or they are constructed explicitly
* with series and race columns in which case this data needs to be protected. This flag indicates whether
* the data of this regatta needs to be maintained persistently.
*/
boolean isPersistent();
void addRaceColumnListener(RaceColumnListener listener);
void removeRaceColumnListener(RaceColumnListener listener);
}
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.base;
import java.util.ConcurrentModificationException;
import com.sap.sailing.domain.common.RegattaIdentifier;
public interface RegattaRegistry {
/**
* @return a thread-safe copy of the regattas currently known by the service; it's safe for callers to iterate over
* the iterable returned, and no risk of a {@link ConcurrentModificationException} exists
*/
Iterable<Regatta> getAllRegattas();
Regatta getRegattaByName(String name);
Regatta getRegatta(RegattaIdentifier regattaIdentifier);
}
@@ -1,7 +1,8 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.Named;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
/**
* A series is a part of a {@link Regatta}. Rounds are ordered within the regatta, and rules for who is assigned to
@@ -10,12 +11,18 @@ import com.sap.sailing.domain.leaderboard.RaceColumn;
* race column have to be run in this round. For example, if the 49er regatta has so many competitors that they cannot
* all start in one race, the qualification round can be split into two {@link Fleet}s, "Yellow" and "Blue," each
* getting their separate races. Fleet assignment may or may not vary. This usually depends on the round's
* characteristics of having ordered or unordered fleets.
* characteristics of having ordered or unordered fleets.<p>
*
* To receive notifications when {@link TrackedRace tracked races} are linked to or unlinked from any of this series'
* columns, {@link RaceColumnListener}s can be added / removed.
*
* @author Axel Uhl (D043530)
*
*/
public interface Series extends Named {
/**
* Returns the fleets of this series, in ascending order, better fleets first.
*/
Iterable<? extends Fleet> getFleets();
/**
@@ -24,12 +31,36 @@ public interface Series extends Named {
* something called "R2", those are as many "races" as we have fleets; therefore, we use "race column" instead to
* describe all "races" named, e.g., "R3" in a series.
*/
Iterable<? extends RaceColumn> getRaceColumns();
Iterable<? extends RaceColumnInSeries> getRaceColumns();
RaceColumnInSeries getRaceColumnByName(String columnName);
/**
* Tells whether this is the "last" / "medal" race series, usually having only one race. This may have implications
* on the scoring scheme (usually, medal races scores are doubled and cannot be discarded).
*/
boolean isMedal();
void setIsMedal(boolean isMedal);
Fleet getFleetByName(String fleetName);
RaceColumnInSeries addRaceColumn(String raceColumnName, TrackedRegattaRegistry trackedRegattaRegistry);
void moveRaceColumnUp(String raceColumnName);
void moveRaceColumnDown(String raceColumnName);
void removeRaceColumn(String raceColumnName);
Regatta getRegatta();
/**
* Tells whether the fleets returned by {@link #getFleets()} have a relevant ordering. This is the case particularly
* for final series with fleets such as "Gold" and "Silver" but usually not for qualification series with fleets such
* as "Yellow" and "Blue" which don't have an ordering, particularly for ranking considerations.
* Sets this series' regatta.
*/
boolean isFleetsOrdered();
void setRegatta(Regatta regatta);
void addRaceColumnListener(RaceColumnListener listener);
void removeRaceColumnListener(RaceColumnListener listener);
}
@@ -9,5 +9,9 @@ import com.sap.sailing.domain.common.Named;
*
*/
public interface Venue extends Named {
Iterable<RaceArea> getRaceAreas();
Iterable<CourseArea> getCourseAreas();
void addCourseArea(CourseArea courseArea);
void removeCourseArea(CourseArea courseArea);
}
@@ -0,0 +1,150 @@
package com.sap.sailing.domain.base.impl;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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.base.RaceColumnListener;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.tracking.TrackedRace;
public abstract class AbstractRaceColumn implements RaceColumn {
private static final long serialVersionUID = -7801617988982540470L;
private final Map<Fleet, TrackedRace> trackedRaces;
private String name;
private final Map<Fleet, RaceIdentifier> raceIdentifiers;
private Set<RaceColumnListener> raceColumnListeners;
public AbstractRaceColumn(String name) {
this.name = name;
this.trackedRaces = new HashMap<Fleet, TrackedRace>();
this.raceIdentifiers = new HashMap<Fleet, RaceIdentifier>();
this.raceColumnListeners = new HashSet<RaceColumnListener>();
}
@Override
public void addRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.add(listener);
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.remove(listener);
}
private void notifyListenersAboutTrackedRaceLinked(Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceLinked(this, fleet, trackedRace);
}
}
private void notifyListenersAboutTrackedRaceUnlinked(Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceUnlinked(this, fleet, trackedRace);
}
}
@Override
public TrackedRace getTrackedRace(Fleet fleet) {
return trackedRaces.get(fleet);
}
@Override
public void setTrackedRace(Fleet fleet, TrackedRace trackedRace) {
TrackedRace previouslyLinkedRace = this.trackedRaces.get(fleet);
if (trackedRace == null) {
setRaceIdentifier(fleet, null);
this.trackedRaces.remove(fleet);
} else {
this.trackedRaces.put(fleet, trackedRace);
this.setRaceIdentifier(fleet, trackedRace.getRaceIdentifier());
}
if (previouslyLinkedRace != null) {
notifyListenersAboutTrackedRaceUnlinked(fleet, previouslyLinkedRace);
}
if (trackedRace != null) {
notifyListenersAboutTrackedRaceLinked(fleet, trackedRace);
}
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String newName) {
this.name = newName;
}
@Override
public Pair<Competitor, RaceColumn> getKey(Competitor competitor) {
return new Pair<Competitor, RaceColumn>(competitor, this);
}
@Override
public RaceIdentifier getRaceIdentifier(Fleet fleet) {
return raceIdentifiers.get(fleet);
}
@Override
public void setRaceIdentifier(Fleet fleet, RaceIdentifier raceIdentifier) {
if (raceIdentifier == null) {
this.raceIdentifiers.remove(fleet);
} else {
this.raceIdentifiers.put(fleet, raceIdentifier);
}
}
@Override
public void releaseTrackedRace(Fleet fleet) {
trackedRaces.remove(fleet);
}
@Override
public Fleet getFleetOfCompetitor(Competitor competitor) {
for (Map.Entry<Fleet, TrackedRace> e : trackedRaces.entrySet()) {
if (Util.contains(e.getValue().getRace().getCompetitors(), competitor)) {
return e.getKey();
}
}
return null;
}
@Override
public Fleet getFleetByName(String fleetName) {
for (Fleet fleet : getFleets()) {
if (fleet.getName().equals(fleetName)) {
return fleet;
}
}
return null;
}
@Override
public boolean hasTrackedRaces() {
return !trackedRaces.isEmpty();
}
@Override
public TrackedRace getTrackedRace(Competitor competitor) {
for (TrackedRace trackedRace : trackedRaces.values()) {
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
return trackedRace;
}
}
return null;
}
@Override
public String toString() {
return getName();
}
}
@@ -0,0 +1,12 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class CourseAreaImpl extends NamedImpl implements CourseArea {
private static final long serialVersionUID = 5912385360170509150L;
public CourseAreaImpl(String name) {
super(name);
}
}
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -87,6 +86,14 @@ public class CourseImpl extends NamedImpl implements Course {
@Override
public synchronized void addWaypoint(int zeroBasedPosition, Waypoint waypointToAdd) {
waypoints.add(zeroBasedPosition, waypointToAdd);
Map<Waypoint, Integer> updatesToWaypointIndexes = new HashMap<Waypoint, Integer>();
updatesToWaypointIndexes.put(waypointToAdd, zeroBasedPosition);
for (Map.Entry<Waypoint, Integer> e : waypointIndexes.entrySet()) {
if (e.getValue() >= zeroBasedPosition) {
updatesToWaypointIndexes.put(e.getKey(), e.getValue()+1);
}
}
waypointIndexes.putAll(updatesToWaypointIndexes);
int legStartWaypointIndex;
if (zeroBasedPosition == waypoints.size()-1) { // added to end
legStartWaypointIndex = zeroBasedPosition-1;
@@ -104,6 +111,14 @@ public class CourseImpl extends NamedImpl implements Course {
if (zeroBasedPosition >= 0) {
boolean isLast = zeroBasedPosition == waypoints.size()-1;
Waypoint removedWaypoint = waypoints.remove(zeroBasedPosition);
waypointIndexes.remove(removedWaypoint);
Map<Waypoint, Integer> updatesToWaypointIndexes = new HashMap<Waypoint, Integer>();
for (Map.Entry<Waypoint, Integer> e : waypointIndexes.entrySet()) {
if (e.getValue() > zeroBasedPosition) { // only > because the entry with == was just removed
updatesToWaypointIndexes.put(e.getKey(), e.getValue()-1);
}
}
waypointIndexes.putAll(updatesToWaypointIndexes);
if (isLast) {
if (waypoints.size() > 0) { // if we had only one waypoint, we didn't have any legs
// last waypoint was removed; remove last leg
@@ -141,8 +156,8 @@ public class CourseImpl extends NamedImpl implements Course {
}
@Override
public List<Leg> getLegs() {
return Collections.unmodifiableList(legs);
public synchronized List<Leg> getLegs() {
return new ArrayList<Leg>(legs);
}
@Override
@@ -167,7 +182,7 @@ public class CourseImpl extends NamedImpl implements Course {
}
@Override
public int getIndexOfWaypoint(Waypoint waypoint) {
public synchronized int getIndexOfWaypoint(Waypoint waypoint) {
int result = -1;
Integer indexEntry = waypointIndexes.get(waypoint);
if (indexEntry != null) {
@@ -4,8 +4,11 @@ import java.awt.TrayIcon.MessageType;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import com.sap.sailing.domain.base.Boat;
@@ -38,12 +41,15 @@ public class DomainFactoryImpl implements DomainFactory {
private final WeakHashMap<Serializable, Waypoint> waypointCache;
private final Set<String> mayStartWithNoUpwindLeg;
public DomainFactoryImpl() {
nationalityCache = new HashMap<String, Nationality>();
buoyCache = new HashMap<String, Buoy>();
boatClassCache = new HashMap<String, BoatClass>();
competitorCache = new HashMap<Serializable, Competitor>();
waypointCache = new WeakHashMap<Serializable, Waypoint>();
mayStartWithNoUpwindLeg = new HashSet<String>(Arrays.asList(new String[] { "extreme40", "ess", "ess40" }));
}
@Override
@@ -114,6 +120,11 @@ public class DomainFactoryImpl implements DomainFactory {
return result;
}
}
@Override
public BoatClass getOrCreateBoatClass(String name) {
return getOrCreateBoatClass(name, /* typicallyStartsUpwind */!mayStartWithNoUpwindLeg.contains(name.toLowerCase()));
}
@Override
public Competitor getExistingCompetitorById(Serializable competitorId) {
@@ -0,0 +1,53 @@
package com.sap.sailing.domain.base.impl;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class EventImpl extends NamedImpl implements Event {
private static final long serialVersionUID = 855135446595485715L;
private final Set<Regatta> regattas;
private final Venue venue;
public EventImpl(String name, String venueName) {
this(name, new VenueImpl(venueName));
}
/**
* @param venue must not be <code>null</code>
*/
public EventImpl(String name, Venue venue) {
super(name);
assert venue != null;
this.venue = venue;
this.regattas = new HashSet<Regatta>();
}
@Override
public Iterable<Regatta> getRegattas() {
return Collections.unmodifiableSet(regattas);
}
@Override
public void addRegatta(Regatta regatta) {
regattas.add(regatta);
}
@Override
public void removeRegatta(Regatta regatta) {
regattas.remove(regatta);
}
@Override
public Venue getVenue() {
return venue;
}
}
@@ -1,12 +1,56 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.impl.NamedImpl;
/**
* The comparability is implemented by an integer field. For fleets of a series to compare equal, the constructor
* without this ordering criterion should be chosen (implicitly setting it to 0).
*
* @author Axel Uhl (D043530)
*
*/
public class FleetImpl extends NamedImpl implements Fleet {
private static final long serialVersionUID = 7560417723293278246L;
private final int ordering;
private final Color color;
public FleetImpl(String name) {
this(name, 0);
}
/**
* @param ordering
* a lesser value for <code>ordering</code> means "better"; for example, use 1 for the Gold fleet and 2
* for the Silver fleet.
*/
public FleetImpl(String name, int ordering) {
super(name);
this.ordering = ordering;
this.color = null;
}
public FleetImpl(String name, int ordering, Color color) {
super(name);
this.ordering = ordering;
this.color = color;
}
@Override
public int compareTo(Fleet o) {
return this.ordering - ((FleetImpl) o).ordering;
}
@Override
public int getOrdering() {
return ordering;
}
@Override
public Color getColor() {
return color;
}
}
@@ -0,0 +1,80 @@
package com.sap.sailing.domain.base.impl;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
/**
* Obtains flees and medal information from the {@link Series} to which it is connected at construction time.
*
* @author Axel Uhl (D043530)
*
*/
public class RaceColumnInSeriesImpl extends AbstractRaceColumn implements RaceColumnInSeries {
private static final long serialVersionUID = -2199678838624406645L;
private final Series series;
private final TrackedRegattaRegistry trackedRegattaRegistry;
/**
* @param trackedRegattaRegistry
* used to find the {@link TrackedRegatta} for this column's series' {@link Series#getRegatta() regatta}
* in order to re-associate a {@link TrackedRace} passed to {@link #setTrackedRace(Fleet, TrackedRace)}
* with this column's series' {@link TrackedRegatta}, and the tracked race's {@link RaceDefinition} with
* this column's series {@link Regatta}, respectively. If <code>null</code>, the re-association won't be
* carried out.
*/
public RaceColumnInSeriesImpl(String name, Series series, TrackedRegattaRegistry trackedRegattaRegistry) {
super(name);
this.series = series;
this.trackedRegattaRegistry = trackedRegattaRegistry;
}
@Override
public Regatta getRegatta() {
return getSeries().getRegatta();
}
@Override
public Iterable<? extends Fleet> getFleets() {
return series.getFleets();
}
@Override
public boolean isMedalRace() {
return series.isMedal();
}
@Override
public Series getSeries() {
return series;
}
/**
* In addition to associating the tracked race to this column for the fleet specified, if this column has a non-
* <code>null</code> {@link #trackedRegattaRegistry}, this method also verifies that <code>trackedRace</code>
* belongs to the {@link TrackedRegatta} of this column's series' {@link Series#getRegatta() regatta} and that the
* corresponding {@link TrackedRace#getRace() RaceDefinition} is owned by this column's series'
* {@link Series#getRegatta() regatta}. If not, an {@link IllegalArgumentException} is thrown.
*/
@Override
public void setTrackedRace(Fleet fleet, TrackedRace trackedRace) {
if (trackedRegattaRegistry != null) {
if (trackedRace != null) {
TrackedRegatta trackedRegatta = trackedRace.getTrackedRegatta();
Regatta regatta = trackedRegatta.getRegatta();
if (regatta != getRegatta()) {
throw new IllegalArgumentException("Trying to associate tracked race of regatta "
+ regatta.getName() + " to a race column of regatta " + getRegatta().getName());
}
}
}
// re-associating the TrackedRace needs to happen before the super call because the RaceIdentifier may have changed
super.setTrackedRace(fleet, trackedRace);
}
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.base.impl;
import java.io.Serializable;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
@@ -11,12 +13,23 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
private final Course course;
private final Iterable<Competitor> competitors;
private final BoatClass boatClass;
private final Serializable id;
public RaceDefinitionImpl(String name, Course course, BoatClass boatClass, Iterable<Competitor> competitors) {
this(name, course, boatClass, competitors, /* use name as default ID */ name);
}
public RaceDefinitionImpl(String name, Course course, BoatClass boatClass, Iterable<Competitor> competitors, Serializable id) {
super(name);
this.course = course;
this.competitors = competitors;
this.boatClass = boatClass;
this.id = id;
}
@Override
public Serializable getId() {
return id;
}
@Override
@@ -6,9 +6,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaListener;
@@ -16,29 +20,55 @@ import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.impl.NamedImpl;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
public class RegattaImpl extends NamedImpl implements Regatta {
public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListener {
private static final Logger logger = Logger.getLogger(RegattaImpl.class.getName());
private static final long serialVersionUID = 6509564189552478869L;
private final Set<RaceDefinition> races;
private final BoatClass boatClass;
private transient Set<RegattaListener> regattaListeners;
private final Iterable<? extends Series> series;
private Set<RaceColumnListener> raceColumnListeners;
/**
* Regattas may be constructed as implicit default regattas in which case they won't need to be stored
* durably and don't contain valuable information worth being preserved; or they are constructed explicitly
* with series and race columns in which case this data needs to be protected. This flag indicates whether
* the data of this regatta needs to be maintained persistently.
*
* @see #isPersistent
*/
private final boolean persistent;
/**
* Constructs a regatta with a single default series with empty race column list, and a single default fleet.
* Constructs a regatta with a single default series with empty race column list, and a single default fleet which
* is not {@link #isPersistent() marked for persistence}.
*/
public RegattaImpl(String baseName, BoatClass boatClass) {
this(baseName, boatClass, Collections.singletonList(new SeriesImpl("Default", /* isFleetsOrdered */true,
Collections.singletonList(new FleetImpl("Default")), new ArrayList<RaceColumn>())));
public RegattaImpl(String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry) {
this(baseName, boatClass, Collections.singletonList(new SeriesImpl("Default", /* isMedal */false, Collections
.singletonList(new FleetImpl("Default")), /* race column names */new ArrayList<String>(),
trackedRegattaRegistry)), /* persistent */false);
}
public RegattaImpl(String baseName, BoatClass boatClass, Iterable<? extends Series> series) {
/**
* @param series
* all {@link Series} in this iterable will have their {@link Series#setRegatta(Regatta) regatta set} to
* this new regatta.
*/
public RegattaImpl(String baseName, BoatClass boatClass, Iterable<? extends Series> series, boolean persistent) {
super(baseName+(boatClass==null?"":" ("+boatClass.getName()+")"));
races = new HashSet<RaceDefinition>();
regattaListeners = new HashSet<RegattaListener>();
raceColumnListeners = new HashSet<RaceColumnListener>();
this.boatClass = boatClass;
this.series = series;
for (Series s : series) {
s.setRegatta(this);
s.addRaceColumnListener(this);
}
this.persistent = persistent;
}
@Override
@@ -52,6 +82,11 @@ public class RegattaImpl extends NamedImpl implements Regatta {
return result;
}
@Override
public boolean isPersistent() {
return persistent;
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
regattaListeners = new HashSet<>();
@@ -61,10 +96,22 @@ public class RegattaImpl extends NamedImpl implements Regatta {
public Iterable<? extends Series> getSeries() {
return series;
}
@Override
public Series getSeriesByName(String name) {
for (Series s : getSeries()) {
if (s.getName().equals(name)) {
return s;
}
}
return null;
}
@Override
public Iterable<RaceDefinition> getAllRaces() {
return races;
synchronized (races) {
return new ArrayList<RaceDefinition>(races);
}
}
@Override
@@ -74,19 +121,17 @@ public class RegattaImpl extends NamedImpl implements Regatta {
@Override
public RaceDefinition getRaceByName(String raceName) {
Iterable<RaceDefinition> allRaces = getAllRaces();
synchronized (allRaces) {
for (RaceDefinition r : getAllRaces()) {
if (r.getName().equals(raceName)) {
return r;
}
for (RaceDefinition r : getAllRaces()) {
if (r.getName().equals(raceName)) {
return r;
}
return null;
}
return null;
}
@Override
public void addRace(RaceDefinition race) {
logger.info("Adding race "+race.getName()+" to regatta "+getName()+" ("+hashCode()+")");
if (getBoatClass() != null && race.getBoatClass() != getBoatClass()) {
throw new IllegalArgumentException("Boat class "+race.getBoatClass()+" doesn't match regatta's boat class "+getBoatClass());
}
@@ -103,6 +148,7 @@ public class RegattaImpl extends NamedImpl implements Regatta {
@Override
public void removeRace(RaceDefinition race) {
synchronized (races) {
logger.info("Removing race "+race.getName()+" from regatta "+getName()+" ("+hashCode()+")");
races.remove(race);
}
synchronized (regattaListeners) {
@@ -142,4 +188,36 @@ public class RegattaImpl extends NamedImpl implements Regatta {
}
}
@Override
public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
}
@Override
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
@Override
public void addRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.add(listener);
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.remove(listener);
}
private void notifyListenersAboutTrackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceLinked(raceColumn, fleet, trackedRace);
}
}
private void notifyListenersAboutTrackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
}
}
@@ -1,34 +1,180 @@
package com.sap.sailing.domain.base.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.impl.NamedImpl;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
public class SeriesImpl extends NamedImpl implements Series {
public class SeriesImpl extends NamedImpl implements Series, RaceColumnListener {
private static final long serialVersionUID = -1640404303144907381L;
private final boolean isFleetsOrdered;
private final Iterable<? extends Fleet> fleets;
private final Iterable<? extends RaceColumn> raceColumns;
private final Map<String, Fleet> fleetsByName;
private final List<Fleet> fleetsInAscendingOrder;
private final List<RaceColumnInSeries> raceColumns;
private boolean isMedal;
private Regatta regatta;
private Set<RaceColumnListener> raceColumnListeners;
public SeriesImpl(String name, boolean isFleetsOrdered, Iterable<? extends Fleet> fleets, Iterable<? extends RaceColumn> raceColumns) {
/**
* @param fleets must be non-empty
*/
public SeriesImpl(String name, boolean isMedal, Iterable<? extends Fleet> fleets, Iterable<String> raceColumnNames,
TrackedRegattaRegistry trackedRegattaRegistry) {
super(name);
this.isFleetsOrdered = isFleetsOrdered;
this.fleets = fleets;
this.raceColumns = raceColumns;
}
public boolean isFleetsOrdered() {
return isFleetsOrdered;
}
public Iterable<? extends Fleet> getFleets() {
return fleets;
if (fleets == null || Util.isEmpty(fleets)) {
throw new IllegalArgumentException("Series must have at least one fleet");
}
this.fleetsByName = new HashMap<String, Fleet>();
for (Fleet fleet : fleets) {
this.fleetsByName.put(fleet.getName(), fleet);
}
fleetsInAscendingOrder = new ArrayList<Fleet>(fleetsByName.values());
Collections.sort(fleetsInAscendingOrder);
List<RaceColumnInSeries> myRaceColumns = new ArrayList<RaceColumnInSeries>();
this.raceColumns = myRaceColumns;
this.isMedal = isMedal;
this.raceColumnListeners = new HashSet<RaceColumnListener>();
for (String raceColumnName : raceColumnNames) {
addRaceColumn(raceColumnName, trackedRegattaRegistry);
}
}
@Override
public Iterable<? extends RaceColumn> getRaceColumns() {
public void addRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.add(listener);
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.remove(listener);
}
private void notifyListenersAboutTrackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceLinked(raceColumn, fleet, trackedRace);
}
}
private void notifyListenersAboutTrackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
}
@Override
public Regatta getRegatta() {
return regatta;
}
@Override
public void setRegatta(Regatta regatta) {
this.regatta = regatta;
}
public Iterable<? extends Fleet> getFleets() {
return fleetsInAscendingOrder;
}
@Override
public Fleet getFleetByName(String fleetName) {
return fleetsByName.get(fleetName);
}
@Override
public Iterable<? extends RaceColumnInSeries> getRaceColumns() {
return raceColumns;
}
@Override
public RaceColumnInSeries addRaceColumn(String raceColumnName, TrackedRegattaRegistry trackedRegattaRegistry) {
final RaceColumnInSeriesImpl result = createRaceColumn(raceColumnName, trackedRegattaRegistry);
result.addRaceColumnListener(this);
raceColumns.add(result);
return result;
}
private RaceColumnInSeriesImpl createRaceColumn(String raceColumnName, TrackedRegattaRegistry trackedRegattaRegistry) {
RaceColumnInSeriesImpl result = new RaceColumnInSeriesImpl(raceColumnName, this, trackedRegattaRegistry);
return result;
}
@Override
public void moveRaceColumnUp(String raceColumnName) {
// start at second element because first can't be moved up
for (int i=1; i<raceColumns.size(); i++) {
RaceColumnInSeries rc = raceColumns.get(i);
if (rc.getName().equals(raceColumnName)) {
raceColumns.remove(i);
raceColumns.add(i-1, rc);
break;
}
}
}
@Override
public void moveRaceColumnDown(String raceColumnName) {
// end at second-last element because last can't be moved down
for (int i=0; i<raceColumns.size()-1; i++) {
RaceColumnInSeries rc = raceColumns.get(i);
if (rc.getName().equals(raceColumnName)) {
raceColumns.remove(i);
raceColumns.add(i+1, rc);
break;
}
}
}
@Override
public void removeRaceColumn(String raceColumnName) {
RaceColumnInSeries rc = getRaceColumnByName(raceColumnName);
if (rc != null) {
raceColumns.remove(rc);
rc.removeRaceColumnListener(this);
}
}
@Override
public RaceColumnInSeries getRaceColumnByName(String columnName) {
for (RaceColumnInSeries raceColumn : getRaceColumns()) {
if (raceColumn.getName().equals(columnName)) {
return raceColumn;
}
}
return null;
}
@Override
public boolean isMedal() {
return isMedal;
}
@Override
public void setIsMedal(boolean isMedal) {
this.isMedal = isMedal;
}
@Override
public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
}
@Override
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
}
@@ -0,0 +1,40 @@
package com.sap.sailing.domain.base.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.common.impl.NamedImpl;
public class VenueImpl extends NamedImpl implements Venue {
private static final long serialVersionUID = 6854152040737643290L;
/**
* The course areas are ordered because they typically follow an ordered naming pattern borrowed from the
* "NATO alphabet" (Alpha, Bravo, Charlie, ...).
*/
private final List<CourseArea> courseAreas;
public VenueImpl(String name) {
super(name);
courseAreas = new ArrayList<CourseArea>();
}
@Override
public Iterable<CourseArea> getCourseAreas() {
return Collections.unmodifiableList(courseAreas);
}
@Override
public void addCourseArea(CourseArea courseArea) {
courseAreas.add(courseArea);
}
@Override
public void removeCourseArea(CourseArea courseArea) {
courseAreas.remove(courseArea);
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.domain.leaderboard;
import java.io.Serializable;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* For a {@link Leaderboard}, captures corrections such as {@link ScoreCorrection}s or display names for competitors. It
* does so by keying the corrections by competitor names which can work as a competitor "ID," e.g., in a persistence layer.
* When for any of the leaderboards columns a new {@link TrackedRace} is linked, the not yet resolved corrections are matched
* against the tracked race's competitor names and resolved. For this to work, this object needs to be registered as
* {@link RaceColumnListener} with the object owning all {@link RaceColumn}s for the leaderboard. The type of object
* may vary depending on the concrete leaderboard type.
*
* @author Axel Uhl (D043530)
*
*/
public interface DelayedLeaderboardCorrections extends RaceColumnListener, Serializable {
void correctScore(String competitorName, RaceColumn raceColumn, int correctedScore);
void setCarriedPoints(String competitorName, int carriedPoints);
void setMaxPointsReason(String competitorName, RaceColumn raceColumn, MaxPointsReason maxPointsReason);
void setDisplayName(String competitorName, String displayName);
Leaderboard getLeaderboard();
}
@@ -0,0 +1,76 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A leaderboard that allows its clients to flexibly modify the race columns arranged in this leaderboard without the need to adhere
* to the constraints of a {@link Regatta} with its {@link Series} and {@link Fleet}s.
*
* @author Axel Uhl (D043530)
*
*/
public interface FlexibleLeaderboard extends Leaderboard {
/**
* Moves the column with the name <code>name</code> up.
* @param name The name of the column to move.
*/
void moveRaceColumnUp(String name);
/**
* Moves the column with the name <code>name</code> down.
* @param name The name of the column to move.
*/
void moveRaceColumnDown(String name);
/**
* Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard.
*
* @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
* @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);
/**
* 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.
*
* @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);
void removeRaceColumn(String columnName);
void updateIsMedalRace(String raceName, boolean isMedalRace);
/**
* A leaderboard can be renamed. If a leaderboard is managed in a structure that keys leaderboards by name,
* that structure's rules have to be obeyed to ensure the structure's consistency. For example,
* <code>RacingEventService</code> has a <code>renameLeaderboard</code> method that ensures the internal
* structure's consistency and invokes this method.
*/
void setName(String newName);
}
@@ -0,0 +1,7 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.RaceColumn;
public interface FlexibleRaceColumn extends RaceColumn {
void setIsMedalRace(boolean isMedalRace);
}
@@ -1,8 +1,13 @@
package com.sap.sailing.domain.leaderboard;
import java.util.List;
import java.util.Map;
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.base.RaceColumnListener;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.Named;
import com.sap.sailing.domain.common.NoWindException;
@@ -43,6 +48,12 @@ public interface Leaderboard extends Named {
* Tells if the net points have been corrected by a {@link ScoreCorrection}
*/
boolean isNetPointsCorrected();
/**
* @return <code>null</code>, if the competitor's fleet in the race column cannot be determined, the
* {@link Fleet} otherwise
*/
Fleet getFleet();
}
/**
@@ -51,6 +62,14 @@ public interface Leaderboard extends Named {
*/
Iterable<Competitor> getCompetitors();
/**
* Returns the first fleet found in the sequence of this leaderboard's {@link #getRaceColumns() race columns}'
* {@link RaceColumn#getFleets() fleets} whose name equals <code>fleetName</code>. If no such fleet is found,
* <code>null</code> is returned. If <code>fleetName</code> is <code>null</code>, the leaderboard may return
* a default fleet if it has one, or <code>null</code> otherwise.
*/
Fleet getFleet(String fleetName);
Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
/**
@@ -62,19 +81,24 @@ public interface Leaderboard extends Named {
/**
* Shorthand for {@link TrackedRace#getRank(Competitor, com.sap.sailing.domain.common.TimePoint)} with the
* additional logic that in case the <code>race</code> hasn't {@link TrackedRace#hasStarted(TimePoint) started}
* yet, 0 points will be allotted to the race for all competitors.
* additional logic that in case the <code>race</code> hasn't {@link TrackedRace#hasStarted(TimePoint) started} yet
* or no {@link TrackedRace} exists for <code>race</code>, 0 will be returned for all those competitors. The tracked
* race for the correct {@link Fleet} is determined using {@link RaceColumn#getTrackedRace(Competitor)}.<p>
*
* For each competitor tracking-wise ranking better than <code>competitor</code> but with
* a {@link #getMaxPointsReason(Competitor, RaceColumn, TimePoint) disqualification reason} given, <code>competitor</code>'s
* rank is improved by one.
*
* @param competitor
* a competitor contained in the {@link #getCompetitors()} result
* @param race
* a race that is contained in the {@link #getRaceColumns()} result
*/
int getTrackedPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
/**
* A possibly corrected number of points for the race specified. Defaults to the result of calling
* {@link #getTrackedPoints(Competitor, TrackedRace, TimePoint)} but may be corrected by disqualifications or calls
* {@link #getTrackedRank(Competitor, TrackedRace, TimePoint)} but may be corrected by disqualifications or calls
* by the jury for the particular race that differ from the tracking results.
*
* @param competitor
@@ -85,14 +109,16 @@ public interface Leaderboard extends Named {
int getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
/**
* Tells if and why a competitor received maximum points for a race.
* Tells if and why a competitor received "penalty" points for a race (however the scoring rules define the
* points for such a penalty; usually, it would be a high score defined by the number of competitors plus one)
*/
MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn race, TimePoint timePoint);
/**
* A possibly corrected number of points for the race specified. Defaults to the result of calling
* {@link #getNetPoints(Competitor, TrackedRace, TimePoint)} but may be corrected by the regatta
* rules for discarding results.
* {@link #getNetPoints(Competitor, TrackedRace, TimePoint)} but may be corrected by the regatta rules for
* discarding results. If {@link #isDiscarded(Competitor, RaceColumn, TimePoint) discarded}, the points returned
* will be 0.
*
* @param competitor
* a competitor contained in the {@link #getCompetitors()} result
@@ -102,32 +128,39 @@ public interface Leaderboard extends Named {
int getTotalPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
/**
* Tells whether the contribution of <code>raceColumn</code> is discarded in the current leaderboard's
* standings for <code>competitor</code>. A column representing a {@link RaceColumn#isMedalRace() medal race}
* cannot be discarded.
* Tells whether the contribution of <code>raceColumn</code> is discarded in the current leaderboard's standings for
* <code>competitor</code>. A column representing a {@link RaceColumn#isMedalRace() medal race} cannot be discarded.
* Neither can be a race where the competitor received a non-{@link MaxPointsReason#isDiscardable() discardable}
* penalty or disqualification.
*/
boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint);
/**
* 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(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.
*
* @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
*
* @return the race column in the leaderboard used to represent the tracked <code>race</code>
*/
RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace);
/**
* Sums up the {@link #getTotalPoints(Competitor, TrackedRace, TimePoint) total points} of <code>competitor</code>
* across all races tracked by this leaderboard.
*/
int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
/**
* Sorts the competitors according to their ranking in the race column specified. Only competitors who have a score
* are added to the result list. This excludes competitors whose fleet hasn't raced for the <code>raceColumn</code>
* yet, and those where no tracked rank is known and no manual score correction was performed.
* <p>
*
* The sorting order considers this leaderboard's scoring scheme including the semantics of
* {@link Fleet#compareTo(Fleet) ordered fleets} and {@link RaceColumn#isMedalRace() medal races}. The ordering
* does not consider result discarding because when sorting for a race column it is of interest how the competitor
* performed in that race and not how the score affected the overall regatta score. Therefore, it is based on
* {@link #getNetPoints(Competitor, RaceColumn, TimePoint)} and not on
* {@link #getTotalPoints(Competitor, RaceColumn, TimePoint)}.
*/
List<Competitor> getCompetitorsFromBestToWorst(RaceColumn raceColumn, TimePoint timePoint) throws NoWindException;
/**
* Sorts the competitors according to the overall regatta standings, considering the sorting rules for
* {@link Series}, {@link Fleet}s, medal races, discarding rules and score corrections.
*/
List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint);
/**
* Fetches all entries for all competitors of all races tracked by this leaderboard in one sweep. This saves some
@@ -139,23 +172,6 @@ public interface Leaderboard extends Named {
*/
Map<Pair<Competitor, RaceColumn>, Entry> getContent(TimePoint timePoint) throws NoWindException;
/**
* A leaderboard can be renamed. If a leaderboard is managed in a structure that keys leaderboards by name,
* that structure's rules have to be obeyed to ensure the structure's consistency. For example,
* <code>RacingEventService</code> has a <code>renameLeaderboard</code> method that ensures the internal
* structure's consistency and invokes this method.
*/
void setName(String newName);
/**
* Adds a new {@link RaceColumn} that has no {@link TrackedRace} associated yet to this leaderboard.
* @param medalRace
* tells if the column to add represents a medal race which has double score and cannot be discarded
*
* @return the race column in the leaderboard used to represent the tracked <code>race</code>
*/
RaceColumn addRaceColumn(String name, boolean medalRace);
/**
* Retrieves all race columns that were added, either by {@link #addRace(TrackedRace, String, boolean)} or
* {@link #addRaceColumn(String, boolean)}.
@@ -168,18 +184,6 @@ public interface Leaderboard extends Named {
*/
RaceColumn getRaceColumnByName(String name);
/**
* Moves the column with the name <code>name</code> up.
* @param name The name of the column to move.
*/
void moveRaceColumnUp(String name);
/**
* Moves the column with the name <code>name</code> down.
* @param name The name of the column to move.
*/
void moveRaceColumnDown(String name);
/**
* A leaderboard can carry over points from races that are not tracked by this leaderboard in detail,
* so for which no {@link RaceColumn} column is present in this leaderboard. These scores are
@@ -204,8 +208,6 @@ public interface Leaderboard extends Named {
boolean hasCarriedPoints(Competitor competitor);
void removeRaceColumn(String columnName);
SettableScoreCorrection getScoreCorrection();
ThresholdBasedResultDiscardingRule getResultDiscardingRule();
@@ -221,17 +223,20 @@ public interface Leaderboard extends Named {
String getDisplayName(Competitor competitor);
/**
* Tells if the column represented by <code>raceInLeaderboard</code> shall be considered for discarding.
* Medal races are never considered for discarding (not counted as a "started race" nor discarded themselves).
* If a leaderboard has corrections for a column then that column shall be considered for discarding and counts
* for determining the number of races so far. Also, if a tracked race is connected to the column and has
* started already, the column is to be considered for discarding.
* Tells if the column represented by <code>raceColumn</code> shall be considered when counting the number of "races
* so far" for discarding. Although medal races are never discarded themselves, they still count in determining the
* number of "races so far" which is then the basis for deciding how many races may be discarded. If a leaderboard
* has corrections for a column then that column shall be considered for discarding and counts for determining the
* number of races so far. Also, if a tracked race is connected to the column and has started already, the column is
* to be considered for discarding.
*/
boolean considerForDiscarding(RaceColumn raceInLeaderboard, TimePoint timePoint);
void updateIsMedalRace(String raceName, boolean isMedalRace);
boolean considerForDiscarding(RaceColumn raceColumn, TimePoint timePoint);
public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule);
Competitor getCompetitorByIdAsString(String idAsString);
void addRaceColumnListener(RaceColumnListener listener);
void removeRaceColumnListener(RaceColumnListener listener);
}
@@ -1,66 +0,0 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Named;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A column in a {@link Leaderboard} that represents the data of a race. Over the life time of this object it can be
* assigned a {@link TrackedRace} which then acts as a data provider to this column. If no tracked race has been
* assigned, the scores reported by this column will all default to zero.
*
* @author Axel Uhl (D043530)
*
*/
public interface RaceColumn extends Named {
/**
* This does also update the {@link #getRaceIdentifier() race identifier} by setting it to <code>null</code> if <code>race</code>
* is <code>null</code>, and <code>race.getRaceIdentifier()</code> otherwise.
*/
void setTrackedRace(TrackedRace race);
/**
* @return <code>null</code> if this column does not currently have a tracked race associated; otherwise the tracked
* race from where all information relevant to this column can be obtained. See also
* {@link #getRaceIdentifier()}.
*/
TrackedRace getTrackedRace();
/**
* If this column ever was assigned to a tracked race, that race's identifier can be obtained using this method;
* otherwise, <code>null</code> is returned.
*/
RaceIdentifier getRaceIdentifier();
/**
* Records that this leaderboard column is to be associated with the race identified by <code>raceIdentifier</code>.
* This does not automatically load the tracked race, but the information may be used to re-associate a tracked
* race with this column based on its {@link TrackedRace#getRaceIdentifier() race identifier}.
*/
void setRaceIdentifier(RaceIdentifier raceIdentifier);
/**
* A "medal race" cannot be discarded. It's score is doubled during score aggregation.
*/
boolean isMedalRace();
void setIsMedalRace(boolean isMedalRace);
void setName(String newName);
/**
* Constructs a key for maps storing corrections such as score corrections and max points reasons.
*/
Pair<Competitor, RaceColumn> getKey(Competitor competitor);
/**
* Releases the {@link TrackedRace} previously set by {@link #setTrackedRace(TrackedRace)} but leaves the
* {@link #getRaceIdentifier() race identifier} untouched. Therefore, the {@link TrackedRace} may be garbage
* collected but may be re-resolved for this column using the race identifier at a later time.
*/
void releaseTrackedRace();
}
@@ -0,0 +1,7 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Regatta;
public interface RegattaLeaderboard extends Leaderboard {
Regatta getRegatta();
}
@@ -4,6 +4,7 @@ import java.io.Serializable;
import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.TimePoint;
/**
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.leaderboard;
import java.io.Serializable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -31,7 +32,9 @@ public interface ScoreCorrection extends Serializable {
* Note, though, that {@link MaxPointsReason#NONE} can also be the reason for an explicit score correction, e.g., if
* the tracking results were overruled by the jury. Clients may use
* {@link #isScoreCorrected(Competitor, TrackedRace)} to detect the difference.
* @param numberOfCompetitors TODO
*
* @param numberOfCompetitors
* the number of competitors to use as the basis for penalty score calculation ("max points")
*/
Result getCorrectedScore(int uncorrectedScore, Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, int numberOfCompetitors);
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.MaxPointsReason;
public interface SettableScoreCorrection extends ScoreCorrection {
@@ -0,0 +1,465 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.base.RaceColumnListener;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoreCorrection;
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* Abstract leaderboard implementation that already knows about carried points, competitor display name re-keying,
* score corrections and result discarding rules. It manages a set of registered {@link RaceColumnListener}s and
* is itself one. All events received this way are forwarded to all {@link RaceColumnListener}s subscribed. This can
* be used to subscribe a concrete leaderboard implementation to the data structure providing the actual race columns
* in order to be notified whenever the set of {@link TrackedRace}s attached to the leaderboard changes.
*
* @author Axel Uhl (D043530)
*
*/
public abstract class AbstractLeaderboardImpl implements Leaderboard, RaceColumnListener {
private static final long serialVersionUID = -328091952760083438L;
/**
* The factor by which a medal race score is multiplied in the overall point scheme
*/
private static final int MEDAL_RACE_FACTOR = 2;
private final SettableScoreCorrection scoreCorrection;
private ThresholdBasedResultDiscardingRule resultDiscardingRule;
/**
* The optional display name mappings for competitors. This allows a user to override the tracking-provided
* competitor names for display in a leaderboard.
*/
private final Map<Competitor, String> displayNames;
/**
* Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard
* with aggregated results of races not tracked / displayed by this leaderboard in detail. The points
* provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}.
*/
private final Map<Competitor, Integer> carriedPoints;
private final Comparator<Integer> scoreComparator;
private Set<RaceColumnListener> raceColumnListeners;
/**
* A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
*
* @author Axel Uhl (d043530)
*
*/
private class EntryImpl implements Entry {
private final int trackedPoints;
private final int netPoints;
private final boolean isNetPointsCorrected;
private final int totalPoints;
private final MaxPointsReason maxPointsReason;
private final boolean discarded;
private final Fleet fleet;
private EntryImpl(int trackedPoints, int netPoints, boolean isNetPointsCorrected, int totalPoints,
MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
super();
this.trackedPoints = trackedPoints;
this.netPoints = netPoints;
this.isNetPointsCorrected = isNetPointsCorrected;
this.totalPoints = totalPoints;
this.maxPointsReason = maxPointsReason;
this.discarded = discarded;
this.fleet = fleet;
}
@Override
public int getTrackedPoints() {
return trackedPoints;
}
@Override
public int getNetPoints() {
return netPoints;
}
@Override
public boolean isNetPointsCorrected() {
return isNetPointsCorrected;
}
@Override
public int getTotalPoints() {
return totalPoints;
}
@Override
public MaxPointsReason getMaxPointsReason() {
return maxPointsReason;
}
@Override
public boolean isDiscarded() {
return discarded;
}
@Override
public Fleet getFleet() {
return fleet;
}
}
/**
* @param scoreComparator the comparator to use to compare basic scores, such as net points
* @param name must not be <code>null</code>
*/
public AbstractLeaderboardImpl(SettableScoreCorrection scoreCorrection,
ThresholdBasedResultDiscardingRule resultDiscardingRule, Comparator<Integer> scoreComparator) {
this.carriedPoints = new HashMap<Competitor, Integer>();
this.scoreCorrection = scoreCorrection;
this.displayNames = new HashMap<Competitor, String>();
this.resultDiscardingRule = resultDiscardingRule;
this.scoreComparator = scoreComparator;
this.raceColumnListeners = new HashSet<RaceColumnListener>();
}
@Override
public Fleet getFleet(String fleetName) {
for (RaceColumn raceColumn : getRaceColumns()) {
for (Fleet fleet : raceColumn.getFleets()) {
if (fleet.getName().equals(fleetName)) {
return fleet;
}
}
}
return null;
}
@Override
public RaceColumn getRaceColumnByName(String columnName) {
RaceColumn result = null;
for (RaceColumn r : getRaceColumns()) {
if (r.getName().equals(columnName)) {
result = r;
break;
}
}
return result;
}
private Iterable<TrackedRace> getTrackedRaces() {
Set<TrackedRace> trackedRaces = new HashSet<TrackedRace>();
for (RaceColumn r : getRaceColumns()) {
for (Fleet fleet : r.getFleets()) {
TrackedRace trackedRace = r.getTrackedRace(fleet);
if (trackedRace != null) {
trackedRaces.add(trackedRace);
}
}
}
return Collections.unmodifiableSet(trackedRaces);
}
@Override
public Iterable<Competitor> getCompetitors() {
Set<Competitor> result = new HashSet<Competitor>();
for (TrackedRace r : getTrackedRaces()) {
for (Competitor c : r.getRace().getCompetitors()) {
result.add(c);
}
}
return result;
}
@Override
public Competitor getCompetitorByName(String competitorName) {
for (Competitor competitor : getCompetitors()) {
if (competitor.getName().equals(competitorName)) {
return competitor;
}
}
return null;
}
@Override
public Competitor getCompetitorByIdAsString(String idAsString) {
for (Competitor competitor : getCompetitors()) {
if (competitor.getId().toString().equals(idAsString)) {
return competitor;
}
}
return null;
}
@Override
public SettableScoreCorrection getScoreCorrection() {
return scoreCorrection;
}
@Override
public ThresholdBasedResultDiscardingRule getResultDiscardingRule() {
return resultDiscardingRule;
}
@Override
public int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
final TrackedRace trackedRace = race.getTrackedRace(competitor);
return trackedRace == null ? 0
: trackedRace.hasStarted(timePoint) ? improveByDisqualificationsOfBetterRankedCompetitors(race, trackedRace, timePoint, trackedRace
.getRank(competitor, timePoint)) : 0;
}
/**
* Per competitor disqualified ({@link ScoreCorrection} has a {@link MaxPointsReason} for the competitor), all
* competitors ranked worse by the tracking system need to have their rank corrected by one.
* @param trackedRace the race to which the rank refers; look for disqualifications / max points reasons in this column
* @param timePoint
* time point at which to consider disqualifications (not used yet because currently we don't remember
* <em>when</em> a competitor was disqualified)
* @param rank a competitors rank according to the tracking system
*
* @return the unmodified <code>rank</code> if no disqualifications for better-ranked competitors exist for <code>race</code>,
* or otherwise a rank improved (lowered) by the number of disqualifications of competitors whose tracked rank is better (lower)
* than <code>rank</code>.
*/
private int improveByDisqualificationsOfBetterRankedCompetitors(RaceColumn raceColumn, TrackedRace trackedRace, TimePoint timePoint, int rank) {
int correctedRank = rank;
List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(timePoint);
int betterCompetitorRank=1;
Iterator<Competitor> ci = competitorsFromBestToWorst.iterator();
while (betterCompetitorRank < rank && ci.hasNext()) {
Competitor betterTrackedCompetitor = ci.next();
MaxPointsReason maxPointsReasonForBetterCompetitor = getScoreCorrection().getMaxPointsReason(betterTrackedCompetitor, raceColumn);
if (maxPointsReasonForBetterCompetitor != null && maxPointsReasonForBetterCompetitor != MaxPointsReason.NONE) {
correctedRank--;
}
betterCompetitorRank++;
}
return correctedRank;
}
@Override
public int getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
return getScoreCorrection().getCorrectedScore(getTrackedRank(competitor, raceColumn, timePoint), competitor,
raceColumn, timePoint, Util.size(getCompetitors())).getCorrectedScore();
}
@Override
public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return raceColumn.getTrackedRace(competitor) == null ? MaxPointsReason.NONE : getScoreCorrection()
.getMaxPointsReason(competitor, raceColumn);
}
@Override
public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return !raceColumn.isMedalRace() && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
&& getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, timePoint).contains(
raceColumn);
}
@Override
public int getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
return isDiscarded(competitor, raceColumn, timePoint) ?
0 :
(raceColumn.isMedalRace() ? MEDAL_RACE_FACTOR : 1) * getNetPoints(competitor, raceColumn, timePoint);
}
@Override
public int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException {
int result = getCarriedPoints(competitor);
for (RaceColumn r : getRaceColumns()) {
result += getTotalPoints(competitor, r, timePoint);
}
return result;
}
@Override
public Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
int trackedPoints = getTrackedRank(competitor, race, timePoint);
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedPoints, competitor, race,
timePoint, Util.size(getCompetitors()));
boolean discarded = isDiscarded(competitor, race, timePoint);
return new EntryImpl(trackedPoints, correctedResults.getCorrectedScore(), correctedResults.isCorrected(),
discarded ? 0
: correctedResults.getCorrectedScore() * (race.isMedalRace() ? MEDAL_RACE_FACTOR : 1),
correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
}
@Override
public Map<Pair<Competitor, RaceColumn>, Entry> getContent(TimePoint timePoint) throws NoWindException {
Map<Pair<Competitor, RaceColumn>, Entry> result = new HashMap<Pair<Competitor, RaceColumn>, Entry>();
Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>();
for (RaceColumn raceColumn : getRaceColumns()) {
for (Competitor competitor : getCompetitors()) {
int trackedPoints;
final TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
if (trackedRace != null && trackedRace.hasStarted(timePoint)) {
trackedPoints = trackedRace.getRank(competitor, timePoint);
} else {
trackedPoints = 0;
}
Result correctedResults = getScoreCorrection().getCorrectedScore(trackedPoints, competitor, raceColumn,
timePoint, Util.size(getCompetitors()));
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
if (discardedRacesForCompetitor == null) {
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, timePoint);
discardedRaces.put(competitor, discardedRacesForCompetitor);
}
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
Entry entry = new EntryImpl(trackedPoints, correctedResults.getCorrectedScore(),
correctedResults.isCorrected(), discarded ? 0 : correctedResults.getCorrectedScore()
* (raceColumn.isMedalRace() ? 2 : 1), correctedResults.getMaxPointsReason(), discarded,
raceColumn.getFleetOfCompetitor(competitor));
result.put(new Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
}
}
return result;
}
@Override
public void setCarriedPoints(Competitor competitor, int carriedPoints) {
this.carriedPoints.put(competitor, carriedPoints);
}
@Override
public int getCarriedPoints(Competitor competitor) {
Integer result = carriedPoints.get(competitor);
return result == null ? 0 : result;
}
@Override
public void unsetCarriedPoints(Competitor competitor) {
carriedPoints.remove(competitor);
}
@Override
public boolean hasCarriedPoints() {
return !carriedPoints.isEmpty();
}
@Override
public boolean hasCarriedPoints(Competitor competitor) {
return carriedPoints.containsKey(competitor);
}
@Override
public boolean considerForDiscarding(RaceColumn raceColumn, TimePoint timePoint) {
boolean result = getScoreCorrection().hasCorrectionFor(raceColumn);
if (!result && !raceColumn.isMedalRace()) {
for (Fleet fleet : raceColumn.getFleets()) {
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
if (trackedRace != null && trackedRace.hasStarted(timePoint)) {
result = true;
break;
}
}
}
return result;
}
@Override
public String getDisplayName(Competitor competitor) {
return displayNames.get(competitor);
}
@Override
public void setDisplayName(Competitor competitor, String displayName) {
displayNames.put(competitor, displayName);
}
@Override
public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
this.resultDiscardingRule = discardingRule;
}
/**
* All competitors with non-zero net points are added to the result which is then sorted by net points in ascending
* order. The fleet is the primary ordering criterion, followed by the net points.
*/
@Override
public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
final Map<Competitor, Pair<Integer, Fleet>> netPointsAndFleet = new HashMap<Competitor, Pair<Integer, Fleet>>();
for (Competitor competitor : getCompetitors()) {
int netPoints = getNetPoints(competitor, raceColumn, timePoint);
if (netPoints != 0) {
netPointsAndFleet.put(competitor, new Pair<Integer, Fleet>(netPoints, raceColumn.getFleetOfCompetitor(competitor)));
}
}
List<Competitor> result = new ArrayList<Competitor>(netPointsAndFleet.keySet());
Collections.sort(result, new Comparator<Competitor>() {
@Override
public int compare(Competitor o1, Competitor o2) {
int comparisonResult;
if (o1 == o2) {
comparisonResult = 0;
} else {
comparisonResult = netPointsAndFleet.get(o1).getB().compareTo(netPointsAndFleet.get(o2).getB());
if (comparisonResult == 0) {
comparisonResult = scoreComparator.compare(netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA());
}
}
return comparisonResult;
}
});
return result;
}
@Override
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
List<Competitor> result = new ArrayList<Competitor>();
for (Competitor competitor : getCompetitors()) {
result.add(competitor);
}
Collections.sort(result, getTotalRankComparator(timePoint));
return result;
}
protected Comparator<? super Competitor> getTotalRankComparator(TimePoint timePoint) {
return new LeaderboardTotalRankComparator(this, timePoint, scoreComparator);
}
@Override
public void addRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.add(listener);
}
@Override
public void removeRaceColumnListener(RaceColumnListener listener) {
raceColumnListeners.remove(listener);
}
@Override
public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
}
@Override
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
private void notifyListenersAboutTrackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceLinked(raceColumn, fleet, trackedRace);
}
}
private void notifyListenersAboutTrackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
for (RaceColumnListener listener : raceColumnListeners) {
listener.trackedRaceUnlinked(raceColumn, fleet, trackedRace);
}
}
}
@@ -0,0 +1,186 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;
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.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding
* the columns belonging to the same regatta or even boat class.<p>
*
* The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to
* all {@link RaceColumnListener}s subscribed with this leaderboard.
*
* @author Axel Uhl (D043530)
*
*/
public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
protected static final Fleet defaultFleet = new FleetImpl("Default");
private static final long serialVersionUID = -5708971849158747846L;
private final List<FlexibleRaceColumn> races;
private String name;
public FlexibleLeaderboardImpl(String name, SettableScoreCorrection scoreCorrection,
ThresholdBasedResultDiscardingRule resultDiscardingRule, Comparator<Integer> scoreComparator) {
super(scoreCorrection, resultDiscardingRule, scoreComparator);
if (name == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = name;
this.races = new ArrayList<FlexibleRaceColumn>();
}
@Override
public String getName() {
return name;
}
/**
* @param newName must not be <code>null</code>
*/
public void setName(String newName) {
if (newName == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = newName;
}
@Override
public RaceColumn addRaceColumn(String name, boolean medalRace, Fleet... fleets) {
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.addRaceColumnListener(this);
races.add(column);
}
return column;
}
@Override
public FlexibleRaceColumn getRaceColumnByName(String columnName) {
return (FlexibleRaceColumn) super.getRaceColumnByName(columnName);
}
@Override
public Fleet getFleet(String fleetName) {
Fleet result;
if (fleetName == null) {
result = defaultFleet;
} else {
result = super.getFleet(fleetName);
}
return result;
}
@Override
public void removeRaceColumn(String columnName) {
final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName);
races.remove(raceColumn);
raceColumn.removeRaceColumnListener(this);
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
return Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
}
@Override
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace, Fleet fleet) {
FlexibleRaceColumn column = getRaceColumnByName(columnName);
if (column == null) {
column = createRaceColumn(columnName, medalRace);
column.addRaceColumnListener(this);
races.add(column);
}
column.setTrackedRace(fleet, race); // triggers listeners because this object was registered above as race column listener on the column
return column;
}
protected RaceColumnImpl createRaceColumn(String columnName, boolean medalRace, Fleet... fleets) {
return new RaceColumnImpl(columnName, medalRace, turnNullOrEmptyFleetsIntoDefaultFleet(fleets));
}
protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
Iterable<Fleet> theFleets;
if (fleets == null || fleets.length == 0) {
theFleets = Collections.singleton(defaultFleet);
} else {
theFleets = Arrays.asList(fleets);
}
return theFleets;
}
@Override
public void moveRaceColumnUp(String name) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(name)) {
race = r;
}
}
if (race == null) {
return;
}
int index = 0;
index = races.lastIndexOf(race);
index--;
if (index >= 0) {
races.remove(race);
races.add(index, race);
}
}
@Override
public void moveRaceColumnDown(String name) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(name)) {
race = r;
}
}
if (race == null) {
return;
}
int index = 0;
index = races.lastIndexOf(race);
if (index == -1) {
return;
}
index++;
if (index < races.size()) {
races.remove(race);
races.add(index, race);
}
}
@Override
public void updateIsMedalRace(String raceName, boolean isMedalRace) {
FlexibleRaceColumn race = null;
for (FlexibleRaceColumn r : races) {
if (r.getName().equals(raceName))
race = r;
}
if (race != null) {
race.setIsMedalRace(isMedalRace);
}
}
}
@@ -1,402 +0,0 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.Named;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.tracking.TrackedRace;
public class LeaderboardImpl implements Named, Leaderboard {
private static final long serialVersionUID = -328091952760083438L;
private final List<RaceColumn> races;
private final SettableScoreCorrection scoreCorrection;
private ThresholdBasedResultDiscardingRule resultDiscardingRule;
private String name;
/**
* The optional display name mappings for competitors. This allows a user to override the tracking-provided
* competitor names for display in a leaderboard.
*/
private final Map<Competitor, String> displayNames;
/**
* Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard
* with aggregated results of races not tracked / displayed by this leaderboard in detail. The points
* provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}.
*/
private final Map<Competitor, Integer> carriedPoints;
/**
* A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
*
* @author Axel Uhl (d043530)
*
*/
private class EntryImpl implements Entry {
private final int trackedPoints;
private final int netPoints;
private final boolean isNetPointsCorrected;
private final int totalPoints;
private final MaxPointsReason maxPointsReason;
private final boolean discarded;
private EntryImpl(int trackedPoints, int netPoints, boolean isNetPointsCorrected, int totalPoints, MaxPointsReason maxPointsReason, boolean discarded) {
super();
this.trackedPoints = trackedPoints;
this.netPoints = netPoints;
this.isNetPointsCorrected = isNetPointsCorrected;
this.totalPoints = totalPoints;
this.maxPointsReason = maxPointsReason;
this.discarded = discarded;
}
@Override
public int getTrackedPoints() {
return trackedPoints;
}
@Override
public int getNetPoints() {
return netPoints;
}
@Override
public boolean isNetPointsCorrected() {
return isNetPointsCorrected;
}
@Override
public int getTotalPoints() {
return totalPoints;
}
@Override
public MaxPointsReason getMaxPointsReason() {
return maxPointsReason;
}
@Override
public boolean isDiscarded() {
return discarded;
}
}
/**
* @param name must not be <code>null</code>
*/
public LeaderboardImpl(String name, SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
if (name == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = name;
this.carriedPoints = new HashMap<Competitor, Integer>();
this.races = new ArrayList<RaceColumn>();
this.scoreCorrection = scoreCorrection;
this.displayNames = new HashMap<Competitor, String>();
this.resultDiscardingRule = resultDiscardingRule;
}
@Override
public RaceColumn addRaceColumn(String name, boolean medalRace) {
RaceColumnImpl column = createRaceColumn(name, medalRace);
races.add(column);
return column;
}
@Override
public void removeRaceColumn(String columnName) {
races.remove(getRaceColumnByName(columnName));
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
return Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
}
@Override
public RaceColumn getRaceColumnByName(String columnName) {
RaceColumn result = null;
for (RaceColumn r : races) {
if (r.getName().equals(columnName)) {
result = r;
break;
}
}
return result;
}
@Override
public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
RaceColumn column = getRaceColumnByName(columnName);
if (column == null) {
column = createRaceColumn(columnName, medalRace);
races.add(column);
}
column.setTrackedRace(race);
return column;
}
protected RaceColumnImpl createRaceColumn(String columnName, boolean medalRace) {
return new RaceColumnImpl(columnName, medalRace);
}
private Iterable<TrackedRace> getTrackedRaces() {
Set<TrackedRace> trackedRaces = new HashSet<TrackedRace>();
for (RaceColumn r : races) {
TrackedRace trackedRace = r.getTrackedRace();
if (trackedRace != null) {
trackedRaces.add(trackedRace);
}
}
return Collections.unmodifiableSet(trackedRaces);
}
@Override
public Iterable<Competitor> getCompetitors() {
Set<Competitor> result = new HashSet<Competitor>();
for (TrackedRace r : getTrackedRaces()) {
for (Competitor c : r.getRace().getCompetitors()) {
result.add(c);
}
}
return result;
}
@Override
public Competitor getCompetitorByName(String competitorName) {
for (Competitor competitor : getCompetitors()) {
if (competitor.getName().equals(competitorName)) {
return competitor;
}
}
return null;
}
@Override
public Competitor getCompetitorByIdAsString(String idAsString) {
for (Competitor competitor : getCompetitors()) {
if (competitor.getId().toString().equals(idAsString)) {
return competitor;
}
}
return null;
}
@Override
public SettableScoreCorrection getScoreCorrection() {
return scoreCorrection;
}
@Override
public ThresholdBasedResultDiscardingRule getResultDiscardingRule() {
return resultDiscardingRule;
}
@Override
public int getTrackedPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
return race.getTrackedRace() == null ? 0 : race.getTrackedRace().hasStarted(timePoint) ? race.getTrackedRace()
.getRank(competitor, timePoint) : 0;
}
@Override
public int getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
return getScoreCorrection().getCorrectedScore(getTrackedPoints(competitor, raceColumn, timePoint), competitor,
raceColumn, timePoint, Util.size(getCompetitors())).getCorrectedScore();
}
@Override
public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint)
throws NoWindException {
return raceColumn.getTrackedRace() == null ? MaxPointsReason.NONE : getScoreCorrection().getCorrectedScore(
getTrackedPoints(competitor, raceColumn, timePoint), competitor, raceColumn, timePoint, Util.size(getCompetitors()))
.getMaxPointsReason();
}
@Override
public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return !raceColumn.isMedalRace()
&& getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, timePoint).contains(
raceColumn);
}
@Override
public int getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
return isDiscarded(competitor, raceColumn, timePoint) ?
0 :
(raceColumn.isMedalRace() ? 2 : 1) * getNetPoints(competitor, raceColumn, timePoint);
}
@Override
public int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException {
int result = getCarriedPoints(competitor);
for (RaceColumn r : getRaceColumns()) {
result += getTotalPoints(competitor, r, timePoint);
}
return result;
}
@Override
public Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
int trackedPoints = getTrackedPoints(competitor, race, timePoint);
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedPoints, competitor, race,
timePoint, Util.size(getCompetitors()));
boolean discarded = isDiscarded(competitor, race, timePoint);
return new EntryImpl(trackedPoints, correctedResults.getCorrectedScore(), correctedResults.isCorrected(),
discarded ? 0
: correctedResults.getCorrectedScore() * (race.isMedalRace() ? 2 : 1), correctedResults.getMaxPointsReason(), discarded);
}
@Override
public Map<Pair<Competitor, RaceColumn>, Entry> getContent(TimePoint timePoint) throws NoWindException {
Map<Pair<Competitor, RaceColumn>, Entry> result = new HashMap<Pair<Competitor, RaceColumn>, Entry>();
Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>();
for (RaceColumn raceColumn : getRaceColumns()) {
for (Competitor competitor : getCompetitors()) {
int trackedPoints;
// TODO when a column has more than one race, keyed by fleet, find the one for competitor
if (raceColumn.getTrackedRace() != null && raceColumn.getTrackedRace().hasStarted(timePoint)) {
trackedPoints = raceColumn.getTrackedRace().getRank(competitor, timePoint);
} else {
trackedPoints = 0;
}
Result correctedResults = getScoreCorrection().getCorrectedScore(trackedPoints, competitor, raceColumn,
timePoint, Util.size(getCompetitors()));
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
if (discardedRacesForCompetitor == null) {
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, timePoint);
discardedRaces.put(competitor, discardedRacesForCompetitor);
}
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
Entry entry = new EntryImpl(trackedPoints, correctedResults.getCorrectedScore(),
correctedResults.isCorrected(), discarded ? 0 : correctedResults.getCorrectedScore()
* (raceColumn.isMedalRace() ? 2 : 1), correctedResults.getMaxPointsReason(), discarded);
result.put(new Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
}
}
return result;
}
@Override
public String getName() {
return name;
}
/**
* @param newName must not be <code>null</code>
*/
public void setName(String newName) {
if (newName == null) {
throw new IllegalArgumentException("A leaderboard's name must not be null");
}
this.name = newName;
}
@Override
public void setCarriedPoints(Competitor competitor, int carriedPoints) {
this.carriedPoints.put(competitor, carriedPoints);
}
@Override
public int getCarriedPoints(Competitor competitor) {
Integer result = carriedPoints.get(competitor);
return result == null ? 0 : result;
}
@Override
public void unsetCarriedPoints(Competitor competitor) {
carriedPoints.remove(competitor);
}
@Override
public boolean hasCarriedPoints() {
return !carriedPoints.isEmpty();
}
@Override
public boolean hasCarriedPoints(Competitor competitor) {
return carriedPoints.containsKey(competitor);
}
@Override
public boolean considerForDiscarding(RaceColumn raceInLeaderboard, TimePoint timePoint) {
return !raceInLeaderboard.isMedalRace()
&& (raceInLeaderboard.getTrackedRace() != null && raceInLeaderboard.getTrackedRace().hasStarted(
timePoint)) || getScoreCorrection().hasCorrectionFor(raceInLeaderboard);
}
@Override
public String getDisplayName(Competitor competitor) {
return displayNames.get(competitor);
}
@Override
public void setDisplayName(Competitor competitor, String displayName) {
displayNames.put(competitor, displayName);
}
@Override
public void moveRaceColumnUp(String name) {
RaceColumn race = null;
for (RaceColumn r : races){
if (r.getName().equals(name))
race = r;
}
if (race == null)
return;
int index = 0;
index = races.lastIndexOf(race);
index--;
if (index >= 0){
races.remove(race);
races.add(index, race);
}
}
@Override
public void moveRaceColumnDown(String name) {
RaceColumn race = null;
for (RaceColumn r : races){
if (r.getName().equals(name))
race = r;
}
if (race == null)
return;
int index = 0;
index = races.lastIndexOf(race);
if (index == -1)
return;
index++;
if (index < races.size()){
races.remove(race);
races.add(index, race);
}
}
@Override
public void updateIsMedalRace(String raceName, boolean isMedalRace) {
RaceColumn race = null;
for (RaceColumn r : races){
if (r.getName().equals(raceName))
race = r;
}
if (race == null)
return;
race.setIsMedalRace(isMedalRace);
}
@Override
public void setResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
this.resultDiscardingRule = discardingRule;
}
}
@@ -0,0 +1,244 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
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.NoWindError;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.leaderboard.Leaderboard;
/**
* Compares two competitors that occur in a {@link Leaderboard#getCompetitors()} set in the context of the
* {@link Leaderboard} according to their total rank at a given point in time. "Better" is represented as "lesser."
* <p>
*
* For a general {@link Leaderboard} we don't know about Series. In a {@link Leaderboard}, all we have are the columns
* with their fleets which can be compared but may compare equal; also, we know if a column is a medal race column.
* Participants of a medal race always score better than all remaining competitors. (We only know the medal race
* participants if the column has a tracked race.) If both competitors scored in a medal race, this score will be
* compared, regardless of the sum of any other scores.
* <p>
*
* If that hasn't decided the order yet, then as soon as we find a column with more than one tracked race with the fleets
* comparing non-equal, this decides the order if the two competitors belong to different fleets.
* <p>
*
* If that still hasn't decided the order, the scores will decide. If the competitors scored in a different number of
* races, the competitor scoring in more races is considered better (lesser). If they scored in an equal number of races,
* the score sums are compared using {@link #compareByScoreSum(int, int)}. If that still doesn't decide the order, the scores
* are sorted from best to worst and compared one by one. The first differing score decides. If all scores are equal pairwise,
* both competitors are ranked equal.<p>
*
* For a RegattaLeaderboard, if a column in a series has tracked races for all of its fleets, these competitors rank
* better than all remaining competitors that appear in prior series. This is probably the generalization of the
* "medal race" rule where the medal "series" has one race, and if it's tracked, its participants rank better than all
* others in prior series who did not reach the medal race.
*
* @author Axel Uhl (D043530)
*/
public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
private final Leaderboard leaderboard;
private final TimePoint timePoint;
private final Comparator<Integer> scoreComparator;
public LeaderboardTotalRankComparator(Leaderboard leaderboard, TimePoint timePoint, Comparator<Integer> scoreComparator) {
super();
this.leaderboard = leaderboard;
this.timePoint = timePoint;
this.scoreComparator = scoreComparator;
}
protected Leaderboard getLeaderboard() {
return leaderboard;
}
protected TimePoint getTimePoint() {
return timePoint;
}
@Override
public int compare(Competitor o1, Competitor o2) {
try {
List<Integer> o1Scores = new ArrayList<Integer>();
List<Integer> o2Scores = new ArrayList<Integer>();
int o1ScoreSum = 0;
int o2ScoreSum = 0;
int o1MedalRaceScore = 0;
int o2MedalRaceScore = 0;
for (RaceColumn raceColumn : getLeaderboard().getRaceColumns()) {
int preemptiveColumnResult = 0;
final int o1Score = getLeaderboard().getTotalPoints(o1, raceColumn, timePoint);
if (o1Score != 0) {
o1Scores.add(o1Score);
o1ScoreSum += o1Score;
}
final int o2Score = getLeaderboard().getTotalPoints(o2, raceColumn, timePoint);
if (o2Score != 0) {
o2Scores.add(o2Score);
o2ScoreSum += o2Score;
}
if (raceColumn.isMedalRace()) {
o1MedalRaceScore = o1Score;
o2MedalRaceScore = o2Score;
// similar to compareByFleet, however, tracking is not required; having medal race column points (tracked or manual) is sufficient
preemptiveColumnResult = compareByMedalRaceParticipation(o1Score, o2Score);
}
if (preemptiveColumnResult == 0) {
preemptiveColumnResult = compareByFleet(raceColumn, o1, o2);
}
if (preemptiveColumnResult != 0) {
return preemptiveColumnResult;
}
}
// now count the races in which they scored; if they scored in a different number of races, prefer the competitor
// who scored more often; otherwise, prefer the competitor who has a better score sum; if score sums are equal, break
// tie by sorting scores and looking for the first score difference.
int result = compareByNumberOfRacesScored(o1Scores.size(), o2Scores.size());
if (result == 0) {
result = compareByScoreSum(o1ScoreSum, o2ScoreSum);
if (result == 0) {
result = compareByMedalRaceScore(o1MedalRaceScore, o2MedalRaceScore);
if (result == 0) {
result = compareByBetterScore(o1Scores, o2Scores);
}
}
}
return result;
} catch (NoWindException e) {
throw new NoWindError(e);
}
}
/**
* Precondition: either both scored in medal race or both didn't. If both scored, the better score wins.
* This is to be applied only if the total score of both competitors are equal to each other.
*/
private int compareByMedalRaceScore(int o1MedalRaceScore, int o2MedalRaceScore) {
assert o1MedalRaceScore != 0 || o2MedalRaceScore == 0;
if (o1MedalRaceScore != 0) {
return getScoreComparator().compare(o1MedalRaceScore, o2MedalRaceScore);
} else {
return 0;
}
}
private int compareByFleet(RaceColumn raceColumn, Competitor o1, Competitor o2) {
Fleet o1f = raceColumn.getFleetOfCompetitor(o1);
Fleet o2f = raceColumn.getFleetOfCompetitor(o2);
// if the fleet for both was identified because both were tracked in this column, then if the fleets
// don't compare equal, return the fleet comparison as result immediately. Example: o1 competed in Gold fleet,
// o2 in Silver fleet; Gold compares better to Silver, so o1 is compared better to o2.
int result = 0;
if (o1f != null) {
if (o2f != null) {
if (o1f.compareTo(o2f) != 0) {
result = o1f.compareTo(o2f);
}
} else {
// check if o1's fleet is best or worst in column; in that case, o1's membership in this fleet and the fact
// that o2 is not part of that fleet determines the result
result = extremeFleetComparison(raceColumn, o1f);
}
} else if (o2f != null) {
// check if o1's fleet is best or worst in column; in that case, o1's membership in this fleet and the fact
// that o2 is not part of that fleet determines the result
result = -extremeFleetComparison(raceColumn, o2f);
}
return result;
}
/**
* If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if <code>fleet</code> is the
* best fleet with others in the column being worse, return "better" (lesser; -1). If <code>fleet</code> is the worst fleet
* with others in the column being better, return "worse" (greater; 1). Otherwise, return 0.
*/
private int extremeFleetComparison(RaceColumn raceColumn, Fleet fleet) {
boolean allOthersAreGreater = true;
boolean allOthersAreLess = true;
boolean othersExist = false;
for (Fleet f : raceColumn.getFleets()) {
if (f != fleet) {
othersExist = true;
allOthersAreGreater = allOthersAreGreater && f.compareTo(fleet) > 0;
allOthersAreLess = allOthersAreLess && f.compareTo(fleet) < 0;
}
}
int result = 0;
if (othersExist) {
assert !(allOthersAreGreater && allOthersAreLess);
if (allOthersAreGreater) {
result = -1;
} else if (allOthersAreLess) {
result = 1;
}
}
return result;
}
private int compareByMedalRaceParticipation(int o1Score, int o2Score) {
// if only one scored in medal race, this decides the order and returns immediately
if (o1Score == 0) {
if (o2Score != 0) {
return 1;
}
} else {
if (o2Score == 0) {
return -1;
} else {
return 0;
}
}
return 0;
}
/**
* Assuming both competitors scored in the same number of races, compares the sorted scores.
* @param o1Scores
* @param o2Scores
* @return
*/
protected int compareByBetterScore(List<Integer> o1Scores, List<Integer> o2Scores) {
assert o1Scores.size() == o2Scores.size();
Comparator<Integer> scoreComparator = getScoreComparator();
Collections.sort(o1Scores, scoreComparator);
Collections.sort(o2Scores, scoreComparator);
// now both lists are sorted from best to worst score
Iterator<Integer> o1Iter = o1Scores.iterator();
Iterator<Integer> o2Iter = o2Scores.iterator();
int result = 0;
while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) {
result = scoreComparator.compare(o1Iter.next(), o2Iter.next());
}
return result;
}
/**
* Returns a comparator for comparing individual scores. This implementation returns a comparator for the usual ISAF
* scheme where lesser scores compare "better" which again means "lesser." Therefore, the comparator retunred compares
* the integer numbers by their natural ordering.
*/
protected Comparator<Integer> getScoreComparator() {
return scoreComparator;
}
/**
* This implementation ranks a competitor better (lesser) if it has the lower score sum
*/
protected int compareByScoreSum(int o1ScoreSum, int o2ScoreSum) {
return getScoreComparator().compare(o1ScoreSum, o2ScoreSum);
}
/**
* Compares a competitor better (lesser) if it has the greater number of races scored
*/
protected int compareByNumberOfRacesScored(int o1NumberOfRacesScored, int o2NumberOfRacesScored) {
return o2NumberOfRacesScored - o1NumberOfRacesScored;
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.io.Serializable;
import java.util.Comparator;
/**
* The score comparator as used by the ISAF standard scoring scheme. Lower scores are better.
*
* @author Axel Uhl (D043530)
*
*/
public class LowerScoreIsBetter implements Comparator<Integer>, Serializable {
private static final long serialVersionUID = -2767385186133743330L;
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
}
@@ -1,21 +1,32 @@
package com.sap.sailing.domain.leaderboard.impl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.leaderboard.RaceColumn;
import com.sap.sailing.domain.tracking.TrackedRace;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RaceColumnImpl implements RaceColumn {
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.impl.AbstractRaceColumn;
import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
public class RaceColumnImpl extends AbstractRaceColumn implements FlexibleRaceColumn {
private static final long serialVersionUID = -7801617988982540470L;
private TrackedRace trackedRace;
private boolean medalRace;
private String name;
private RaceIdentifier raceIdentifier;
public RaceColumnImpl(String name, boolean medalRace) {
this.name = 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) {
super(name);
this.medalRace = medalRace;
List<Fleet> myFleets = new ArrayList<Fleet>();
for (Fleet fleet : fleets) {
myFleets.add(fleet);
}
Collections.sort(myFleets);
this.fleets = myFleets;
}
@Override
@@ -23,49 +34,14 @@ public class RaceColumnImpl implements RaceColumn {
return medalRace;
}
@Override
public TrackedRace getTrackedRace() {
return trackedRace;
}
@Override
public void setTrackedRace(TrackedRace trackedRace) {
this.trackedRace = trackedRace;
this.setRaceIdentifier(trackedRace == null ? null : trackedRace.getRaceIdentifier());
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String newName) {
this.name = newName;
}
@Override
public Pair<Competitor, RaceColumn> getKey(Competitor competitor) {
return new Pair<Competitor, RaceColumn>(competitor, this);
}
@Override
public void setIsMedalRace(boolean isMedalRace) {
this.medalRace = isMedalRace;
}
@Override
public RaceIdentifier getRaceIdentifier() {
return raceIdentifier;
public Iterable<Fleet> getFleets() {
return fleets;
}
@Override
public void setRaceIdentifier(RaceIdentifier raceIdentifier) {
this.raceIdentifier = raceIdentifier;
}
@Override
public void releaseTrackedRace() {
trackedRace = null;
}
}
@@ -0,0 +1,64 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
/**
* A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta
* leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for
* {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with
* this leaderboard.
*
* @author Axel Uhl (D043530)
*
*/
public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard {
private static final long serialVersionUID = 2370461218294770084L;
private final Regatta regatta;
public RegattaLeaderboardImpl(Regatta regatta, SettableScoreCorrection scoreCorrection,
ThresholdBasedResultDiscardingRule resultDiscardingRule, Comparator<Integer> scoreComparator) {
super(scoreCorrection, resultDiscardingRule, scoreComparator);
this.regatta = regatta;
regatta.addRaceColumnListener(this);
}
@Override
public Regatta getRegatta() {
return regatta;
}
@Override
public String getName() {
return getRegatta().getName();
}
@Override
public Iterable<RaceColumn> getRaceColumns() {
List<RaceColumn> result = new ArrayList<RaceColumn>();
for (Series series : getRegatta().getSeries()) {
for (RaceColumn raceColumn : series.getRaceColumns()) {
result.add(raceColumn);
}
}
return result;
}
@Override
public RaceColumnInSeries getRaceColumnByName(String columnName) {
return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName);
}
}
Loaded 100 of 424 files, more files were not shown because too many files have changed in this diff. Show more