mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-08 05:11:04 +00:00
Merge remote-tracking branch 'origin/master' into xcelsiusadapter_development
This commit is contained in:
commit
b0df915c81
424 files changed
+34680
-17407
No files matched your search
@@ -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)
|
||||
|
||||
+10
-3
@@ -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;
|
||||
|
||||
+19
-5
@@ -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"
|
||||
+6
-3
@@ -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();
|
||||
}
|
||||
+20
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class RegattaNameAndRaceName extends RegattaName implements RegattaAndRac
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return raceName;
|
||||
return getRegattaName()+"/"+getRaceName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Executable
+31
@@ -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);
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public interface ScoreCorrections {
|
||||
|
||||
}
|
||||
+82
@@ -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;
|
||||
|
||||
+9
-4
@@ -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 {
|
||||
|
||||
|
||||
+3
-5
@@ -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"
|
||||
+22
-6
@@ -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);
|
||||
}
|
||||
+21
-5
@@ -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);
|
||||
|
||||
}
|
||||
+46
-1
@@ -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;
|
||||
}
|
||||
+47
-58
@@ -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) {
|
||||
}
|
||||
}
|
||||
Regular → Executable
+352
-34
@@ -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;
|
||||
}
|
||||
}
|
||||
+26
-1
@@ -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
|
||||
}
|
||||
+157
-19
@@ -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,
|
||||
|
||||
+11
-9
@@ -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",
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
+4
-2
@@ -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);
|
||||
}
|
||||
+9
-1
@@ -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);
|
||||
|
||||
}
|
||||
+22
-13
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+13
-2
@@ -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
|
||||
|
||||
+21
-5
@@ -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() {
|
||||
|
||||
+16
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+21
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -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) {
|
||||
|
||||
+7
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+11
-7
@@ -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));
|
||||
|
||||
+64
-37
@@ -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;
|
||||
|
||||
+325
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
-20
@@ -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
|
||||
|
||||
+5
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+420
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -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());
|
||||
|
||||
+5
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+52
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
+27
-9
@@ -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);
|
||||
|
||||
}
|
||||
+1
-1
@@ -99,7 +99,7 @@ public class RaceRecord {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public String getEventName() {
|
||||
return regattaName;
|
||||
}
|
||||
|
||||
+7
-6
@@ -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;
|
||||
|
||||
+59
-42
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+8
-6
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+18
-2
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+139
-11
@@ -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();
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
public interface RaceColumnInSeries extends RaceColumn {
|
||||
Series getSeries();
|
||||
|
||||
Regatta getRegatta();
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
+150
@@ -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();
|
||||
}
|
||||
}
|
||||
+12
@@ -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) {
|
||||
|
||||
+11
@@ -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) {
|
||||
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+80
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -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();
|
||||
}
|
||||
Executable
+76
@@ -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);
|
||||
|
||||
}
|
||||
Executable
+7
@@ -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);
|
||||
}
|
||||
+70
-65
@@ -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();
|
||||
|
||||
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
|
||||
public interface RegattaLeaderboard extends Leaderboard {
|
||||
Regatta getRegatta();
|
||||
}
|
||||
+1
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
+4
-1
@@ -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
@@ -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 {
|
||||
|
||||
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java
Executable
+465
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+186
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-402
@@ -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;
|
||||
}
|
||||
}
|
||||
+244
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+20
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+25
-49
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+64
@@ -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
Reference in new issue
Block a user