From 2d686a7528911708c00994a7bd085fb556ac6a5f Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 29 Mar 2016 17:15:48 +0200 Subject: [PATCH 01/57] Hide qualifying races if final or medal races in progress --- .../common/racelog/RaceLogRaceStatus.java | 5 +++ .../app/ui/adapters/racelist/RaceFilter.java | 45 +++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java index a617030d43e..f0f9ddb9b55 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java @@ -21,6 +21,11 @@ public enum RaceLogRaceStatus { } + public static boolean isRunningOrFinished(RaceLogRaceStatus status) { + return status != null && + (status.equals(STARTPHASE) || status.equals(RUNNING) || status.equals(FINISHING) || status.equals(FINISHED)); + } + public int getOrderNumber() { return orderNumber; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index dea47f84b2e..0c8f499cc11 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -2,7 +2,9 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import android.widget.Filter; @@ -48,15 +50,20 @@ public class RaceFilter extends Filter { return createResults(items); } - List filteredItems = new ArrayList(); + List filteredItems = new ArrayList<>(); RaceListDataTypeRace currentUnscheduledItem = null; RaceListDataTypeRace currentFinishedItem = null; + Map finalsStarted = new HashMap<>(); int finishedItems = 0; int subItems = 0; List headersToRemove= new ArrayList<>(); RaceListDataTypeHeader currentHeader = null; - - List allItems = new ArrayList(items); + + List allItems = new ArrayList<>(items); + detectStartedFinalsOrMedals(finalsStarted, allItems); + + filterByFinalOrMedal(finalsStarted, headersToRemove, allItems); + for (RaceListDataType item : allItems) { if (item instanceof RaceListDataTypeHeader) { if (currentHeader != null && subItems > 0 && subItems == finishedItems) { @@ -101,6 +108,38 @@ public class RaceFilter extends Filter { return createResults(filteredItems); } + private void filterByFinalOrMedal(Map finalsStarted, List headersToRemove, + List allItems) { + RaceListDataTypeHeader currentHeader = null; + for (RaceListDataType item : allItems) { + if (item instanceof RaceListDataTypeHeader) { + currentHeader = (RaceListDataTypeHeader) item; + } else if (item instanceof RaceListDataTypeRace) { + RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; + if (raceItem.getRace().getFleet().getOrdering() == 0 && !raceItem.getRace().getSeries().isMedal()) { + String regattaName = raceItem.getRace().getRaceGroup().getName(); + if(finalsStarted.containsKey(regattaName) && currentHeader != null) { + headersToRemove.add(currentHeader); + } + } + } + } + } + + private void detectStartedFinalsOrMedals(Map finalsStarted, List allItems) { + for (RaceListDataType item : allItems) { + if (item instanceof RaceListDataTypeRace) { + RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; + String regattaName = raceItem.getRace().getRaceGroup().getName(); + if (raceItem.getRace().getFleet().getOrdering() != 0 || raceItem.getRace().getSeries().isMedal()) { + if (RaceLogRaceStatus.isRunningOrFinished(raceItem.getCurrentStatus())) { + finalsStarted.put(regattaName, true); + } + } + } + } + } + private void removeFinishedSeries(List filteredItems, List headersToRemove) { List itemsToKeep = new ArrayList<>(); boolean keepItems = false; From dd419ab5dd13ad70be08131456d72f73848d539f Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Thu, 31 Mar 2016 11:56:10 +0200 Subject: [PATCH 02/57] Preserve first unscheduled races that belong to the first race column of the leaderboard and only show subsequent unscheduled races --- .../app/ui/adapters/racelist/RaceFilter.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 0c8f499cc11..0fca4e739c1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -9,6 +9,7 @@ import java.util.Map; import android.widget.Filter; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment.FilterMode; /** @@ -53,6 +54,8 @@ public class RaceFilter extends Filter { List filteredItems = new ArrayList<>(); RaceListDataTypeRace currentUnscheduledItem = null; RaceListDataTypeRace currentFinishedItem = null; + String currentUnscheduledRaceName = ""; + RaceGroupSeriesFleet currentlyRunningFleet = null; Map finalsStarted = new HashMap<>(); int finishedItems = 0; int subItems = 0; @@ -76,16 +79,30 @@ public class RaceFilter extends Filter { currentHeader = (RaceListDataTypeHeader) item; currentUnscheduledItem = null; currentFinishedItem = null; + currentlyRunningFleet = null; finishedItems = 0; subItems = 0; } else if (item instanceof RaceListDataTypeRace) { subItems ++; RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; + boolean qualifying = raceItem.getRace().getFleet().getOrdering() == 0 || !raceItem.getRace().getSeries().isMedal(); RaceLogRaceStatus status = raceItem.getCurrentStatus(); - if (currentUnscheduledItem == null && status.equals(RaceLogRaceStatus.UNSCHEDULED)) { - filteredItems.add(raceItem); - currentUnscheduledItem = raceItem; - } else if (status.equals(RaceLogRaceStatus.FINISHED)) { + // Preserve first unscheduled races that belong to the first race column of the leaderboard + if (qualifying && RaceLogRaceStatus.UNSCHEDULED.equals(status)) { + if (currentUnscheduledRaceName.equals("")) { + currentUnscheduledRaceName = raceItem.getRaceName(); + } + if (currentUnscheduledRaceName.equals(raceItem.getRaceName())) { + filteredItems.add(raceItem); + currentUnscheduledItem = raceItem; + } + } + if (currentUnscheduledItem == null && RaceLogRaceStatus.UNSCHEDULED.equals(status)) { + if (currentlyRunningFleet == null || currentlyRunningFleet.equals(raceItem.getFleet())) { + filteredItems.add(raceItem); + currentUnscheduledItem = raceItem; + } + } else if (RaceLogRaceStatus.FINISHED.equals(status)) { finishedItems ++; if (filteredItems.contains(currentFinishedItem)) { filteredItems.remove(currentFinishedItem); @@ -95,6 +112,7 @@ public class RaceFilter extends Filter { currentUnscheduledItem = null; } else if (RaceLogRaceStatus.isActive(status)) { filteredItems.add(raceItem); + currentlyRunningFleet = raceItem.getFleet(); // new run for all types! currentUnscheduledItem = null; currentFinishedItem = null; From 28a40447d1f847589557997b32e4f6e9a224e1e5 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Thu, 31 Mar 2016 12:38:09 +0200 Subject: [PATCH 03/57] Fixes issue with subsequent unscheduled race in current races --- .../app/ui/adapters/racelist/RaceFilter.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 0fca4e739c1..337c922f2d8 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -14,7 +14,7 @@ import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment.FilterMod /** * Filters races by status. - * + *

* For each "run" of unscheduled races only the first will be taken. * For each "run" of finished races only the last will be taken. */ @@ -50,7 +50,7 @@ public class RaceFilter extends Filter { if (filterMode == null || filterMode.equals(FilterMode.ALL)) { return createResults(items); } - + List filteredItems = new ArrayList<>(); RaceListDataTypeRace currentUnscheduledItem = null; RaceListDataTypeRace currentFinishedItem = null; @@ -59,7 +59,7 @@ public class RaceFilter extends Filter { Map finalsStarted = new HashMap<>(); int finishedItems = 0; int subItems = 0; - List headersToRemove= new ArrayList<>(); + List headersToRemove = new ArrayList<>(); RaceListDataTypeHeader currentHeader = null; List allItems = new ArrayList<>(items); @@ -74,7 +74,7 @@ public class RaceFilter extends Filter { } RaceListDataTypeHeader headerItem = (RaceListDataTypeHeader) item; filteredItems.add(headerItem); - + // new run for all types! currentHeader = (RaceListDataTypeHeader) item; currentUnscheduledItem = null; @@ -83,7 +83,7 @@ public class RaceFilter extends Filter { finishedItems = 0; subItems = 0; } else if (item instanceof RaceListDataTypeRace) { - subItems ++; + subItems++; RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; boolean qualifying = raceItem.getRace().getFleet().getOrdering() == 0 || !raceItem.getRace().getSeries().isMedal(); RaceLogRaceStatus status = raceItem.getCurrentStatus(); @@ -94,16 +94,20 @@ public class RaceFilter extends Filter { } if (currentUnscheduledRaceName.equals(raceItem.getRaceName())) { filteredItems.add(raceItem); - currentUnscheduledItem = raceItem; + if (currentlyRunningFleet == null) { + currentUnscheduledItem = raceItem; + } } } if (currentUnscheduledItem == null && RaceLogRaceStatus.UNSCHEDULED.equals(status)) { if (currentlyRunningFleet == null || currentlyRunningFleet.equals(raceItem.getFleet())) { - filteredItems.add(raceItem); currentUnscheduledItem = raceItem; + if (!filteredItems.contains(raceItem)) { + filteredItems.add(raceItem); + } } } else if (RaceLogRaceStatus.FINISHED.equals(status)) { - finishedItems ++; + finishedItems++; if (filteredItems.contains(currentFinishedItem)) { filteredItems.remove(currentFinishedItem); } @@ -132,11 +136,11 @@ public class RaceFilter extends Filter { for (RaceListDataType item : allItems) { if (item instanceof RaceListDataTypeHeader) { currentHeader = (RaceListDataTypeHeader) item; - } else if (item instanceof RaceListDataTypeRace) { + } else if (item instanceof RaceListDataTypeRace) { RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; if (raceItem.getRace().getFleet().getOrdering() == 0 && !raceItem.getRace().getSeries().isMedal()) { String regattaName = raceItem.getRace().getRaceGroup().getName(); - if(finalsStarted.containsKey(regattaName) && currentHeader != null) { + if (finalsStarted.containsKey(regattaName) && currentHeader != null) { headersToRemove.add(currentHeader); } } From 1c5488711141fe7d31d439b02cdac3f72ebdc58e Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Thu, 31 Mar 2016 17:03:25 +0200 Subject: [PATCH 04/57] Mark race header (including races) of subsequent series to be removed if previous series was not in last race --- .../app/ui/adapters/racelist/RaceFilter.java | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 337c922f2d8..8668009b644 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -55,12 +55,17 @@ public class RaceFilter extends Filter { RaceListDataTypeRace currentUnscheduledItem = null; RaceListDataTypeRace currentFinishedItem = null; String currentUnscheduledRaceName = ""; + String currentRegattaName = ""; RaceGroupSeriesFleet currentlyRunningFleet = null; + boolean showCurrentFleet = true; Map finalsStarted = new HashMap<>(); int finishedItems = 0; int subItems = 0; + List headersToRemove = new ArrayList<>(); - RaceListDataTypeHeader currentHeader = null; + RaceListDataTypeHeader previousHeader = null; + Map fleetItemCount = new HashMap<>(); + Map lastRunningRaceInFleet = new HashMap<>(); List allItems = new ArrayList<>(items); detectStartedFinalsOrMedals(finalsStarted, allItems); @@ -69,14 +74,33 @@ public class RaceFilter extends Filter { for (RaceListDataType item : allItems) { if (item instanceof RaceListDataTypeHeader) { - if (currentHeader != null && subItems > 0 && subItems == finishedItems) { - headersToRemove.add(currentHeader); + if (previousHeader != null && subItems > 0 && subItems == finishedItems && !showCurrentFleet) { + headersToRemove.add(previousHeader); + } + showCurrentFleet = false; + // Mark race header (including races) of subsequent series to be removed if previous series was not in last race + if (fleetItemCount.isEmpty() && lastRunningRaceInFleet.isEmpty()) { + showCurrentFleet = true; + } else { + for (String key : fleetItemCount.keySet()) { + if (fleetItemCount.get(key).equals(lastRunningRaceInFleet.get(key))) { + showCurrentFleet = true; + break; + } + } } RaceListDataTypeHeader headerItem = (RaceListDataTypeHeader) item; filteredItems.add(headerItem); - // new run for all types! - currentHeader = (RaceListDataTypeHeader) item; + previousHeader = headerItem; + if (!showCurrentFleet) { + if (currentRegattaName.equals("") || currentRegattaName.equals(headerItem.getRaceGroup().getName())) { + headersToRemove.add(headerItem); + } + } + fleetItemCount.clear(); + lastRunningRaceInFleet.clear(); + currentRegattaName = previousHeader.getRaceGroup().getName(); currentUnscheduledItem = null; currentFinishedItem = null; currentlyRunningFleet = null; @@ -85,6 +109,8 @@ public class RaceFilter extends Filter { } else if (item instanceof RaceListDataTypeRace) { subItems++; RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; + String fleetName = getFleetName(raceItem); + countFleetItems(fleetItemCount, fleetName); boolean qualifying = raceItem.getRace().getFleet().getOrdering() == 0 || !raceItem.getRace().getSeries().isMedal(); RaceLogRaceStatus status = raceItem.getCurrentStatus(); // Preserve first unscheduled races that belong to the first race column of the leaderboard @@ -115,6 +141,8 @@ public class RaceFilter extends Filter { currentFinishedItem = raceItem; currentUnscheduledItem = null; } else if (RaceLogRaceStatus.isActive(status)) { + int fleetCount = fleetItemCount.get(getFleetName(raceItem)); + lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); filteredItems.add(raceItem); currentlyRunningFleet = raceItem.getFleet(); // new run for all types! @@ -122,17 +150,29 @@ public class RaceFilter extends Filter { currentFinishedItem = null; } } - } - if (currentHeader != null && subItems > 0 && subItems == finishedItems) { - headersToRemove.add(currentHeader); + } if (previousHeader != null && subItems > 0 && subItems == finishedItems) { + headersToRemove.add(previousHeader); } removeFinishedSeries(filteredItems, headersToRemove); return createResults(filteredItems); } + private String getFleetName(RaceListDataTypeRace raceItem) { + return raceItem.getRace().getIdentifier().getFleet().getName(); + } + + private void countFleetItems(Map fleetItemCount, String fleetName) { + if (!fleetItemCount.containsKey(fleetName)) { + fleetItemCount.put(fleetName, 0); + } + int count = fleetItemCount.get(fleetName); + fleetItemCount.put(fleetName, count++); + } + private void filterByFinalOrMedal(Map finalsStarted, List headersToRemove, List allItems) { RaceListDataTypeHeader currentHeader = null; + List racesToRemove = new ArrayList<>(); for (RaceListDataType item : allItems) { if (item instanceof RaceListDataTypeHeader) { currentHeader = (RaceListDataTypeHeader) item; @@ -142,10 +182,12 @@ public class RaceFilter extends Filter { String regattaName = raceItem.getRace().getRaceGroup().getName(); if (finalsStarted.containsKey(regattaName) && currentHeader != null) { headersToRemove.add(currentHeader); + racesToRemove.add(raceItem); } } } } + allItems.removeAll(racesToRemove); } private void detectStartedFinalsOrMedals(Map finalsStarted, List allItems) { From a869141cfe4c2cadf71c2dab3bd926e69b238455 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 10:27:37 +0200 Subject: [PATCH 05/57] Remove finished races from current filter --- .../app/ui/adapters/racelist/RaceFilter.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 8668009b644..a554326f957 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -53,7 +53,6 @@ public class RaceFilter extends Filter { List filteredItems = new ArrayList<>(); RaceListDataTypeRace currentUnscheduledItem = null; - RaceListDataTypeRace currentFinishedItem = null; String currentUnscheduledRaceName = ""; String currentRegattaName = ""; RaceGroupSeriesFleet currentlyRunningFleet = null; @@ -100,9 +99,9 @@ public class RaceFilter extends Filter { } fleetItemCount.clear(); lastRunningRaceInFleet.clear(); + currentUnscheduledRaceName = ""; currentRegattaName = previousHeader.getRaceGroup().getName(); currentUnscheduledItem = null; - currentFinishedItem = null; currentlyRunningFleet = null; finishedItems = 0; subItems = 0; @@ -134,20 +133,16 @@ public class RaceFilter extends Filter { } } else if (RaceLogRaceStatus.FINISHED.equals(status)) { finishedItems++; - if (filteredItems.contains(currentFinishedItem)) { - filteredItems.remove(currentFinishedItem); + if (filteredItems.contains(raceItem)) { + filteredItems.remove(raceItem); } - filteredItems.add(raceItem); - currentFinishedItem = raceItem; - currentUnscheduledItem = null; + int fleetCount = fleetItemCount.get(getFleetName(raceItem)); + lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); } else if (RaceLogRaceStatus.isActive(status)) { int fleetCount = fleetItemCount.get(getFleetName(raceItem)); lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); filteredItems.add(raceItem); currentlyRunningFleet = raceItem.getFleet(); - // new run for all types! - currentUnscheduledItem = null; - currentFinishedItem = null; } } } if (previousHeader != null && subItems > 0 && subItems == finishedItems) { From 3cd3e6ef2e62cc541c0075d10109822c91042eef Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 11:07:04 +0200 Subject: [PATCH 06/57] Fixes invalid header shown --- .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index a554326f957..b47d5f31550 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -160,8 +160,8 @@ public class RaceFilter extends Filter { if (!fleetItemCount.containsKey(fleetName)) { fleetItemCount.put(fleetName, 0); } - int count = fleetItemCount.get(fleetName); - fleetItemCount.put(fleetName, count++); + int count = fleetItemCount.get(fleetName) + 1; + fleetItemCount.put(fleetName, count); } private void filterByFinalOrMedal(Map finalsStarted, List headersToRemove, From 64146d72c4d3adb4229494482ecdc9dec691c920 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 11:15:32 +0200 Subject: [PATCH 07/57] Show correct current race --- .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index b47d5f31550..81bc75f6267 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -138,10 +138,13 @@ public class RaceFilter extends Filter { } int fleetCount = fleetItemCount.get(getFleetName(raceItem)); lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); + currentlyRunningFleet = raceItem.getFleet(); + currentUnscheduledItem = null; } else if (RaceLogRaceStatus.isActive(status)) { int fleetCount = fleetItemCount.get(getFleetName(raceItem)); lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); filteredItems.add(raceItem); + currentUnscheduledItem = null; currentlyRunningFleet = raceItem.getFleet(); } } From 4e2762bb7d27ae617735fba85e78c2a9acc52ac7 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 11:31:06 +0200 Subject: [PATCH 08/57] Hide subsequent unscheduled race of a running race --- .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 81bc75f6267..ad734320c66 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -144,7 +144,6 @@ public class RaceFilter extends Filter { int fleetCount = fleetItemCount.get(getFleetName(raceItem)); lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); filteredItems.add(raceItem); - currentUnscheduledItem = null; currentlyRunningFleet = raceItem.getFleet(); } } From 4e0511140daa9ad8d19f8bc95d599e207a8f30cf Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 12:10:02 +0200 Subject: [PATCH 09/57] Show subsequent unscheduled race again --- .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index ad734320c66..81bc75f6267 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -144,6 +144,7 @@ public class RaceFilter extends Filter { int fleetCount = fleetItemCount.get(getFleetName(raceItem)); lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); filteredItems.add(raceItem); + currentUnscheduledItem = null; currentlyRunningFleet = raceItem.getFleet(); } } From 09773924f96a7b73bf3d23a1e81b20b0331a4578 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 12:46:06 +0200 Subject: [PATCH 10/57] Sort races for finals too --- .../app/ui/fragments/RaceListFragment.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index a143adaad97..9b78d7f762c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -451,9 +451,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis mViewItems.add(new RaceListDataTypeHeader(fleets.get(0), false)); // ... and add all the sorted race views! List raceListDataTypeRaces = getRaceListDataTypeRaces(mRacesByGroup, fleets); - if (fleetsEqual(fleets)) { - raceListDataTypeRaces = sortRaceList(raceListDataTypeRaces, fleets.size()); - } + raceListDataTypeRaces = sortRaceList(raceListDataTypeRaces, fleets.size()); for (RaceListDataTypeRace raceListDataTypeRace : raceListDataTypeRaces) { mViewItems.add(raceListDataTypeRace); } @@ -461,20 +459,6 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis } } - private boolean fleetsEqual(List fleets) { - if (!fleets.isEmpty()) { - int lastFleetOrder = fleets.get(0).getFleet().getOrdering(); - for (RaceGroupSeriesFleet fleet : fleets) { - int currentFleetOrder = fleet.getFleet().getOrdering(); - if (currentFleetOrder != lastFleetOrder) { - return false; - } - } - } - - return true; - } - private List sortRaceList(List raceList, int fleetSize) { int racePerFleet = raceList.size() / fleetSize; List sortedList = new ArrayList<>(); From 43a87c31ad4676ac4ad602bbf2dbf43800eadd10 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 16:28:07 +0200 Subject: [PATCH 11/57] Remove header if series finished --- .../app/ui/adapters/racelist/RaceFilter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 81bc75f6267..45744b949c9 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -56,10 +56,10 @@ public class RaceFilter extends Filter { String currentUnscheduledRaceName = ""; String currentRegattaName = ""; RaceGroupSeriesFleet currentlyRunningFleet = null; - boolean showCurrentFleet = true; + boolean showCurrentFleet; Map finalsStarted = new HashMap<>(); int finishedItems = 0; - int subItems = 0; + int subItems = -1; List headersToRemove = new ArrayList<>(); RaceListDataTypeHeader previousHeader = null; @@ -73,6 +73,7 @@ public class RaceFilter extends Filter { for (RaceListDataType item : allItems) { if (item instanceof RaceListDataTypeHeader) { + showCurrentFleet = subItems < 0 || (subItems > 0 && subItems != finishedItems); if (previousHeader != null && subItems > 0 && subItems == finishedItems && !showCurrentFleet) { headersToRemove.add(previousHeader); } @@ -148,7 +149,8 @@ public class RaceFilter extends Filter { currentlyRunningFleet = raceItem.getFleet(); } } - } if (previousHeader != null && subItems > 0 && subItems == finishedItems) { + } + if (previousHeader != null && subItems > 0 && subItems == finishedItems) { headersToRemove.add(previousHeader); } removeFinishedSeries(filteredItems, headersToRemove); From 4935c7d258e03ba3de6a1da72969303da29858e5 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 17:43:45 +0200 Subject: [PATCH 12/57] Prevent scheduled races and races in startphase from showing subsequent races --- .../sap/sailing/domain/common/racelog/RaceLogRaceStatus.java | 5 +++++ .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java index f0f9ddb9b55..9b7a97cb8d2 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogRaceStatus.java @@ -14,6 +14,11 @@ public enum RaceLogRaceStatus { RaceLogRaceStatus(int orderNumber) { this.orderNumber = orderNumber; } + + public static boolean isPreRunning(RaceLogRaceStatus status) { + return status != null && + (status.equals(SCHEDULED) || status.equals(STARTPHASE)); + } public static boolean isActive(RaceLogRaceStatus status) { return status != null && diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 45744b949c9..3e0f87e21fc 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -141,7 +141,10 @@ public class RaceFilter extends Filter { lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); currentlyRunningFleet = raceItem.getFleet(); currentUnscheduledItem = null; - } else if (RaceLogRaceStatus.isActive(status)) { + } else if(RaceLogRaceStatus.isPreRunning(status)) { + filteredItems.add(raceItem); + } + else if (RaceLogRaceStatus.isActive(status)) { int fleetCount = fleetItemCount.get(getFleetName(raceItem)); lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); filteredItems.add(raceItem); From 0ea08a9cdc9e2d29daa68788b52f341c07233841 Mon Sep 17 00:00:00 2001 From: Bennet Brunsen Date: Tue, 5 Apr 2016 17:59:15 +0200 Subject: [PATCH 13/57] Show subsequent race of scheduled race --- .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 3e0f87e21fc..aba6586136e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -143,6 +143,8 @@ public class RaceFilter extends Filter { currentUnscheduledItem = null; } else if(RaceLogRaceStatus.isPreRunning(status)) { filteredItems.add(raceItem); + currentUnscheduledItem = null; + currentlyRunningFleet = raceItem.getFleet(); } else if (RaceLogRaceStatus.isActive(status)) { int fleetCount = fleetItemCount.get(getFleetName(raceItem)); From fe6cddc13bb8c43e08bd9e2e1e47f84ac542d0d5 Mon Sep 17 00:00:00 2001 From: Charlotte Proeller Date: Wed, 6 Apr 2016 14:28:07 +0200 Subject: [PATCH 14/57] removed error from merging, will be able to build again --- .../sap/sailing/server/gateway/jaxrs/api/EventsResource.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index a4c6517dd43..9fba7e3571c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -36,11 +36,7 @@ public class EventsResource extends AbstractSailingServerResource { @GET @Produces("application/json;charset=UTF-8") public Response getEvents(@QueryParam("showNonPublic") String showNonPublic) { -<<<<<<< HEAD // TODO bug2589, bug3504: the following will require EVENT:READ permission; it requires cross-server links to be authentication aware... -======= - // TODO bug2589, bug3504: the following will require EVENT:READ permission; it requires cross-server links to be authentication aware... ->>>>>>> master // SecurityUtils.getSubject().checkPermission(Permission.EVENT.getStringPermission(Permission.Mode.READ)); JsonSerializer eventSerializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); JSONArray result = new JSONArray(); From a7044670cf9a7868acd1ae0cb2c8393bc19af0d3 Mon Sep 17 00:00:00 2001 From: Charlotte Proeller Date: Wed, 6 Apr 2016 14:35:32 +0200 Subject: [PATCH 15/57] corrected indent --- .../sap/sailing/server/gateway/jaxrs/api/EventsResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index 9fba7e3571c..49a8cd60e58 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -36,7 +36,7 @@ public class EventsResource extends AbstractSailingServerResource { @GET @Produces("application/json;charset=UTF-8") public Response getEvents(@QueryParam("showNonPublic") String showNonPublic) { - // TODO bug2589, bug3504: the following will require EVENT:READ permission; it requires cross-server links to be authentication aware... + // TODO bug2589, bug3504: the following will require EVENT:READ permission; it requires cross-server links to be authentication aware... // SecurityUtils.getSubject().checkPermission(Permission.EVENT.getStringPermission(Permission.Mode.READ)); JsonSerializer eventSerializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); JSONArray result = new JSONArray(); From 40ce1150096eef3de3eaa08f9a0c88891c1d7155 Mon Sep 17 00:00:00 2001 From: Charlotte Proeller Date: Mon, 11 Apr 2016 11:40:14 +0200 Subject: [PATCH 16/57] bug: 3011: started changing the Filtering of race-rows --- .../impl/CurrentRaceFilterImplTest.java | 122 +++++++++++++ .../base/racegroup/CurrentRaceFilter.java | 10 + .../base/racegroup/IsFleetFragment.java | 8 + .../domain/base/racegroup/IsRaceFragment.java | 7 + .../base/racegroup/RaceGroupFragment.java | 5 + .../racegroup/impl/CurrentRaceFilterImpl.java | 171 ++++++++++++++++++ .../racelist/RaceListDataTypeHeader.java | 3 +- 7 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupFragment.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java new file mode 100644 index 00000000000..0a968c644b5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -0,0 +1,122 @@ +package com.sap.sailing.domain.base.racegroup.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; +import com.sap.sailing.domain.base.racegroup.IsRaceFragment; +import com.sap.sailing.domain.base.racegroup.IsFleetFragment; +import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; + +public class CurrentRaceFilterImplTest { + + private CurrentRaceFilter fixture; + + @Before + public void setUp(){ + fixture = new CurrentRaceFilterImpl<>(); + } + + @Test + public void easyRaceGroupTest() throws Exception{ + SeriesBase seriesBase = Mockito.mock(SeriesBase.class); + List values = new ArrayList<>(); + SeriesFragment fristSerie = new SeriesFragment(seriesBase); + values.add(fristSerie); + RaceFragment firstRace = new RaceFragment(RaceLogRaceStatus.UNSCHEDULED); + values.add(firstRace); + + List result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(2)); + Assert.assertThat(result.get(0), Matchers.equalTo(fristSerie)); + Assert.assertThat(result.get(1), Matchers.equalTo(firstRace)); + } + + @Test + public void raceGroupWithTwoSeries() throws Exception{ + SeriesBase seriesBase1 = Mockito.mock(SeriesBase.class); + SeriesBase seriesBase2 = Mockito.mock(SeriesBase.class); + List values = new ArrayList<>(); + values.addAll(createRacesForSeries(3, seriesBase1)); + values.addAll(createRacesForSeries(3, seriesBase2)); + + List result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(2)); + Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); + Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); + + ((RaceFragment)values.get(5)).setCurrentStatus(RaceLogRaceStatus.PRESCHEDULED); + + result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(5)); + Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); + Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); + Assert.assertThat(result.get(2), Matchers.equalTo(values.get(4))); + Assert.assertThat(result.get(3), Matchers.equalTo(values.get(5))); + Assert.assertThat(result.get(4), Matchers.equalTo(values.get(6))); + + ((RaceFragment)values.get(5)).setCurrentStatus(RaceLogRaceStatus.RUNNING); + + result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(3)); + Assert.assertThat(result.get(0), Matchers.equalTo(values.get(4))); + Assert.assertThat(result.get(1), Matchers.equalTo(values.get(5))); + Assert.assertThat(result.get(2), Matchers.equalTo(values.get(6))); + } + + + private Collection createRacesForSeries(int max, SeriesBase seriesBase) { + List results = new ArrayList<>(); + results.add(new SeriesFragment(seriesBase)); + for(int i = 0; i { + + + List filterCurrentRaces(Collection allItems); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java new file mode 100644 index 00000000000..4937571b142 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java @@ -0,0 +1,8 @@ +package com.sap.sailing.domain.base.racegroup; + +import com.sap.sailing.domain.base.SeriesBase; + +public interface IsFleetFragment extends RaceGroupFragment { + + SeriesBase getSeries(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java new file mode 100644 index 00000000000..1e8d16eaf01 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.base.racegroup; + +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; + +public interface IsRaceFragment { + RaceLogRaceStatus getCurrentStatus(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupFragment.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupFragment.java new file mode 100644 index 00000000000..6e1a58c52d1 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupFragment.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.base.racegroup; + +public interface RaceGroupFragment { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java new file mode 100644 index 00000000000..599a9965709 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -0,0 +1,171 @@ +package com.sap.sailing.domain.base.racegroup.impl; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; +import com.sap.sailing.domain.base.racegroup.IsFleetFragment; +import com.sap.sailing.domain.base.racegroup.IsRaceFragment; +import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +public class CurrentRaceFilterImpl implements CurrentRaceFilter { + + @Override + public List filterCurrentRaces(Collection allItems) { + + Map> filteredRace = filterRaces(allItems); + + filteredRace = removeAllListsBevorLastStarted(filteredRace); + + return convertBackToList(filteredRace); + } + + + + @SuppressWarnings("unchecked") + private List convertBackToList(Map> filteredRaces) { + Map> filteredRacesWithoutEmtyList = removeEmtyLists(filteredRaces); + List result = new LinkedList<>(); + for (SERIES currentKey : filteredRacesWithoutEmtyList.keySet()) { + result.add((ITEM) currentKey); + result.addAll((Collection) filteredRacesWithoutEmtyList.get(currentKey)); + } + return result; + } + + + + private Map> removeEmtyLists( + Map> filteredRaces) { + Map> result = new LinkedHashMap<>(); + for (SERIES currentKey : filteredRaces.keySet()) { + if(!filteredRaces.get(currentKey).isEmpty()){ + result.put(currentKey, filteredRaces.get(currentKey)); + } + } + return result; + } + + + + private Map> removeAllListsBevorLastStarted(Map> filteredRaces) { + Map> result; + SERIES keyOfLastStartedRace = null; + for (SERIES currentKey : filteredRaces.keySet()) { + if(containsActiceRace(filteredRaces.get(currentKey))){ + keyOfLastStartedRace = currentKey; + } + } + if(keyOfLastStartedRace != null){ + result = new LinkedHashMap<>(); + boolean found = false; + for (SERIES currentKey : filteredRaces.keySet()) { + if(found){ + result.put(currentKey, filteredRaces.get(currentKey)); + } + else if(currentKey.equals(keyOfLastStartedRace)){ + found=true; + result.put(currentKey, filteredRaces.get(currentKey)); + } + } + }else{ + result = filteredRaces; + } + return result; + } + + + + private boolean containsActiceRace(List races) { + for (RACE race : races) { + if(RaceLogRaceStatus.isRunningOrFinished(race.getCurrentStatus())){ + return true; + } + } + return false; + } + + + + /** + * method maps the races to fleets/series and removes all that should be not shown with "A follows B" relationship + * + * @param allItems All SORTED Items + * @return all current Races mapped to there fleet/series + */ + private Map> filterRaces(Collection allItems) { + Map> mappedItems = mapItems(allItems); + List newValueForKey = null; + SeriesBase firstSeries = mappedItems.keySet().iterator().next().getSeries(); + for(SERIES currentKey : mappedItems.keySet()){ + newValueForKey = filterWithFollowing(mappedItems.get(currentKey), currentKey.getSeries().equals(firstSeries)); + mappedItems.put(currentKey, newValueForKey); + } + + return mappedItems; + } + + /** + * Filters a List (with races of the same series and fleet). + * + * @param list + * @return + */ + private List filterWithFollowing(List races, boolean isFirstSeries) { + List result = new LinkedList<>(); + boolean addNext = isFirstSeries; + for(RACE currentRace: races){ + if(hasShowingStatus(currentRace)){ + result.add(currentRace); + addNext = true; + }else if(addNext){ + result.add(currentRace); + addNext = false; + } + } + return result; + } + + /** + * Checks if the status is A Status to Show + * + * @param currentRace + * @return + */ + private boolean hasShowingStatus(RACE currentRace) { + return RaceLogRaceStatus.isActive(currentRace.getCurrentStatus()) || RaceLogRaceStatus.isActive(currentRace.getCurrentStatus()); + } + + /** + * maps the Races to there Fleet/series + * @param allItems + * @return + */ + @SuppressWarnings("unchecked") + private Map> mapItems(Collection allItems) { + Map> resultMap = new LinkedHashMap<>(); + SERIES currentKey = null; + List currentValue = null; + for(ITEM currentItem: allItems){ + if(currentItem instanceof IsFleetFragment){ + if(currentKey!=null && currentValue != null){ + resultMap.put(currentKey, currentValue); + } + currentKey = (SERIES) currentItem; + currentValue = new LinkedList<>(); + }else{ + currentValue.add((RACE) currentItem); + } + } + if(currentKey!=null && currentValue != null){ + resultMap.put(currentKey, currentValue); + } + + return resultMap; + } + +} diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java index bb29a5636d0..a884c3c61f1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java @@ -2,10 +2,11 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.racegroup.IsFleetFragment; import com.sap.sailing.domain.base.racegroup.RaceGroup; import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; -public class RaceListDataTypeHeader implements RaceListDataType { +public class RaceListDataTypeHeader implements RaceListDataType, IsFleetFragment { private RaceGroupSeriesFleet data; private boolean isFleetVisible; From 5e93b49bc1bc13d5bab875e0af2a10f5263dc662 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 11 Apr 2016 15:10:07 +0200 Subject: [PATCH 17/57] made compileable by letting IsRaceFragment implement RaceGroupFragment Change-Id: Icbb9947d9864a0ebb41091871a4f2bff9521111d --- .../impl/CurrentRaceFilterImplTest.java | 98 +++++++++---------- .../base/racegroup/CurrentRaceFilter.java | 16 ++- .../domain/base/racegroup/IsRaceFragment.java | 2 +- 3 files changed, 60 insertions(+), 56 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index 0a968c644b5..08738521288 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -18,105 +18,97 @@ import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; public class CurrentRaceFilterImplTest { - + private CurrentRaceFilter fixture; - + @Before - public void setUp(){ + public void setUp() { fixture = new CurrentRaceFilterImpl<>(); } @Test - public void easyRaceGroupTest() throws Exception{ + public void easyRaceGroupTest() throws Exception { SeriesBase seriesBase = Mockito.mock(SeriesBase.class); - List values = new ArrayList<>(); + List values = new ArrayList<>(); SeriesFragment fristSerie = new SeriesFragment(seriesBase); values.add(fristSerie); RaceFragment firstRace = new RaceFragment(RaceLogRaceStatus.UNSCHEDULED); values.add(firstRace); - - List result = fixture.filterCurrentRaces(values); + + List result = fixture.filterCurrentRaces(values); Assert.assertThat(result.size(), Matchers.equalTo(2)); Assert.assertThat(result.get(0), Matchers.equalTo(fristSerie)); Assert.assertThat(result.get(1), Matchers.equalTo(firstRace)); } - + @Test - public void raceGroupWithTwoSeries() throws Exception{ - SeriesBase seriesBase1 = Mockito.mock(SeriesBase.class); - SeriesBase seriesBase2 = Mockito.mock(SeriesBase.class); - List values = new ArrayList<>(); - values.addAll(createRacesForSeries(3, seriesBase1)); - values.addAll(createRacesForSeries(3, seriesBase2)); - - List result = fixture.filterCurrentRaces(values); - Assert.assertThat(result.size(), Matchers.equalTo(2)); - Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); - Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); - - ((RaceFragment)values.get(5)).setCurrentStatus(RaceLogRaceStatus.PRESCHEDULED); - - result = fixture.filterCurrentRaces(values); - Assert.assertThat(result.size(), Matchers.equalTo(5)); - Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); - Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); - Assert.assertThat(result.get(2), Matchers.equalTo(values.get(4))); - Assert.assertThat(result.get(3), Matchers.equalTo(values.get(5))); - Assert.assertThat(result.get(4), Matchers.equalTo(values.get(6))); - - ((RaceFragment)values.get(5)).setCurrentStatus(RaceLogRaceStatus.RUNNING); - - result = fixture.filterCurrentRaces(values); - Assert.assertThat(result.size(), Matchers.equalTo(3)); - Assert.assertThat(result.get(0), Matchers.equalTo(values.get(4))); - Assert.assertThat(result.get(1), Matchers.equalTo(values.get(5))); - Assert.assertThat(result.get(2), Matchers.equalTo(values.get(6))); + public void raceGroupWithTwoSeries() throws Exception { + SeriesBase seriesBase1 = Mockito.mock(SeriesBase.class); + SeriesBase seriesBase2 = Mockito.mock(SeriesBase.class); + List values = new ArrayList<>(); + values.addAll(createRacesForSeries(3, seriesBase1)); + values.addAll(createRacesForSeries(3, seriesBase2)); + + List result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(2)); + Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); + Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); + + ((RaceFragment) values.get(5)).setCurrentStatus(RaceLogRaceStatus.PRESCHEDULED); + + result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(5)); + Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); + Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); + Assert.assertThat(result.get(2), Matchers.equalTo(values.get(4))); + Assert.assertThat(result.get(3), Matchers.equalTo(values.get(5))); + Assert.assertThat(result.get(4), Matchers.equalTo(values.get(6))); + + ((RaceFragment) values.get(5)).setCurrentStatus(RaceLogRaceStatus.RUNNING); + + result = fixture.filterCurrentRaces(values); + Assert.assertThat(result.size(), Matchers.equalTo(3)); + Assert.assertThat(result.get(0), Matchers.equalTo(values.get(4))); + Assert.assertThat(result.get(1), Matchers.equalTo(values.get(5))); + Assert.assertThat(result.get(2), Matchers.equalTo(values.get(6))); } - - + private Collection createRacesForSeries(int max, SeriesBase seriesBase) { List results = new ArrayList<>(); results.add(new SeriesFragment(seriesBase)); - for(int i = 0; i + * @param + * @param + */ public interface CurrentRaceFilter { - - List filterCurrentRaces(Collection allItems); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java index 1e8d16eaf01..d92746d3e3a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsRaceFragment.java @@ -2,6 +2,6 @@ package com.sap.sailing.domain.base.racegroup; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -public interface IsRaceFragment { +public interface IsRaceFragment extends RaceGroupFragment { RaceLogRaceStatus getCurrentStatus(); } From eb757a91ce492298ba8399130bcd733269aaacb2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 11 Apr 2016 15:20:14 +0200 Subject: [PATCH 18/57] whitespace/formatting Change-Id: I284752826b767f58e53ee8ad0faf5dcde05ad555 --- .../racegroup/impl/CurrentRaceFilterImpl.java | 82 ++++++++----------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 599a9965709..f7fa5bbba59 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -12,20 +12,17 @@ import com.sap.sailing.domain.base.racegroup.IsFleetFragment; import com.sap.sailing.domain.base.racegroup.IsRaceFragment; import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -public class CurrentRaceFilterImpl implements CurrentRaceFilter { + +public class CurrentRaceFilterImpl + implements CurrentRaceFilter { @Override public List filterCurrentRaces(Collection allItems) { - Map> filteredRace = filterRaces(allItems); - filteredRace = removeAllListsBevorLastStarted(filteredRace); - return convertBackToList(filteredRace); } - - @SuppressWarnings("unchecked") private List convertBackToList(Map> filteredRaces) { Map> filteredRacesWithoutEmtyList = removeEmtyLists(filteredRaces); @@ -37,80 +34,72 @@ public class CurrentRaceFilterImpl > removeEmtyLists( - Map> filteredRaces) { + private Map> removeEmtyLists(Map> filteredRaces) { Map> result = new LinkedHashMap<>(); for (SERIES currentKey : filteredRaces.keySet()) { - if(!filteredRaces.get(currentKey).isEmpty()){ + if (!filteredRaces.get(currentKey).isEmpty()) { result.put(currentKey, filteredRaces.get(currentKey)); } } return result; } - - private Map> removeAllListsBevorLastStarted(Map> filteredRaces) { Map> result; SERIES keyOfLastStartedRace = null; for (SERIES currentKey : filteredRaces.keySet()) { - if(containsActiceRace(filteredRaces.get(currentKey))){ + if (containsActiceRace(filteredRaces.get(currentKey))) { keyOfLastStartedRace = currentKey; } } - if(keyOfLastStartedRace != null){ + if (keyOfLastStartedRace != null) { result = new LinkedHashMap<>(); boolean found = false; for (SERIES currentKey : filteredRaces.keySet()) { - if(found){ + if (found) { result.put(currentKey, filteredRaces.get(currentKey)); - } - else if(currentKey.equals(keyOfLastStartedRace)){ - found=true; + } else if (currentKey.equals(keyOfLastStartedRace)) { + found = true; result.put(currentKey, filteredRaces.get(currentKey)); } } - }else{ - result = filteredRaces; + } else { + result = filteredRaces; } return result; } - - private boolean containsActiceRace(List races) { for (RACE race : races) { - if(RaceLogRaceStatus.isRunningOrFinished(race.getCurrentStatus())){ + if (RaceLogRaceStatus.isRunningOrFinished(race.getCurrentStatus())) { return true; } } return false; } - - /** * method maps the races to fleets/series and removes all that should be not shown with "A follows B" relationship * - * @param allItems All SORTED Items + * @param allItems + * All SORTED Items * @return all current Races mapped to there fleet/series */ private Map> filterRaces(Collection allItems) { Map> mappedItems = mapItems(allItems); List newValueForKey = null; - SeriesBase firstSeries = mappedItems.keySet().iterator().next().getSeries(); - for(SERIES currentKey : mappedItems.keySet()){ - newValueForKey = filterWithFollowing(mappedItems.get(currentKey), currentKey.getSeries().equals(firstSeries)); + SeriesBase firstSeries = mappedItems.keySet().iterator().next().getSeries(); + for (SERIES currentKey : mappedItems.keySet()) { + newValueForKey = filterWithFollowing(mappedItems.get(currentKey), + currentKey.getSeries().equals(firstSeries)); mappedItems.put(currentKey, newValueForKey); } - + return mappedItems; } /** - * Filters a List (with races of the same series and fleet). + * Filters a List (with races of the same series and fleet). * * @param list * @return @@ -118,11 +107,11 @@ public class CurrentRaceFilterImpl filterWithFollowing(List races, boolean isFirstSeries) { List result = new LinkedList<>(); boolean addNext = isFirstSeries; - for(RACE currentRace: races){ - if(hasShowingStatus(currentRace)){ + for (RACE currentRace : races) { + if (hasShowingStatus(currentRace)) { result.add(currentRace); addNext = true; - }else if(addNext){ + } else if (addNext) { result.add(currentRace); addNext = false; } @@ -137,11 +126,13 @@ public class CurrentRaceFilterImpl > resultMap = new LinkedHashMap<>(); SERIES currentKey = null; List currentValue = null; - for(ITEM currentItem: allItems){ - if(currentItem instanceof IsFleetFragment){ - if(currentKey!=null && currentValue != null){ + for (ITEM currentItem : allItems) { + if (currentItem instanceof IsFleetFragment) { + if (currentKey != null && currentValue != null) { resultMap.put(currentKey, currentValue); } - currentKey = (SERIES) currentItem; - currentValue = new LinkedList<>(); - }else{ + currentKey = (SERIES) currentItem; + currentValue = new LinkedList<>(); + } else { currentValue.add((RACE) currentItem); } } - if(currentKey!=null && currentValue != null){ + if (currentKey != null && currentValue != null) { resultMap.put(currentKey, currentValue); } - + return resultMap; } - } From de94a47248e979cf27627c4e4afc52a9e9690892 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 12 Apr 2016 14:25:00 +0200 Subject: [PATCH 19/57] removing type parameters from the CurrentRaceFilter specification Change-Id: I5d83c87f1f7e764f0b5bc75c3f49fac9989c8fb8 --- .../base/racegroup/CurrentRaceFilter.java | 8 +- .../racegroup/impl/CurrentRaceFilterImpl.java | 78 ++++++++++--------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java index 3fe0dd77425..9afa53545cb 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java @@ -12,11 +12,7 @@ import com.sap.sailing.domain.abstractlog.race.state.RaceState; * * @author Charlotte Proeller (D062347) * @author Axel Uhl (d043530) - * - * @param - * @param - * @param */ -public interface CurrentRaceFilter { - List filterCurrentRaces(Collection allItems); +public interface CurrentRaceFilter { + List filterCurrentRaces(Collection allItems); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index f7fa5bbba59..541da450886 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -13,30 +13,34 @@ import com.sap.sailing.domain.base.racegroup.IsRaceFragment; import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -public class CurrentRaceFilterImpl - implements CurrentRaceFilter { +public class CurrentRaceFilterImpl implements CurrentRaceFilter { @Override - public List filterCurrentRaces(Collection allItems) { - Map> filteredRace = filterRaces(allItems); + public List filterCurrentRaces(Collection allItems) { + Map> filteredRace = filterRaces(allItems); filteredRace = removeAllListsBevorLastStarted(filteredRace); return convertBackToList(filteredRace); } - @SuppressWarnings("unchecked") - private List convertBackToList(Map> filteredRaces) { - Map> filteredRacesWithoutEmtyList = removeEmtyLists(filteredRaces); - List result = new LinkedList<>(); - for (SERIES currentKey : filteredRacesWithoutEmtyList.keySet()) { - result.add((ITEM) currentKey); - result.addAll((Collection) filteredRacesWithoutEmtyList.get(currentKey)); + private List convertBackToList(Map> filteredRaces) { + Map> filteredRacesWithoutEmtyList = removeEmtyLists(filteredRaces); + List result = new LinkedList<>(); + for (IsFleetFragment currentKey : filteredRacesWithoutEmtyList.keySet()) { + @SuppressWarnings("unchecked") + T t = (T) currentKey; // assumes that the IsFleetFragment specialization used here also specializes T which is the special RaceGroupFragment used here + result.add(t); + for (IsRaceFragment r : filteredRacesWithoutEmtyList.get(currentKey)) { + @SuppressWarnings("unchecked") + T tRace = (T) r;// assumes that the IsRaceFragment specialization used here also specializes T which is the special RaceGroupFragment used here + result.add(tRace); + } } return result; } - private Map> removeEmtyLists(Map> filteredRaces) { - Map> result = new LinkedHashMap<>(); - for (SERIES currentKey : filteredRaces.keySet()) { + private Map> removeEmtyLists(Map> filteredRaces) { + Map> result = new LinkedHashMap<>(); + for (IsFleetFragment currentKey : filteredRaces.keySet()) { if (!filteredRaces.get(currentKey).isEmpty()) { result.put(currentKey, filteredRaces.get(currentKey)); } @@ -44,10 +48,10 @@ public class CurrentRaceFilterImpl> removeAllListsBevorLastStarted(Map> filteredRaces) { - Map> result; - SERIES keyOfLastStartedRace = null; - for (SERIES currentKey : filteredRaces.keySet()) { + private Map> removeAllListsBevorLastStarted(Map> filteredRaces) { + Map> result; + IsFleetFragment keyOfLastStartedRace = null; + for (IsFleetFragment currentKey : filteredRaces.keySet()) { if (containsActiceRace(filteredRaces.get(currentKey))) { keyOfLastStartedRace = currentKey; } @@ -55,7 +59,7 @@ public class CurrentRaceFilterImpl(); boolean found = false; - for (SERIES currentKey : filteredRaces.keySet()) { + for (IsFleetFragment currentKey : filteredRaces.keySet()) { if (found) { result.put(currentKey, filteredRaces.get(currentKey)); } else if (currentKey.equals(keyOfLastStartedRace)) { @@ -69,8 +73,8 @@ public class CurrentRaceFilterImpl races) { - for (RACE race : races) { + private boolean containsActiceRace(List races) { + for (IsRaceFragment race : races) { if (RaceLogRaceStatus.isRunningOrFinished(race.getCurrentStatus())) { return true; } @@ -85,11 +89,11 @@ public class CurrentRaceFilterImpl> filterRaces(Collection allItems) { - Map> mappedItems = mapItems(allItems); - List newValueForKey = null; + private Map> filterRaces(Collection allItems) { + Map> mappedItems = mapItems(allItems); + List newValueForKey = null; SeriesBase firstSeries = mappedItems.keySet().iterator().next().getSeries(); - for (SERIES currentKey : mappedItems.keySet()) { + for (IsFleetFragment currentKey : mappedItems.keySet()) { newValueForKey = filterWithFollowing(mappedItems.get(currentKey), currentKey.getSeries().equals(firstSeries)); mappedItems.put(currentKey, newValueForKey); @@ -104,10 +108,10 @@ public class CurrentRaceFilterImpl filterWithFollowing(List races, boolean isFirstSeries) { - List result = new LinkedList<>(); + private List filterWithFollowing(List races, boolean isFirstSeries) { + List result = new LinkedList<>(); boolean addNext = isFirstSeries; - for (RACE currentRace : races) { + for (IsRaceFragment currentRace : races) { if (hasShowingStatus(currentRace)) { result.add(currentRace); addNext = true; @@ -125,7 +129,7 @@ public class CurrentRaceFilterImpl> mapItems(Collection allItems) { - Map> resultMap = new LinkedHashMap<>(); - SERIES currentKey = null; - List currentValue = null; - for (ITEM currentItem : allItems) { + private Map> mapItems(Collection allItems) { + Map> resultMap = new LinkedHashMap<>(); + IsFleetFragment currentKey = null; + List currentValue = null; + for (T currentItem : allItems) { if (currentItem instanceof IsFleetFragment) { if (currentKey != null && currentValue != null) { resultMap.put(currentKey, currentValue); } - currentKey = (SERIES) currentItem; + currentKey = (IsFleetFragment) currentItem; currentValue = new LinkedList<>(); } else { - currentValue.add((RACE) currentItem); + currentValue.add((IsRaceFragment) currentItem); } } if (currentKey != null && currentValue != null) { resultMap.put(currentKey, currentValue); } - return resultMap; } } From 5361718ab40b920d830cd60d7b98d8b6d712505d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 12 Apr 2016 14:29:49 +0200 Subject: [PATCH 20/57] fixed test case regarding removal of generics from CurrentRaceFilter Change-Id: Ie927c97ff3b064cd3bae739aba41a17c8cf9cdbb --- .../domain/base/racegroup/impl/CurrentRaceFilterImplTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index 08738521288..efd832f1358 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -19,11 +19,11 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; public class CurrentRaceFilterImplTest { - private CurrentRaceFilter fixture; + private CurrentRaceFilter fixture; @Before public void setUp() { - fixture = new CurrentRaceFilterImpl<>(); + fixture = new CurrentRaceFilterImpl(); } @Test From 575d373a22de278e787b92e8160bca1ef7913c42 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 13 Apr 2016 08:30:55 +0200 Subject: [PATCH 21/57] whitespace, formatting and commet changes; made one method private Change-Id: Ibb102cec17f2f848f21235e18e66d58383990e98 --- .../base/racegroup/impl/CurrentRaceFilterImplTest.java | 6 +++--- .../sap/sailing/domain/base/racegroup/IsFleetFragment.java | 1 - .../com/sap/sailing/domain/base/racegroup/RaceGroup.java | 5 ++++- .../sailing/server/gateway/impl/rc/RaceGroupFactory.java | 2 +- .../app/data/parsers/ManagedRacesDataParser.java | 7 +++++-- .../racecommittee/app/ui/activities/RacingActivity.java | 3 --- .../racecommittee/app/ui/fragments/RaceListFragment.java | 7 +------ 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index efd832f1358..8f70f450868 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -30,14 +30,14 @@ public class CurrentRaceFilterImplTest { public void easyRaceGroupTest() throws Exception { SeriesBase seriesBase = Mockito.mock(SeriesBase.class); List values = new ArrayList<>(); - SeriesFragment fristSerie = new SeriesFragment(seriesBase); - values.add(fristSerie); + SeriesFragment fristSeries = new SeriesFragment(seriesBase); + values.add(fristSeries); RaceFragment firstRace = new RaceFragment(RaceLogRaceStatus.UNSCHEDULED); values.add(firstRace); List result = fixture.filterCurrentRaces(values); Assert.assertThat(result.size(), Matchers.equalTo(2)); - Assert.assertThat(result.get(0), Matchers.equalTo(fristSerie)); + Assert.assertThat(result.get(0), Matchers.equalTo(fristSeries)); Assert.assertThat(result.get(1), Matchers.equalTo(firstRace)); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java index 4937571b142..725dc991b19 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/IsFleetFragment.java @@ -3,6 +3,5 @@ package com.sap.sailing.domain.base.racegroup; import com.sap.sailing.domain.base.SeriesBase; public interface IsFleetFragment extends RaceGroupFragment { - SeriesBase getSeries(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java index d68e0b4e04c..a0a40d73b5e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.base.racegroup; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.CourseArea; @@ -17,7 +18,9 @@ import com.sap.sse.common.Named; * * To align with the semantics of the Android application a {@link RaceGroup} consists of race rows rather than race * columns. A {@link RaceRow} is a set of races for one {@link Fleet}. A specific "race" of a row is called - * {@link RaceCell}. + * {@link RaceCell}. (Note that as described by bug 3011 this view is by now obsolete. The grouping by fleet is + * no longer desired by users; instead, grouping is desired only by regatta / {@link RaceGroup}, whereas ordering + * and filtering is based on series and fleet pertinence and {@link RaceState race state}, respectively.) *

* * For example: if you have 2 fleets (blue and yellow) there will be 2 {@link RaceRow}s in your {@link RaceGroup} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java index 1fe32f5c094..d4171146f3f 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java @@ -62,7 +62,7 @@ public class RaceGroupFactory { return new RaceGroupImpl(name, leaderboard.getDisplayName(), boatClass, courseArea, series, configuration); } - public Iterable getSeries(Leaderboard leaderboard) { + private Iterable getSeries(Leaderboard leaderboard) { Map> seriesToRaceColumns = getSeriesToRaceColumns(leaderboard); Collection seriesWithRows = new ArrayList<>(); for (Series series : getSeriesIterable(leaderboard, seriesToRaceColumns)) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java index 5b1b27ff356..ea8e683a485 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java @@ -29,6 +29,11 @@ import com.sap.sailing.racecommittee.app.utils.ManagedRaceCalculator; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.Helpers; +/** + * Produces an collection of {@link ManagedRace} objects whose order is determined by the order of the {@link RaceGroup}s + * as provided by the JSON object, and within each {@link RaceGroup} by the order of the series in the group, and within + * the series by the order of the "race rows" as provided by the server. + */ public class ManagedRacesDataParser implements DataParser> { private final JsonDeserializer deserializer; @@ -45,11 +50,9 @@ public class ManagedRacesDataParser implements DataParser parse(Reader reader) throws Exception { Object parsedResult = JSONValue.parse(reader); JSONArray jsonArray = Helpers.toJSONArraySafe(parsedResult); - Collection managedRaces = new ArrayList(); for (Object element : jsonArray) { JSONObject json = Helpers.toJSONObjectSafe(element); - RaceGroup group = deserializer.deserialize(json); addManagedRaces(managedRaces, group); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java index e09c8dba858..9efc508ec06 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java @@ -643,13 +643,10 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks ExLog.i(RacingActivity.this, TAG, "Same races are already loaded..."); } else { lastSeenRaces = data; - registerOnService(data); mRaceList.setupOn(data); - Toast.makeText(RacingActivity.this, String.format(getString(R.string.racing_load_success), data.size()), Toast.LENGTH_SHORT).show(); } - setProgressSpinnerVisibility(false); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 6e6ca0d2627..9e698e8b4f2 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -91,6 +91,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis private boolean mUpdateList = true; private ArrayList mViewItems; private View mProgress; + private BaseRaceStateChangedListener stateListener = new BaseRaceStateChangedListener() { @Override public void onStartTimeChanged(ReadonlyRaceState state) { @@ -200,9 +201,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - RaceListDataTypeRace.initializeTemplates(this); - if (mListView != null) { mAdapter = new ManagedRaceListAdapter(getActivity(), mViewItems); mListView.setAdapter(mAdapter); @@ -497,16 +496,12 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis public void setupOn(Collection races) { ExLog.i(getActivity(), TAG, String.format("Setting up %s with %d races.", this.getClass().getSimpleName(), races.size())); - unregisterOnAllRaces(); - mManagedRacesById.clear(); - for (ManagedRace managedRace : races) { mManagedRacesById.put(managedRace.getId(), managedRace); } registerOnAllRaces(); - initializeViewElements(); // prepare views and do initial filtering filterChanged(); From b39d958fcfc7335219f35a88fc1155a6b158a6ea Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 20 Apr 2016 18:50:09 +0200 Subject: [PATCH 22/57] renamed raceName to raceColumnName where appropriate Change-Id: I3fdc09864245543f4b40a1e0a1bdfb8441706e32 --- .../domain/base/racegroup/RaceCell.java | 2 +- .../racegroup/impl/CurrentRaceFilterImpl.java | 4 ++-- .../base/racegroup/impl/RaceCellImpl.java | 4 ++-- .../app/data/InMemoryDataStore.java | 2 +- .../app/data/OnlineDataManager.java | 6 +++--- .../data/parsers/ManagedRacesDataParser.java | 4 ++-- .../racecommittee/app/domain/ManagedRace.java | 6 ++++++ .../app/domain/ManagedRaceIdentifier.java | 2 +- .../domain/impl/ManagedRaceIdentifierImpl.java | 18 +++++++++--------- .../app/domain/impl/ManagedRaceImpl.java | 4 ++-- .../dialogs/ProtestTimeDialogFragment.java | 2 +- .../fragments/raceinfo/StartTimeFragment.java | 2 +- .../racecommittee/app/utils/RaceHelper.java | 10 +++++----- .../racecommittee/app/utils/WindHelper.java | 2 +- 14 files changed, 37 insertions(+), 31 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java index c3dc9f542bd..2fd42a2ef4c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java @@ -4,7 +4,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sse.common.Named; /** - * A "race". + * A "race". Its {@link #getName() name} represents the Race Column name coming from the leaderboard / {@link RaceGroup}. *

* * Because this and all other {@link RaceGroup} interfaces are used for communication with the Android applications a diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 541da450886..5ebaf485788 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -18,7 +18,7 @@ public class CurrentRaceFilterImpl implements CurrentRaceFilter { @Override public List filterCurrentRaces(Collection allItems) { Map> filteredRace = filterRaces(allItems); - filteredRace = removeAllListsBevorLastStarted(filteredRace); + filteredRace = removeAllListsBeforeLastStarted(filteredRace); return convertBackToList(filteredRace); } @@ -48,7 +48,7 @@ public class CurrentRaceFilterImpl implements CurrentRaceFilter { return result; } - private Map> removeAllListsBevorLastStarted(Map> filteredRaces) { + private Map> removeAllListsBeforeLastStarted(Map> filteredRaces) { Map> result; IsFleetFragment keyOfLastStartedRace = null; for (IsFleetFragment currentKey : filteredRaces.keySet()) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java index fb56c588657..57adfecf3ef 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java @@ -19,8 +19,8 @@ public class RaceCellImpl extends NamedImpl implements RaceCell { */ private final Double explicitFactor; - public RaceCellImpl(String name, RaceLog raceLog, double factor, Double explicitFactor) { - super(name); + public RaceCellImpl(String raceColumnName, RaceLog raceLog, double factor, Double explicitFactor) { + super(raceColumnName); this.raceLog = raceLog; this.factor = factor; this.explicitFactor = explicitFactor; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java index 8f6c9244c5b..f213b524b75 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java @@ -189,7 +189,7 @@ public enum InMemoryDataStore implements DataStore { } private SimpleRaceLogIdentifier convertManagedRaceIdentifierToSimpleRaceLogIdentifier(ManagedRaceIdentifier id) { - return new SimpleRaceLogIdentifierImpl(id.getRaceGroup().getName(), id.getRaceName(), id.getFleet().getName()); + return new SimpleRaceLogIdentifierImpl(id.getRaceGroup().getName(), id.getRaceColumnName(), id.getFleet().getName()); } @Override diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java index 98e9a8a43c8..1c4c8b36d46 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java @@ -202,7 +202,7 @@ public class OnlineDataManager extends DataManager { ManagedRaceIdentifier identifier = managedRace.getIdentifier(); // no parameter encoding required here; the UrlHelper.generateUrl call uses Url.Builder which handles encoding String raceGroupName = identifier.getRaceGroup().getName(); - String raceColumnName = identifier.getRaceName(); + String raceColumnName = identifier.getRaceColumnName(); String fleetName = identifier.getFleet().getName(); List> params = new ArrayList<>(); @@ -229,7 +229,7 @@ public class OnlineDataManager extends DataManager { ManagedRaceIdentifier identifier = managedRace.getIdentifier(); String raceGroupName = identifier.getRaceGroup().getName(); - String raceColumnName = identifier.getRaceName(); + String raceColumnName = identifier.getRaceColumnName(); String fleetName = identifier.getFleet().getName(); List> params = new ArrayList<>(); @@ -257,7 +257,7 @@ public class OnlineDataManager extends DataManager { ManagedRaceIdentifier identifier = managedRace.getIdentifier(); String raceGroupName = identifier.getRaceGroup().getName(); - String raceColumnName = identifier.getRaceName(); + String raceColumnName = identifier.getRaceColumnName(); String fleetName = identifier.getFleet().getName(); List> params = new ArrayList<>(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java index c5a053c4ec0..920d04267aa 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java @@ -71,7 +71,7 @@ public class ManagedRacesDataParser implements DataParser configurationLoader = globalConfigurationLoader; RegattaConfiguration localConfiguration = raceGroup.getRegattaConfiguration(); @@ -79,7 +79,7 @@ public class ManagedRacesDataParser implements DataParser data = new Util.Pair<>(race.getRaceName(), null); + Util.Pair data = new Util.Pair<>(race.getRaceColumnName(), null); int position = mRaceAdapter.add(data); if (position >= 0) { if (mRaceId != null) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java index 3d998b1cd1a..03067c6ec0b 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java @@ -36,7 +36,7 @@ public class RaceHelper { raceName = getRaceGroupName(race); raceName += getSeriesName(race.getSeries(), delimiter); raceName += getFleetName(race.getFleet(), delimiter); - raceName += delimiter + race.getRaceName(); + raceName += delimiter + race.getRaceColumnName(); } return raceName; @@ -51,7 +51,7 @@ public class RaceHelper { String raceName = ""; if (race != null) { - raceName += race.getRaceName(); + raceName += race.getRaceColumnName(); raceName += getFleetName(race.getFleet(), delimiter); raceName += getSeriesName(race.getSeries(), delimiter); raceName += delimiter + getRaceGroupName(race); @@ -69,7 +69,7 @@ public class RaceHelper { String raceName = ""; if (race != null) { - raceName += race.getRaceName(); + raceName += race.getRaceColumnName(); raceName += getFleetName(race.getFleet(), delimiter); } @@ -83,7 +83,7 @@ public class RaceHelper { if (race != null) { int maxElements = 3; - raceName += race.getRaceName(); + raceName += race.getRaceColumnName(); String groupName = getRaceGroupName(race); String seriesName = getSeriesName(race.getSeries(), delimiter); @@ -251,7 +251,7 @@ public class RaceHelper { public static SimpleRaceLogIdentifier getSimpleRaceLogIdentifier(@Nullable ManagedRace race) { SimpleRaceLogIdentifier identifier = null; if (race != null) { - identifier = new SimpleRaceLogIdentifierImpl(race.getRaceGroup().getName(), race.getRaceName(), race.getFleet().getName()); + identifier = new SimpleRaceLogIdentifierImpl(race.getRaceGroup().getName(), race.getRaceColumnName(), race.getFleet().getName()); } return identifier; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/WindHelper.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/WindHelper.java index 268121450cd..b31f8bdbf77 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/WindHelper.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/WindHelper.java @@ -85,7 +85,7 @@ public class WindHelper { String raceName = raceState.getString("raceName"); boolean matchingLeaderboardName = leaderboardName != null && leaderboardName.equals(triple.getA()); boolean matchingFleetName = fleetName != null && fleetName.equals(triple.getC()); - boolean matchingRaceName = raceName != null && raceName.equals(race.getRaceName()); + boolean matchingRaceName = raceName != null && raceName.equals(race.getRaceColumnName()); if (matchingLeaderboardName && matchingFleetName && matchingRaceName){ if (raceState.getBoolean("trackedRaceLinked")){ isTracked = true; From 3ff77640e8b0db5fc58f159fd98db5255df4123d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 21 Apr 2016 11:14:25 +0200 Subject: [PATCH 23/57] equipped RaceCell with a field zeroBasedIndexInFleet Change-Id: Ic09d225dcfce73b747424f73fae794e9043c512d --- .../domain/base/racegroup/FilterableRace.java | 25 +++++++++++++++++++ .../domain/base/racegroup/RaceCell.java | 17 +++++++++++++ .../base/racegroup/impl/RaceCellImpl.java | 23 ++++++++++++++++- .../racegroup/impl/RaceCellDeserializer.java | 3 ++- .../impl/RaceCellJsonSerializer.java | 6 +++-- .../gateway/impl/rc/RaceGroupFactory.java | 5 ++-- .../racecommittee/app/domain/ManagedRace.java | 3 ++- 7 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java new file mode 100644 index 00000000000..051e3a0f74c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.base.racegroup; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; + +/** + * Describes the properties by which races may be filtered. This encompasses, in particular, the race's place + * in the regatta structure + * @author Axel Uhl (d043530) + * + */ +public interface FilterableRace { + RaceGroup getRaceGroup(); + + SeriesBase getSeries(); + + Fleet getFleet(); + + /** + * @return the status of the race's state. + */ + RaceLogRaceStatus getStatus(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java index 2fd42a2ef4c..833f0a6fac2 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceCell.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.racegroup; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sse.common.Named; +import com.sap.sse.common.Util; /** * A "race". Its {@link #getName() name} represents the Race Column name coming from the leaderboard / {@link RaceGroup}. @@ -16,4 +17,20 @@ public interface RaceCell extends Named { double getFactor(); Double getExplicitFactor(); + + /** + * A Series offers a sequence of RaceColumns, each of them split according to the Fleets modeled for the Series. A + * {@link RaceCell} describes a "slot" in this grid, defined by the series, the fleet and the race column. While + * this object's {@link #getName() name} represents the race column's name, this doesn't tell anything about the + * "horizontal" position in the "grid" or in other words what the index is of the race column in which this cell + * lies. + *

+ * + * Indices returned by this method start with zero, meaning the first race column in the series. This corresponds to + * what one would get by asking {@link Util#indexOf(Iterable, Object) Util.indexOf(series.getRaceColumns(), + * thisCellsRaceColumn)}, except in case the first race column is a "virtual" one that holds a non-discardable + * carry-forward result. In this case, the second Race Column, which is the first "non-virtual" one, receives + * index 0. + */ + int getZeroBasedIndexInFleet(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java index 57adfecf3ef..b120a9c734f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.racegroup.impl; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.base.racegroup.RaceCell; +import com.sap.sse.common.Util; import com.sap.sse.common.impl.NamedImpl; public class RaceCellImpl extends NamedImpl implements RaceCell { @@ -18,12 +19,27 @@ public class RaceCellImpl extends NamedImpl implements RaceCell { * See {@code RaceColumn.getExplicitFactor()} */ private final Double explicitFactor; + + private final int zeroBasedIndexInFleet; - public RaceCellImpl(String raceColumnName, RaceLog raceLog, double factor, Double explicitFactor) { + /** + * @param zeroBasedIndexInFleet + * A Series offers a sequence of RaceColumns, each of them split according to the Fleets modeled for the + * Series. A {@link RaceCell} describes a "slot" in this grid, defined by the series, the fleet and the + * race column. While this object's {@link #getName() name} represents the race column's name, this + * doesn't tell anything about the "horizontal" position in the "grid" or in other words what the index + * is of the race column in which this cell lies. Indices returned by this method start with zero, + * meaning the first race column in the series. This corresponds to what one would get by asking + * {@link Util#indexOf(Iterable, Object) Util.indexOf(series.getRaceColumns(), thisCellsRaceColumn)}, + * except in case the first race column is a "virtual" one that holds a non-discardable carry-forward + * result. In this case, the second Race Column, which is the first "non-virtual" one, receives index 0. + */ + public RaceCellImpl(String raceColumnName, RaceLog raceLog, double factor, Double explicitFactor, int zeroBasedIndexInFleet) { super(raceColumnName); this.raceLog = raceLog; this.factor = factor; this.explicitFactor = explicitFactor; + this.zeroBasedIndexInFleet = zeroBasedIndexInFleet; } @Override @@ -48,4 +64,9 @@ public class RaceCellImpl extends NamedImpl implements RaceCell { public Double getExplicitFactor() { return explicitFactor; } + + @Override + public int getZeroBasedIndexInFleet() { + return zeroBasedIndexInFleet; + } } diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceCellDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceCellDeserializer.java index 018da384eb0..767e41dc1f9 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceCellDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceCellDeserializer.java @@ -24,9 +24,10 @@ public class RaceCellDeserializer implements JsonDeserializer { final Object factorJson = object.get(RaceCellJsonSerializer.FIELD_FACTOR); final double factor = factorJson == null ? 1 : ((Number) factorJson).doubleValue(); final Double explicitFactor = (Double) object.get(RaceCellJsonSerializer.FIELD_EXPLICIT_FACTOR); + final Number zeroBasedIndexInFleet = (Number) object.get(RaceCellJsonSerializer.FIELD_ZERO_BASED_INDEX_IN_FLEET); JSONObject logJson = Helpers.getNestedObjectSafe(object, RaceCellJsonSerializer.FIELD_RACE_LOG); RaceLog log = logDeserializer.deserialize(logJson); - return new RaceCellImpl(name, log, factor, explicitFactor); + return new RaceCellImpl(name, log, factor, explicitFactor, zeroBasedIndexInFleet == null ? -1 : zeroBasedIndexInFleet.intValue()); } } diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceCellJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceCellJsonSerializer.java index 38369bde1a4..19240a6b646 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceCellJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceCellJsonSerializer.java @@ -10,8 +10,9 @@ public class RaceCellJsonSerializer implements JsonSerializer { public static final String FIELD_NAME = "name"; public static final String FIELD_RACE_LOG = "raceLog"; public static final String FIELD_COMPETITORS = "competitors"; - public static final Object FIELD_FACTOR = "factor"; - public static final Object FIELD_EXPLICIT_FACTOR = "explicitfactor"; + public static final String FIELD_FACTOR = "factor"; + public static final String FIELD_EXPLICIT_FACTOR = "explicitfactor"; + public static final String FIELD_ZERO_BASED_INDEX_IN_FLEET = "zerobasedindexinfleet"; private JsonSerializer logSerializer; @@ -25,6 +26,7 @@ public class RaceCellJsonSerializer implements JsonSerializer { result.put(FIELD_NAME, object.getName()); result.put(FIELD_FACTOR, object.getFactor()); result.put(FIELD_EXPLICIT_FACTOR, object.getExplicitFactor()); + result.put(FIELD_ZERO_BASED_INDEX_IN_FLEET, object.getZeroBasedIndexInFleet()); result.put(FIELD_RACE_LOG, logSerializer.serialize(object.getRaceLog())); return result; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java index a63c3e5683a..53448156a4a 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java @@ -98,12 +98,13 @@ public class RaceGroupFactory { boolean skippedFirst = false; Collection cells = new ArrayList<>(); if (raceColumns != null) { - for (RaceColumn raceColumn : raceColumns) { + int zeroBasedIndexOfRaceInFleet = 0; + for (final RaceColumn raceColumn : raceColumns) { if (isFirstRaceColumnVirtual && !skippedFirst) { skippedFirst = true; } else { Fleet fleet = raceColumn.getFleetByName(fleetName); - cells.add(new RaceCellImpl(raceColumn.getName(), raceColumn.getRaceLog(fleet), raceColumn.getFactor(), raceColumn.getExplicitFactor())); + cells.add(new RaceCellImpl(raceColumn.getName(), raceColumn.getRaceLog(fleet), raceColumn.getFactor(), raceColumn.getExplicitFactor(), zeroBasedIndexOfRaceInFleet++)); } } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/ManagedRace.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/ManagedRace.java index 7b96846f193..3e30571d206 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/ManagedRace.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/ManagedRace.java @@ -7,6 +7,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.racegroup.FilterableRace; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.domain.impl.Result; import com.sap.sse.common.Named; @@ -19,7 +20,7 @@ import com.sap.sse.common.WithID; * @author Axel Uhl (d043530) * */ -public interface ManagedRace extends ManagedRaceIdentifier, Named, WithID { +public interface ManagedRace extends FilterableRace, ManagedRaceIdentifier, Named, WithID { /** * @return the identifier of the race. From 2fe8099f618322b7d6cc8232b1082dc06b42d7dd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 21 Apr 2016 12:40:29 +0200 Subject: [PATCH 24/57] added zeroBasedIndexInFleet to FilterableRace and ManagedRaceImpl Change-Id: I0d14a949d1905ded6d84107f2f4de3af30d512b3 --- .../domain/base/racegroup/FilterableRace.java | 16 +++++++++ .../app/data/OfflineDataManager.java | 9 +++-- .../data/parsers/ManagedRacesDataParser.java | 8 +++-- .../app/domain/impl/ManagedRaceImpl.java | 35 ++++++++++++++++--- .../app/ui/fragments/RaceListFragment.java | 4 +-- 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java index 051e3a0f74c..22fdb378312 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.base.racegroup; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sse.common.Util; /** * Describes the properties by which races may be filtered. This encompasses, in particular, the race's place @@ -22,4 +23,19 @@ public interface FilterableRace { */ RaceLogRaceStatus getStatus(); + /** + * A Series offers a sequence of RaceColumns, each of them split according to the Fleets modeled for the Series. A + * {@link RaceCell} describes a "slot" in this grid, defined by the series, the fleet and the race column. While + * this object's {@link #getName() name} represents the race column's name, this doesn't tell anything about the + * "horizontal" position in the "grid" or in other words what the index is of the race column in which this cell + * lies. + *

+ * + * Indices returned by this method start with zero, meaning the first race column in the series. This corresponds to + * what one would get by asking {@link Util#indexOf(Iterable, Object) Util.indexOf(series.getRaceColumns(), + * thisCellsRaceColumn)}, except in case the first race column is a "virtual" one that holds a non-discardable + * carry-forward result. In this case, the second Race Column, which is the first "non-virtual" one, receives + * index 0. + */ + int getZeroBasedIndexInFleet(); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java index 71683a1d6cc..95c0ab1b65c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java @@ -111,7 +111,8 @@ public class OfflineDataManager extends DataManager { AppPreferences.on(context).getAuthor(), 1, RaceLogRaceStatus.FINISHING)); ManagedRace q1 = new ManagedRaceImpl(new ManagedRaceIdentifierImpl("A.B", new FleetImpl("A"), qualifying, raceGroup), - RaceStateImpl.create(new AndroidRaceLogResolver(), log, AppPreferences.on(context).getAuthor(), configuration)); + RaceStateImpl.create(new AndroidRaceLogResolver(), log, AppPreferences.on(context).getAuthor(), configuration), + /* zeroBasedIndexInFleet */ 0); log = new RaceLogImpl(UUID.randomUUID()); /* @@ -120,7 +121,8 @@ public class OfflineDataManager extends DataManager { */ ManagedRace q2 = new ManagedRaceImpl(new ManagedRaceIdentifierImpl("B", new FleetImpl("A.A"), qualifying, raceGroup), - RaceStateImpl.create(new AndroidRaceLogResolver(), log, AppPreferences.on(context).getAuthor(), configuration)); + RaceStateImpl.create(new AndroidRaceLogResolver(), log, AppPreferences.on(context).getAuthor(), configuration), + /* zeroBasedIndexInFleet */ 1); log = new RaceLogImpl(UUID.randomUUID()); /* @@ -128,7 +130,8 @@ public class OfflineDataManager extends DataManager { * RaceLogRaceStatus.FINISHED)); */ ManagedRace q3 = new ManagedRaceImpl(new ManagedRaceIdentifierImpl("Q3", new FleetImpl("Default"), qualifying, raceGroup), - RaceStateImpl.create(new AndroidRaceLogResolver(), log, AppPreferences.on(context).getAuthor(), configuration)); + RaceStateImpl.create(new AndroidRaceLogResolver(), log, AppPreferences.on(context).getAuthor(), configuration), + /* zeroBasedIndexInFleet */ 2); /* * ManagedRace m1 = new ManagedRaceImpl( new ManagedRaceIdentifierImpl( "M1", new FleetImpl("Default"), medal, * raceGroup), null); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java index 920d04267aa..0b0789dc0ef 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java @@ -64,7 +64,8 @@ public class ManagedRacesDataParser implements DataParser configurationLoader = globalConfigurationLoader; RegattaConfiguration localConfiguration = raceGroup.getRegattaConfiguration(); if (localConfiguration != null) { @@ -80,7 +81,8 @@ public class ManagedRacesDataParser implements DataParser + * + * Indices returned by this method start with zero, meaning the first race column in the series. This + * corresponds to what one would get by asking {@link Util#indexOf(Iterable, Object) + * Util.indexOf(series.getRaceColumns(), thisCellsRaceColumn)}, except in case the first race column is a + * "virtual" one that holds a non-discardable carry-forward result. In this case, the second Race Column, + * which is the first "non-virtual" one, receives index 0. + */ + private ManagedRaceImpl(ManagedRaceIdentifier identifier, double factor, Double explicitFactor, int zeroBasedIndexInFleet) { this.identifier = identifier; this.competitors = new ArrayList<>(); this.courseOnServer = null; this.factor = factor; this.explicitFactor = explicitFactor; + this.zeroBasedIndexInFleet = zeroBasedIndexInFleet; } - public ManagedRaceImpl(ManagedRaceIdentifier identifier, RaceState state) { - this(identifier, 0, null); + public ManagedRaceImpl(ManagedRaceIdentifier identifier, RaceState state, int zeroBasedIndexInFleet) { + this(identifier, 0, null, zeroBasedIndexInFleet); this.state = state; } - public ManagedRaceImpl(ManagedRaceIdentifier identifier, ManagedRaceCalculator calculator, double factor, Double explicitFactor) { - this(identifier, factor, explicitFactor); + public ManagedRaceImpl(ManagedRaceIdentifier identifier, ManagedRaceCalculator calculator, double factor, Double explicitFactor, int zeroBasedIndexInFleet) { + this(identifier, factor, explicitFactor, zeroBasedIndexInFleet); this.calculator = calculator; } @@ -201,4 +220,10 @@ public class ManagedRaceImpl implements ManagedRace { public String toString() { return "ManagedRaceImpl [identifier=" + identifier + "]"; } + + @Override + public int getZeroBasedIndexInFleet() { + return zeroBasedIndexInFleet; + } + } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index a88a0cca96d..d178b015886 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -529,10 +529,10 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis } } - private void showProtestTimeDialog(String raceGroupName) { + private void showProtestTimeDialog(String raceGroupDisplayName) { // Find the race group for which the for (RaceGroupSeriesFleet raceGroupSeriesFleet : mRacesByGroup.keySet()) { - Boolean matchingRaceGroup = raceGroupName.equals(raceGroupSeriesFleet.getDisplayName()); + Boolean matchingRaceGroup = raceGroupDisplayName.equals(raceGroupSeriesFleet.getDisplayName()); if (matchingRaceGroup) { List races = mRacesByGroup.get(raceGroupSeriesFleet); if (!isRaceListDirty(races)) { From a833c463c681a13160af470ccb1b334ed9aa0682 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 21 Apr 2016 14:49:15 +0200 Subject: [PATCH 25/57] preparing filter and comparator, adding stuff to FilterableRace Change-Id: I8a1e54c5cadb8626a28a5647cc8c53a485e0d309 --- .../base/racegroup/CurrentRaceComparator.java | 34 +++++++++++++++++++ .../domain/base/racegroup/FilterableRace.java | 4 ++- .../domain/base/racegroup/RaceGroup.java | 3 +- .../base/racegroup/impl/RaceGroupImpl.java | 5 +++ .../app/domain/impl/ManagedRaceImpl.java | 4 +++ .../app/domain/impl/RaceGroupSeriesFleet.java | 17 +++++++--- .../app/ui/adapters/racelist/RaceFilter.java | 18 ++++++---- .../app/ui/fragments/RaceListFragment.java | 8 +---- 8 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java new file mode 100644 index 00000000000..f7130bf33c3 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java @@ -0,0 +1,34 @@ +package com.sap.sailing.domain.base.racegroup; + +import java.util.Comparator; + +import com.sap.sse.common.Util; + +/** + * When current races of a regatta are to be displayed to a user, a reasonable ordering is key to usability. This + * comparator sorts by their series index (day 1 before day 2; qualification before final, etc.), then by their index + * within the fleet (R1 before R2, etc.) and lastly by their fleet index (keeping the ordering as set on the server; not + * to be confused with the ranking ordering set for the fleet; so if on the server silver is placed before gold then + * that order is kept, regardless of the 1 and 2 values of these fleets). For a typical regatta with qualification, + * final and medal series, with race columns Q1..Q5, F6..F10, M and fleets Yellow/Blue and Silver/Gold, this would result + * in an ordering Q1-Yellow, Q1-Blue, ..., Q5-Yellow, Q5-Blue, F6-Silver, F6-Gold, ..., F10-Silver, F10-Gold, Medal. + * + * @author Axel Uhl (d043530) + * + */ +public class CurrentRaceComparator implements Comparator { + @Override + public int compare(FilterableRace o1, FilterableRace o2) { + int result = o1.getRaceGroup().getDisplayName().compareTo(o2.getRaceGroup().getDisplayName()); + if (result == 0) { + result = o1.getZeroBasedSeriesIndex() - o2.getZeroBasedSeriesIndex(); + if (result == 0) { + result = o1.getZeroBasedIndexInFleet() - o2.getZeroBasedIndexInFleet(); + if (result == 0) { + result = Util.indexOf(o1.getSeries().getFleets(), o1.getFleet()) - Util.indexOf(o2.getSeries().getFleets(), o2.getFleet()); + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java index 22fdb378312..b047e385999 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java @@ -15,7 +15,7 @@ public interface FilterableRace { RaceGroup getRaceGroup(); SeriesBase getSeries(); - + Fleet getFleet(); /** @@ -38,4 +38,6 @@ public interface FilterableRace { * index 0. */ int getZeroBasedIndexInFleet(); + + int getZeroBasedSeriesIndex(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java index a0a40d73b5e..e260a6f5cd5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java @@ -42,7 +42,8 @@ public interface RaceGroup extends Named { public BoatClass getBoatClass(); /** - * Gets the collection of series. + * Gets the collection of series, for a {@link RaceGroup} representing a regatta in the same order as + * the {@code Regatta.getSeries()} call returns them. */ public Iterable getSeries(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java index cb3a9e84d9f..98cb087fbe1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java @@ -17,6 +17,11 @@ public class RaceGroupImpl extends NamedImpl implements RaceGroup { private final Iterable series; private final RegattaConfiguration regattaConfiguration; + /** + * @param series + * the series; for a regatta they are expected to be in the same order as {@code Regatta.getSeries()} + * would deliver them. + */ public RaceGroupImpl(String name, String displayName, BoatClass boatClass, CourseArea courseArea, Iterable series, RegattaConfiguration regattaConfiguration) { super(name); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java index 73323264083..88a8e43727a 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java @@ -226,4 +226,8 @@ public class ManagedRaceImpl implements ManagedRace { return zeroBasedIndexInFleet; } + @Override + public int getZeroBasedSeriesIndex() { + return Util.indexOf(getRaceGroup().getSeries(), getSeries()); + } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java index 194ba051180..9755b1686a1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java @@ -9,6 +9,15 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sse.common.Util; +/** + * Represents a triple of {@link RaceGroup} (representing a regatta or a flexible leaderboard), a {@link SeriesBase series} + * and a {@link Fleet}. An instance can be constructed for a {@link ManagedRace} which then extracts these three properties + * from the race. Note that equal objects of this type can result for different races as long as they are in the equal + * {@link RaceGroup}, {@link SeriesBase series} and {@link Fleet}. + * + * @author Axel Uhl (d043530) + * + */ public class RaceGroupSeriesFleet { private RaceGroup raceGroup; @@ -21,7 +30,6 @@ public class RaceGroupSeriesFleet { raceGroup = race.getRaceGroup(); series = race.getSeries(); fleet = race.getFleet(); - seriesOrder = getSeriesIndex(race, series); fleetOrder = getFleetIndex(series.getFleets(), race.getFleet()); } @@ -30,6 +38,10 @@ public class RaceGroupSeriesFleet { return Util.indexOf(race.getRaceGroup().getSeries(), series); } + private int getFleetIndex(Iterable fleets, Fleet fleet) { + return Util.indexOf(fleets, fleet); + } + public RaceGroup getRaceGroup() { return raceGroup; } @@ -131,7 +143,4 @@ public class RaceGroupSeriesFleet { return true; } - private int getFleetIndex(Iterable fleets, Fleet fleet) { - return Util.indexOf(fleets, fleet); - } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index aba6586136e..6bb3365bd27 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -6,17 +6,23 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import android.widget.Filter; - +import com.sap.sailing.domain.base.racegroup.CurrentRaceComparator; +import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment.FilterMode; +import android.widget.Filter; + /** - * Filters races by status. - *

- * For each "run" of unscheduled races only the first will be taken. - * For each "run" of finished races only the last will be taken. + * Filters {@link ManagedRace} objects by status and place in the regatta structure. See {@link CurrentRaceFilter}. If + * the {@link #filterMode} is {@link FilterMode#ALL}, no filtering takes place at all, and all races will be displayed. + * If the {@link #filterMode} is {@link FilterMode#ACTIVE}, the set of races to be displayed is computed using a + * {@link CurrentRaceFilter}. For each race to be displayed, a {@link RaceListDataTypeRace} view item is produced. The + * races are grouped into sections, one for each series, and the items within each such series section are ordered using + * a {@link CurrentRaceComparator} while the series sections are ordered by the order in which the series appear in the + * Race Group / regatta. */ public class RaceFilter extends Filter { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index d178b015886..61ba9453759 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -345,7 +345,6 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis @Override public void onStart() { super.onStart(); - unregisterOnAllRaces(); registerOnAllRaces(); for (ManagedRace race : mManagedRacesById.values()) { @@ -377,34 +376,29 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); - mUpdateList = false; } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); - mUpdateList = true; } }; mDrawerLayout.post(new Runnable() { - @Override public void run() { mDrawerToggle.syncState(); } }); mDrawerLayout.setDrawerListener(mDrawerToggle); - if (mCourse != null) { SpannableString text = new SpannableString(course); StyleSpan spanBold = new StyleSpan(Typeface.BOLD); text.setSpan(spanBold, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mCourse.setText(text); } - if (mData != null) { mData.setText(StringHelper.on(getActivity()).getAuthor(author)); } @@ -414,7 +408,6 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis mRacesByGroup.clear(); for (ManagedRace race : mManagedRacesById.values()) { RaceGroupSeriesFleet container = new RaceGroupSeriesFleet(race); - if (!mRacesByGroup.containsKey(container)) { mRacesByGroup.put(container, new LinkedList()); } @@ -428,6 +421,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis // 2. Remove previous view items mViewItems.clear(); + // 3. Group active fleets from tree by series Map> seriesWithFleets = getFleetsGroupedBySeries(mRacesByGroup.keySet()); From ca9550e93d8175cb1f0c03076362a1a68389052f Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 21 Apr 2016 17:26:53 +0200 Subject: [PATCH 26/57] implemented new attribute fleetsCanRunInParallel on Series domain object including storage, test, replication and admin UI --- .../dto/SeriesCreationParametersDTO.java | 123 +- .../impl/DomainObjectFactoryImpl.java | 8 +- .../domain/persistence/impl/FieldNames.java | 1 + .../impl/MongoObjectFactoryImpl.java | 1 + .../impl/CreateAndTrackWithRaceLogTest.java | 2 +- .../sap/sailing/domain/base/SeriesBase.java | 2 +- .../racegroup/impl/SeriesWithRowsImpl.java | 11 +- ...mpetitorProviderCacheInvalidationTest.java | 2 +- .../test/LeaderboardCourseChangeTest.java | 2 +- .../LeaderboardScoringAndRankingTest.java | 16 +- ...ecutionOrderProvdiderAttachDetachTest.java | 586 +-- ...ttaLogEventNotificationForwardingTest.java | 3 +- .../AbstractMockedRaceMarkPassingTest.java | 2 +- .../domain/base/RaceColumnListener.java | 4 +- .../com/sap/sailing/domain/base/Series.java | 2 + .../RaceColumnListenerWithDefaultAction.java | 219 +- .../sailing/domain/base/impl/RegattaImpl.java | 7 +- .../sailing/domain/base/impl/SeriesImpl.java | 30 +- .../impl/AbstractSimpleLeaderboardImpl.java | 3891 +++++++++-------- .../DelayedLeaderboardCorrectionsImpl.java | 4 + .../meta/MetaLeaderboardColumn.java | 5 + .../util/impl/RaceColumnListeners.java | 6 + .../ui/test/PingMarkViaRegattaLogTest.java | 106 +- .../adminconsole/CreateRegattaCallback.java | 2 +- .../adminconsole/RegattaDetailsComposite.java | 5 +- .../ui/adminconsole/RegattaListComposite.java | 2 +- .../gwt/ui/adminconsole/SeriesDescriptor.java | 144 +- .../gwt/ui/adminconsole/SeriesEditDialog.java | 9 +- .../sailing/gwt/ui/client/SailingService.java | 2 +- .../gwt/ui/client/SailingServiceAsync.java | 2 +- .../sailing/gwt/ui/client/StringMessages.java | 1 + .../gwt/ui/client/StringMessages.properties | 1 + .../ui/client/StringMessages_de.properties | 1 + .../ui/client/StringMessages_ru.properties | 1 + .../ui/client/StringMessages_zh.properties | 1 + .../ui/client/StringMessages_zh_CN.properties | 1 + .../gwt/ui/server/SailingServiceImpl.java | 10 +- .../sap/sailing/gwt/ui/shared/SeriesDTO.java | 22 +- ...tStoringAndRetrievingRaceLogInRegatta.java | 162 +- ...estStoringAndLoadingEventsAndRegattas.java | 28 +- .../impl/SeriesWithRowsDeserializer.java | 73 +- .../jaxrs/LeaderboardGroupResourceTest.java | 184 +- ...LeaderboardsResourceCheckinAndOutTest.java | 2 +- .../LeaderboardsResourcePingMarkTest.java | 210 +- .../test/jaxrs/LeaderboardsResourceTest.java | 532 +-- .../test/jaxrs/RegattasResourceTest.java | 156 +- .../gateway/impl/rc/RaceGroupFactory.java | 4 +- .../test/AbstractLogReplicationTest.java | 232 +- .../test/RegattaReplicationTest.java | 16 +- .../impl/MediaMasterDataExportTest.java | 380 +- .../server/test/MasterDataImportTest.java | 26 +- .../server/test/SearchServiceTest.java | 644 +-- .../server/impl/RaceLogReplicator.java | 4 + .../server/impl/RaceLogScoringReplicator.java | 4 + .../server/impl/RacingEventServiceImpl.java | 2 +- .../AddSpecificRegatta.java | 2 +- .../UpdateSeries.java | 183 +- .../structureimport/StructureImporter.java | 2 +- .../app/data/OfflineDataManager.java | 4 +- 59 files changed, 4109 insertions(+), 3978 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java index 94ba4e935b4..fbc9ac70b83 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/SeriesCreationParametersDTO.java @@ -1,58 +1,65 @@ -package com.sap.sailing.domain.common.dto; - -import java.io.Serializable; -import java.util.List; - -public class SeriesCreationParametersDTO implements Serializable { - private static final long serialVersionUID = -3205172230707607515L; - - private List fleets; - - private boolean medal; - - private boolean startsWithZero; - - private boolean firstColumnIsNonDiscardableCarryForward; - - private int[] discardingThresholds; - - private boolean hasSplitFleetContiguousScoring; - - SeriesCreationParametersDTO() {} - - public SeriesCreationParametersDTO(List fleets, boolean medal, boolean startsWithZero, boolean firstColumnIsNonDiscardableCarryForward, - int[] discardingThresholds, boolean hasSplitFleetContiguousScoring) { - super(); - this.fleets = fleets; - this.medal = medal; - this.startsWithZero = startsWithZero; - this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; - this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; - this.discardingThresholds = discardingThresholds; - } - - public List getFleets() { - return fleets; - } - - public boolean isMedal() { - return medal; - } - - public boolean isStartsWithZero() { - return startsWithZero; - } - - public boolean hasSplitFleetContiguousScoring() { - return hasSplitFleetContiguousScoring; - } - - public int[] getDiscardingThresholds() { - return discardingThresholds; - } - - public boolean isFirstColumnIsNonDiscardableCarryForward() { - return firstColumnIsNonDiscardableCarryForward; - } - -} +package com.sap.sailing.domain.common.dto; + +import java.io.Serializable; +import java.util.List; + +public class SeriesCreationParametersDTO implements Serializable { + private static final long serialVersionUID = -3205172230707607515L; + + private List fleets; + + private boolean isMedal; + + private boolean isFleetsCanRunInParallel; + + private boolean isStartsWithZeroScore; + + private boolean firstColumnIsNonDiscardableCarryForward; + + private int[] discardingThresholds; + + private boolean hasSplitFleetContiguousScoring; + + SeriesCreationParametersDTO() {} + + public SeriesCreationParametersDTO(List fleets, boolean isMedal, boolean isFleetsCanRunInParallel, boolean isStartsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward, + int[] discardingThresholds, boolean hasSplitFleetContiguousScoring) { + super(); + this.fleets = fleets; + this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + this.isStartsWithZeroScore = isStartsWithZeroScore; + this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; + this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; + this.discardingThresholds = discardingThresholds; + } + + public List getFleets() { + return fleets; + } + + public boolean isMedal() { + return isMedal; + } + + public boolean isStartsWithZero() { + return isStartsWithZeroScore; + } + + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + public boolean hasSplitFleetContiguousScoring() { + return hasSplitFleetContiguousScoring; + } + + public int[] getDiscardingThresholds() { + return discardingThresholds; + } + + public boolean isFirstColumnIsNonDiscardableCarryForward() { + return firstColumnIsNonDiscardableCarryForward; + } + +} diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index 9eb8f687b6b..bf7f91277a4 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -1188,6 +1188,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { 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()); + // isFleetCanRunInParallel is a new field -> set to true when it does not exist in older db versions + boolean isFleetsCanRunInParallel = true; + Object isFleetCanRunInParallelObject = dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name()); + if(isFleetCanRunInParallelObject != null) { + isFleetsCanRunInParallel = (Boolean) dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name()); + } Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name()); Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name()); Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name()); @@ -1195,7 +1201,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { List fleets = loadFleets(dbFleets); BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name()); Iterable raceColumnNames = loadRaceColumnNames(dbRaceColumns); - Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry); + Series series = new SeriesImpl(name, isMedal, isFleetsCanRunInParallel, fleets, raceColumnNames, trackedRegattaRegistry); if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) { ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS); series.setResultDiscardingRule(resultDiscardingRule); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 1763fe75957..e7c2699cf1e 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -36,6 +36,7 @@ public enum FieldNames { REGATTA_USE_START_TIME_INFERENCE, SERIES_NAME, SERIES_IS_MEDAL, + SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL, SERIES_STARTS_WITH_ZERO_SCORE, SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING, SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD, diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 8377d398eff..37e3c3573d5 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -674,6 +674,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBObject dbSeries = new BasicDBObject(); dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName()); dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal()); + dbSeries.put(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name(), s.isFleetsCanRunInParallel()); dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring()); dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore()); dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward()); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index e70ac72f818..b5f231b00f3 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -76,7 +76,7 @@ public class CreateAndTrackWithRaceLogTest { gpsFixStore = service.getGPSFixStore(); service.getMongoObjectFactory().getDatabase().dropDatabase(); author = service.getServerAuthor(); - Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), Collections.emptySet(), + Series series = new SeriesImpl("series", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(fleet), Collections.emptySet(), service); regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /* startDate */null, /* endDate */null, UUID.randomUUID(), Collections. singletonList(series), false, diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java index 726eebefb64..6dbb0f7f1f8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SeriesBase.java @@ -33,5 +33,5 @@ public interface SeriesBase extends Renamable { * made except that within a fleet the races are usually run in the order as defined by the {@link #getRaceColumns() * race columns}. */ - boolean isFleetsRunInParallel(); + boolean isFleetsCanRunInParallel(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java index fa5aa6c7126..9aaeacdee1b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java @@ -13,11 +13,13 @@ public class SeriesWithRowsImpl implements SeriesWithRows { private String name; private Iterable raceRows; private boolean isMedal; - - public SeriesWithRowsImpl(String name, boolean isMedal, Iterable raceRows) { + private boolean isFleetsCanRunInParallel; + + public SeriesWithRowsImpl(String name, boolean isMedal, boolean isFleetsCanRunInParallel, Iterable raceRows) { this.name = name; this.raceRows = raceRows; this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; } public String getName() { @@ -46,8 +48,7 @@ public class SeriesWithRowsImpl implements SeriesWithRows { } @Override - public boolean isFleetsRunInParallel() { - // TODO This is a default implementation so far, allowing for parallel fleet races to not restrict generality. This shall become a configurable series property. See bug 3532 - return true; + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java index b9a5c66198f..b47d2282365 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java @@ -78,7 +78,7 @@ public class CompetitorProviderCacheInvalidationTest extends AbstractLeaderboard null, /* endDate */null, /* trackedRegattaRegistry */null, new LowPoint(), UUID.randomUUID(), courseArea); regattaLeaderboard = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(new int[0])); - regatta.addSeries(new SeriesImpl("Test Series", /* isMedal */false, Arrays.asList(new FleetImpl("Yellow"), + regatta.addSeries(new SeriesImpl("Test Series", /* isMedal */false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new FleetImpl("Yellow"), new FleetImpl("Blue")), Arrays.asList("R1", "R2", "R3"), /* trackedRegattaRegistry */null)); for (int l = 0; l < NUMBER_OF_COMP_LISTS; l++) { compLists[l] = new ArrayList(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java index b43c350c0e5..94f362b2691 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java @@ -175,7 +175,7 @@ public class LeaderboardCourseChangeTest { Set fleets = new HashSet<>(); fleets.add(fleet); - Series series = new SeriesImpl("TestSeries", false, fleets, new HashSet(), trackedRegattaRegistry); + Series series = new SeriesImpl("TestSeries", false, true, fleets, new HashSet(), trackedRegattaRegistry); return series; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index ace5830c7fe..62ee7421832 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1709,7 +1709,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = 1; i <= numberOfQualifyingRaces; i++) { qualifyingRaceColumnNames.add("Q" + i); } - Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets, + Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, qualifyingRaceColumnNames, /* trackedRegattaRegistry */null); series.add(qualifyingSeries); } @@ -1725,7 +1725,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = 1; i <= numberOfFinalRaces; i++) { finalRaceColumnNames.add("F" + i); } - Series finalSeries = new SeriesImpl("Final", /* isMedal */false, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(finalSeries); } @@ -1735,7 +1735,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { medalFleets.add(new FleetImpl("Medal")); List medalRaceColumnNames = new ArrayList(); medalRaceColumnNames.add("M"); - Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(medalSeries); } @@ -1774,7 +1774,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } else { roundName = "Round "+roundNumber; } - Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, fleetsInRound, + Series seriesForRound = new SeriesImpl("E"+elimination+" "+roundName, /* isMedal */ false, /* isFleetsCanRunInParallel */ true, fleetsInRound, raceColumnNameForRound, /* trackedRegattaRegistry */null); if (isFinalRound) { // last "Final" round; here, the fleets are contiguously scored @@ -1808,7 +1808,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = 1; i <= numberOfBeforeRaces; i++) { beforeRaceColumnNames.add("R" + i); } - Series qualifyingSeries = new SeriesImpl("Before", /* isMedal */false, beforeFleets, + Series qualifyingSeries = new SeriesImpl("Before", /* isMedal */false, /* isFleetsCanRunInParallel */ true, beforeFleets, beforeRaceColumnNames, /* trackedRegattaRegistry */null); series.add(qualifyingSeries); } @@ -1820,7 +1820,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } List qualificationRaceColumnNames = new ArrayList(); qualificationRaceColumnNames.add("Q"); - Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, qualificationFleets, qualificationRaceColumnNames, /* trackedRegattaRegistry */ null); + Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualificationFleets, qualificationRaceColumnNames, /* trackedRegattaRegistry */ null); // discard the one and only qualification race; it doesn't score qualificationSeries.setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1 })); series.add(qualificationSeries); @@ -1835,7 +1835,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } List finalRaceColumnNames = new ArrayList(); finalRaceColumnNames.add("F"); - Series finalSeries = new SeriesImpl("Final", /* isMedal */false, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); finalSeries.setSplitFleetContiguousScoring(true); series.add(finalSeries); } @@ -1847,7 +1847,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i = numberOfBeforeRaces+1; i <= numberOfBeforeRaces+numberOfFinalRaces; i++) { finalRaceColumnNames.add("R" + i); } - Series finalSeries = new SeriesImpl("After", /* isMedal */false, afterFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("After", /* isMedal */false, /* isFleetsCanRunInParallel */ true, afterFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(finalSeries); } final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("Extreme40", /* typicallyStartsUpwind */ false); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java index 13784b461aa..fbb03acad0b 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java @@ -1,293 +1,293 @@ -package com.sap.sailing.domain.test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import org.junit.Test; - -import com.sap.sailing.domain.abstractlog.race.RaceLog; -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.RaceColumnInSeries; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.base.impl.TrackedRaces; -import com.sap.sailing.domain.common.TimingConstants; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.WindImpl; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; -import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -/** - * Covers the linking and unlinking between {@link RaceColumn}s and {@link TrackedRaces}s - * and tests whether {@link RaceExecutionOrderProvider}s get correctly attached and detached. Also tests - * late attaching and detaching of {@link RaceExecutionOrderProvider}s in case the {@link RaceExecutionOrderProvider} - * was null when {@link RaceColumn} was linked to {@link TrackedRace}. - * - * @author Alexander Ries (D062114) - * @author Axel Uhl (D043530) - * - */ -public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest { - private FlexibleLeaderboard flexibleLeaderboard; - private RaceColumnInSeries raceColumnInSeries; - private Fleet fleet; - private TrackedRaceImpl trackedRace; - private Regatta regatta; - private Series series; - - private final String REGATTA = "TestRegatta"; - private final String RACE = "TestRace"; - private final String FLEET = "TestFleet"; - private final String BOATCLASS = "TestClass"; - private final String SERIES = "TestSeries"; - private final String FLEXIBLELEADERBOARD = "TestFlexibleLeaderboard"; - private final String RACECOLUMN_SERIES = "TestSeriesRaceColumn"; - private final String RACECOLUMN_FLEXIBLELEADERBOARD = "TestFlexibleLeaderboardRaceColumn"; - - @Test - public void testRaceExecutionOrderProviderAttachDetachWithRaceColumn() { - trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), - MillisecondsTimePoint.now()); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); - assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); - flexibleLeaderboard.removeRaceColumn(RACECOLUMN_FLEXIBLELEADERBOARD); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - } - - @Test - public void testWindInRegularIntervalWithPreviousRaceStillTracking() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_MINUTE.times(5)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - Wind wind = new WindImpl(new DegreePosition(12, 13), startOfSecondRace.plus(Duration.ONE_MINUTE), new KnotSpeedWithBearingImpl( - /* speedInKnots */18, new DegreeBearingImpl(185))); - assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking still open and takes the fix - assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval - } - - @Test - public void testWindInExtendedLeadIntervalWithPreviousRaceStillTracking() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - // the wind fix is after the grace period of the first race's end, so won't be accepted by it, but within the extended - // time range before the second race, so shall be accepted by it. - Wind wind = new WindImpl(new DegreePosition(12, 13), - endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking closed one second after it started and doesn't accept the fix - assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval - } - - @Test - public void testWindInExtendedLeadIntervalWithNoPreviousRace() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); - // the wind fix is within the extended time range before the second race, and there is no previous race, so shall be accepted by it - Wind wind = new WindImpl(new DegreePosition(12, 13), - endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); - } - - @Test - public void testWindInExtendedLeadIntervalWithPreviousRaceLongAgo() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.times(2)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - // the wind fix is outside the extended time range before the second race, and the previous race doesn't accept it; shall not be accepted - Wind wind = new WindImpl(new DegreePosition(12, 13), - endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period - assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); - } - - @Test - public void testWindInExtendedLeadIntervalButStillRecordedByPreviousRace() { - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); - final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); - previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); - flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, - new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); - flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); - flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); - // the wind fix is inside the extended time range before the second race, but the previous race still accepts it; shall not be accepted - Wind wind = new WindImpl(new DegreePosition(12, 13), - startOfFirstRace.plus(Duration.ONE_SECOND), // fix is recorded by previous race - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); - assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period - assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); - } - - @Test - public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumnInSeries() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */true); - raceColumnInSeries.setTrackedRace(fleet, trackedRace); - assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); - raceColumnInSeries.releaseTrackedRace(fleet); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - } - - @Test - public void testRaceExecutionOrderProviderAttachDetachWhenSeriesRegattaIsSetAndRemovedAfterTrackedRaceHaveBeenSetToRaceColumns() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); - raceColumnInSeries.setTrackedRace(fleet, trackedRace); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - series.setRegatta(regatta); - assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); - series.setRegatta(null); - assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); - } - - @Test - public void testThatTrackedRaceReceivesRaceLogWhenSeriesIsLinkedToRegatta() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); - raceColumnInSeries.setTrackedRace(fleet, trackedRace); - assertNull(raceColumnInSeries.getRaceLog(fleet)); - regatta.addSeries(series); - final RaceLog raceLog = raceColumnInSeries.getRaceLog(fleet); - assertNotNull(raceLog); - assertNotNull(trackedRace.getRaceLog(raceLog.getId())); - } - - private void createTestSetupWithRegattaAndSeries(boolean linkSeriesToRegatta) { - trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), - MillisecondsTimePoint.now()); - fleet = new FleetImpl(FLEET); - Set fleets = new HashSet<>(); - fleets.add(fleet); - series = new SeriesImpl(SERIES, false, fleets, new HashSet(), null); - Set seriesSet = new HashSet<>(); - if (linkSeriesToRegatta) { - seriesSet.add(series); - } - BoatClass boatClass = new BoatClassImpl(BOATCLASS, true); - raceColumnInSeries = series.addRaceColumn(RACECOLUMN_SERIES, null); - ScoringScheme scoringScheme = new LowPoint(); - regatta = new RegattaImpl(RegattaImpl.getDefaultName(REGATTA, boatClass.getName()), boatClass, - /* startDate */null, /* endDate */null, seriesSet, false, scoringScheme, UUID.randomUUID(), null, - OneDesignRankingMetric::new); - } - - /** - * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" - * definition's transitive closure may become cyclic, and a race may be considered its own - * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} - * implementation which traverses a race's predecessors recursively. - */ - @Test - public void testCyclicPreviousRacesSequence() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); - final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r3 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r4 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); - final TimePoint startOfSecondRace = endOfFirstRace.plus(Duration.ONE_MINUTE); - final TimePoint endOfSecondRace = startOfSecondRace.plus(Duration.ONE_MINUTE); - final TimePoint startOfThirdRace = endOfSecondRace.plus(Duration.ONE_MINUTE); - final TimePoint endOfThirdRace = startOfThirdRace.plus(Duration.ONE_MINUTE); - final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); - final TrackedRace tr2 = createTrackedRace("SecondRace", startOfSecondRace, endOfSecondRace); - final TrackedRace tr3 = createTrackedRace("ThirdRace", startOfThirdRace, endOfThirdRace); - r1.setTrackedRace(fleet, tr3); - r2.setTrackedRace(fleet, tr1); - r3.setTrackedRace(fleet, tr2); - r4.setTrackedRace(fleet, tr3); // produce a cycle because now transitively tr1 has itself as a predecessor - assertTrue(tr3.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), - startOfThirdRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); - } - - /** - * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" - * definition's transitive closure may become cyclic, and a race may be considered its own - * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} - * implementation which traverses a race's predecessors recursively. - */ - @Test - public void testCyclicPreviousRacesSequenceUsingSingleRace() { - createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); - final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); - final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); - final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); - r1.setTrackedRace(fleet, tr1); - r2.setTrackedRace(fleet, tr1); - assertTrue(tr1.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), - startOfFirstRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), - new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); - } - - private DynamicTrackedRace createTrackedRace(final String name, final TimePoint startOfRace, final TimePoint endOfRace) { - DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections. emptyList(), startOfRace); - trackedRace.setStartOfTrackingReceived(startOfRace); - trackedRace.setEndOfTrackingReceived(endOfRace); - return trackedRace; - } -} +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.race.RaceLog; +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.RaceColumnInSeries; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.TrackedRaces; +import com.sap.sailing.domain.common.TimingConstants; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.WindImpl; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * Covers the linking and unlinking between {@link RaceColumn}s and {@link TrackedRaces}s + * and tests whether {@link RaceExecutionOrderProvider}s get correctly attached and detached. Also tests + * late attaching and detaching of {@link RaceExecutionOrderProvider}s in case the {@link RaceExecutionOrderProvider} + * was null when {@link RaceColumn} was linked to {@link TrackedRace}. + * + * @author Alexander Ries (D062114) + * @author Axel Uhl (D043530) + * + */ +public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest { + private FlexibleLeaderboard flexibleLeaderboard; + private RaceColumnInSeries raceColumnInSeries; + private Fleet fleet; + private TrackedRaceImpl trackedRace; + private Regatta regatta; + private Series series; + + private final String REGATTA = "TestRegatta"; + private final String RACE = "TestRace"; + private final String FLEET = "TestFleet"; + private final String BOATCLASS = "TestClass"; + private final String SERIES = "TestSeries"; + private final String FLEXIBLELEADERBOARD = "TestFlexibleLeaderboard"; + private final String RACECOLUMN_SERIES = "TestSeriesRaceColumn"; + private final String RACECOLUMN_FLEXIBLELEADERBOARD = "TestFlexibleLeaderboardRaceColumn"; + + @Test + public void testRaceExecutionOrderProviderAttachDetachWithRaceColumn() { + trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), + MillisecondsTimePoint.now()); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); + assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); + flexibleLeaderboard.removeRaceColumn(RACECOLUMN_FLEXIBLELEADERBOARD); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + } + + @Test + public void testWindInRegularIntervalWithPreviousRaceStillTracking() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_MINUTE.times(5)); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + Wind wind = new WindImpl(new DegreePosition(12, 13), startOfSecondRace.plus(Duration.ONE_MINUTE), new KnotSpeedWithBearingImpl( + /* speedInKnots */18, new DegreeBearingImpl(185))); + assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking still open and takes the fix + assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval + } + + @Test + public void testWindInExtendedLeadIntervalWithPreviousRaceStillTracking() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + // the wind fix is after the grace period of the first race's end, so won't be accepted by it, but within the extended + // time range before the second race, so shall be accepted by it. + Wind wind = new WindImpl(new DegreePosition(12, 13), + endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // previous race has tracking closed one second after it started and doesn't accept the fix + assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // tracked race also needs to take the fix as it falls into the regular tracking interval + } + + @Test + public void testWindInExtendedLeadIntervalWithNoPreviousRace() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); + // the wind fix is within the extended time range before the second race, and there is no previous race, so shall be accepted by it + Wind wind = new WindImpl(new DegreePosition(12, 13), + endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertTrue(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); + } + + @Test + public void testWindInExtendedLeadIntervalWithPreviousRaceLongAgo() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.times(2)); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + // the wind fix is outside the extended time range before the second race, and the previous race doesn't accept it; shall not be accepted + Wind wind = new WindImpl(new DegreePosition(12, 13), + endOfFirstRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).plus(Duration.ONE_MINUTE), // fix is after the grace period + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertFalse(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period + assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); + } + + @Test + public void testWindInExtendedLeadIntervalButStillRecordedByPreviousRace() { + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); + final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); + previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, + new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); + flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); + flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"2", false); + // the wind fix is inside the extended time range before the second race, but the previous race still accepts it; shall not be accepted + Wind wind = new WindImpl(new DegreePosition(12, 13), + startOfFirstRace.plus(Duration.ONE_SECOND), // fix is recorded by previous race + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))); + assertTrue(previousTrackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); // fix is after first race's end plus grace period + assertFalse(trackedRace.takesWindFixWithTimePoint(wind.getTimePoint())); + } + + @Test + public void testRaceExecutionOrderProviderAttachDetachWithRaceCollumnInSeries() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */true); + raceColumnInSeries.setTrackedRace(fleet, trackedRace); + assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); + raceColumnInSeries.releaseTrackedRace(fleet); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + } + + @Test + public void testRaceExecutionOrderProviderAttachDetachWhenSeriesRegattaIsSetAndRemovedAfterTrackedRaceHaveBeenSetToRaceColumns() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); + raceColumnInSeries.setTrackedRace(fleet, trackedRace); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + series.setRegatta(regatta); + assertTrue(trackedRace.hasRaceExecutionOrderProvidersAttached()); + series.setRegatta(null); + assertFalse(trackedRace.hasRaceExecutionOrderProvidersAttached()); + } + + @Test + public void testThatTrackedRaceReceivesRaceLogWhenSeriesIsLinkedToRegatta() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */false); + raceColumnInSeries.setTrackedRace(fleet, trackedRace); + assertNull(raceColumnInSeries.getRaceLog(fleet)); + regatta.addSeries(series); + final RaceLog raceLog = raceColumnInSeries.getRaceLog(fleet); + assertNotNull(raceLog); + assertNotNull(trackedRace.getRaceLog(raceLog.getId())); + } + + private void createTestSetupWithRegattaAndSeries(boolean linkSeriesToRegatta) { + trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), + MillisecondsTimePoint.now()); + fleet = new FleetImpl(FLEET); + Set fleets = new HashSet<>(); + fleets.add(fleet); + series = new SeriesImpl(SERIES, false, /* isFleetsCanRunInParallel */ true, fleets, new HashSet(), null); + Set seriesSet = new HashSet<>(); + if (linkSeriesToRegatta) { + seriesSet.add(series); + } + BoatClass boatClass = new BoatClassImpl(BOATCLASS, true); + raceColumnInSeries = series.addRaceColumn(RACECOLUMN_SERIES, null); + ScoringScheme scoringScheme = new LowPoint(); + regatta = new RegattaImpl(RegattaImpl.getDefaultName(REGATTA, boatClass.getName()), boatClass, + /* startDate */null, /* endDate */null, seriesSet, false, scoringScheme, UUID.randomUUID(), null, + OneDesignRankingMetric::new); + } + + /** + * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" + * definition's transitive closure may become cyclic, and a race may be considered its own + * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} + * implementation which traverses a race's predecessors recursively. + */ + @Test + public void testCyclicPreviousRacesSequence() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); + final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r3 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r4 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); + final TimePoint startOfSecondRace = endOfFirstRace.plus(Duration.ONE_MINUTE); + final TimePoint endOfSecondRace = startOfSecondRace.plus(Duration.ONE_MINUTE); + final TimePoint startOfThirdRace = endOfSecondRace.plus(Duration.ONE_MINUTE); + final TimePoint endOfThirdRace = startOfThirdRace.plus(Duration.ONE_MINUTE); + final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); + final TrackedRace tr2 = createTrackedRace("SecondRace", startOfSecondRace, endOfSecondRace); + final TrackedRace tr3 = createTrackedRace("ThirdRace", startOfThirdRace, endOfThirdRace); + r1.setTrackedRace(fleet, tr3); + r2.setTrackedRace(fleet, tr1); + r3.setTrackedRace(fleet, tr2); + r4.setTrackedRace(fleet, tr3); // produce a cycle because now transitively tr1 has itself as a predecessor + assertTrue(tr3.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), + startOfThirdRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); + } + + /** + * See bug 3173. When a race is linked more than once to a fleet in a series, the "previous race" + * definition's transitive closure may become cyclic, and a race may be considered its own + * direct or transitive predecessor. This can lead to endless recursion in the {@link TrackedRace#takesWindFix(Wind)} + * implementation which traverses a race's predecessors recursively. + */ + @Test + public void testCyclicPreviousRacesSequenceUsingSingleRace() { + createTestSetupWithRegattaAndSeries(/* linkSeriesToRegatta */ true); + final RaceColumnInSeries r1 = series.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + final RaceColumnInSeries r2 = series.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); + final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_MINUTE); + final TrackedRace tr1 = createTrackedRace("FirstRace", startOfFirstRace, endOfFirstRace); + r1.setTrackedRace(fleet, tr1); + r2.setTrackedRace(fleet, tr1); + assertTrue(tr1.takesWindFixWithTimePoint(new WindImpl(new DegreePosition(12, 13), + startOfFirstRace.minus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)), + new KnotSpeedWithBearingImpl(/* speedInKnots */18, new DegreeBearingImpl(185))).getTimePoint())); + } + + private DynamicTrackedRace createTrackedRace(final String name, final TimePoint startOfRace, final TimePoint endOfRace) { + DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections. emptyList(), startOfRace); + trackedRace.setStartOfTrackingReceived(startOfRace); + trackedRace.setEndOfTrackingReceived(endOfRace); + return trackedRace; + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java index 31d96c7cb22..6555282c269 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java @@ -178,7 +178,8 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa } private Regatta createRegatta() { - Series series = new SeriesImpl("Test Series", /* isMedal */ false, /* fleets */ Collections.singleton(new FleetImpl("Default")), + Series series = new SeriesImpl("Test Series", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, + /* fleets */ Collections.singleton(new FleetImpl("Default")), Collections.singleton("R1"), /* trackedRegattaRegistry */ null); Regatta regatta = new RegattaImpl("test", null, null, null, Collections.singleton(series), false, new LowPoint(), "test", null, OneDesignRankingMetric::new); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java index 1f6023f282d..fedbd2f437a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java @@ -68,7 +68,7 @@ public class AbstractMockedRaceMarkPassingTest { Waypoint w5 = new WaypointImpl(cp, PassingInstruction.Line); waypoints = Arrays.asList(w1, w2, w3, w4, w5); Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, - /*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")), + /*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, /* isFleetsCanRunInParallel */ true, Arrays.asList(new FleetImpl("fleet")), new ArrayList(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5)), OneDesignRankingMetric::new); Course course = new CourseImpl("course", waypoints); RaceDefinition raceDef = new RaceDefinitionImpl("Performance Race", course, boatClass, Arrays.asList(ron, tom, ben)); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java index d096c29fcb4..ac4bea25d40 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumnListener.java @@ -14,7 +14,9 @@ public interface RaceColumnListener extends Serializable { void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace); void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace); - + + void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel); + void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore); void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java index 5f61acb1989..02bf40878bc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Series.java @@ -41,6 +41,8 @@ public interface Series extends SeriesBase { void setIsMedal(boolean isMedal); + void setIsFleetsCanRunInParallel(boolean isFleetsCanRunInParallel); + Fleet getFleetByName(String fleetName); /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java index 6c154e3c3cd..2454b024231 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnListenerWithDefaultAction.java @@ -1,107 +1,112 @@ -package com.sap.sailing.domain.base.impl; - -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; -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.leaderboard.ResultDiscardingRule; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.tracking.TrackedRace; - -/** - * Delegates all listener operations to a default action which is implemented here to do nothing. This way, subclasses - * can easily provide a default action to be executed for all callback methods except for maybe a few which then need to - * be explicitly overridden. If not most methods need to perform the same action then instead of implementing a default - * action subclasses should rather override the {@link RaceColumnListener} operations individually. - * - * @author Alexander Ries (D062114) - * - */ -public interface RaceColumnListenerWithDefaultAction extends RaceColumnListener { - /** - * This implementation of the default action to which all other operations delegate does nothing. Subclasses can - * choose to override this method to provide this as the default behavior for all other methods and then still - * override individual {@link RaceColumnListener} methods, or they can leave the default action empty and just - * override {@link RaceColumnListener} methods. - */ - void defaultAction(); - - @Override - default void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - defaultAction(); - } - - @Override - default void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - defaultAction(); - } - - @Override - default void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { - defaultAction(); - } - - @Override - default void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { - defaultAction(); - } - - @Override - default void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, - boolean firstColumnIsNonDiscardableCarryForward) { - defaultAction(); - } - - @Override - default void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { - defaultAction(); - } - - @Override - default void raceColumnAddedToContainer(RaceColumn raceColumn) { - defaultAction(); - } - - @Override - default void raceColumnRemovedFromContainer(RaceColumn raceColumn) { - defaultAction(); - } - - @Override - default void raceColumnMoved(RaceColumn raceColumn, int newIndex) { - defaultAction(); - } - - @Override - default void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { - defaultAction(); - } - - @Override - default void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { - defaultAction(); - } - - @Override - default void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, - ResultDiscardingRule newDiscardingRule) { - defaultAction(); - } - - @Override - default void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { - defaultAction(); - } - - @Override - default void regattaLogEventAdded(RegattaLogEvent event) { - defaultAction(); - } - - @Override - default boolean isTransient() { - return false; - } -} +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; +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.leaderboard.ResultDiscardingRule; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.tracking.TrackedRace; + +/** + * Delegates all listener operations to a default action which is implemented here to do nothing. This way, subclasses + * can easily provide a default action to be executed for all callback methods except for maybe a few which then need to + * be explicitly overridden. If not most methods need to perform the same action then instead of implementing a default + * action subclasses should rather override the {@link RaceColumnListener} operations individually. + * + * @author Alexander Ries (D062114) + * + */ +public interface RaceColumnListenerWithDefaultAction extends RaceColumnListener { + /** + * This implementation of the default action to which all other operations delegate does nothing. Subclasses can + * choose to override this method to provide this as the default behavior for all other methods and then still + * override individual {@link RaceColumnListener} methods, or they can leave the default action empty and just + * override {@link RaceColumnListener} methods. + */ + void defaultAction(); + + @Override + default void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + defaultAction(); + } + + @Override + default void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + defaultAction(); + } + + @Override + default void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { + defaultAction(); + } + + @Override + default void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + defaultAction(); + } + + @Override + default void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { + defaultAction(); + } + + @Override + default void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, + boolean firstColumnIsNonDiscardableCarryForward) { + defaultAction(); + } + + @Override + default void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { + defaultAction(); + } + + @Override + default void raceColumnAddedToContainer(RaceColumn raceColumn) { + defaultAction(); + } + + @Override + default void raceColumnRemovedFromContainer(RaceColumn raceColumn) { + defaultAction(); + } + + @Override + default void raceColumnMoved(RaceColumn raceColumn, int newIndex) { + defaultAction(); + } + + @Override + default void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { + defaultAction(); + } + + @Override + default void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { + defaultAction(); + } + + @Override + default void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, + ResultDiscardingRule newDiscardingRule) { + defaultAction(); + } + + @Override + default void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { + defaultAction(); + } + + @Override + default void regattaLogEventAdded(RegattaLogEvent event) { + defaultAction(); + } + + @Override + default boolean isTransient() { + return false; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 6c39ac72caa..a665090fd58 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -182,7 +182,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene RankingMetricConstructor rankingMetricConstructor) { this(raceLogStore, regattaLogStore, name, boatClass, startDate, endDate, Collections .singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, - /* isMedal */false, Collections + /* isMedal */false, /* isFleetsCanRunInParallel */ true, Collections .singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList(), trackedRegattaRegistry)), /* persistent */false, scoringScheme, id, courseArea, /* useStartTimeInference */true, rankingMetricConstructor); @@ -429,6 +429,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene raceColumnListeners.notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + raceColumnListeners.notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { raceColumnListeners.notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java index 64b283d2476..80cd55b1260 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SeriesImpl.java @@ -36,6 +36,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe private final List fleetsInAscendingOrder; private final List raceColumns; private boolean isMedal; + private boolean isFleetsCanRunInParallel; private Regatta regatta; private final RaceColumnListeners raceColumnListeners; private ThresholdBasedResultDiscardingRule resultDiscardingRule; @@ -71,7 +72,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe * this column's series {@link Regatta}, respectively. If null, the re-association won't be * carried out. */ - public SeriesImpl(String name, boolean isMedal, Iterable fleets, Iterable raceColumnNames, + public SeriesImpl(String name, boolean isMedal, boolean isFleetsCanRunInParallel, Iterable fleets, Iterable raceColumnNames, TrackedRegattaRegistry trackedRegattaRegistry) { super(name); if (fleets == null || Util.isEmpty(fleets)) { @@ -86,6 +87,7 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe Collections.sort(fleetsInAscendingOrder); this.raceColumns = new ArrayList(); this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; this.raceColumnListeners = new RaceColumnListeners(); for (String raceColumnName : raceColumnNames) { addRaceColumn(raceColumnName, trackedRegattaRegistry); @@ -284,6 +286,22 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe } } + @Override + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + @Override + public void setIsFleetsCanRunInParallel(boolean isFleetsCanRunInParallel) { + boolean oldIsFleetsCanRunInParallel = this.isFleetsCanRunInParallel; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + if (oldIsFleetsCanRunInParallel != isFleetsCanRunInParallel) { + for (RaceColumn raceColumn : getRaceColumns()) { + raceColumnListeners.notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, isFleetsCanRunInParallel); + } + } + } + @Override public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { raceColumnListeners.notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); @@ -299,6 +317,10 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe raceColumnListeners.notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + raceColumnListeners.notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { raceColumnListeners.notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); @@ -434,10 +456,4 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe public boolean hasSplitFleetContiguousScoring() { return hasSplitFleetContiguousScoring; } - - @Override - public boolean isFleetsRunInParallel() { - // TODO This is a default implementation so far, allowing for parallel fleet races to not restrict generality. This shall become a configurable series property. See bug 3532 - return true; - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index c55d25d5ba6..76a200f12f6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -1,1943 +1,1948 @@ -package com.sap.sailing.domain.leaderboard.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Leg; -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.Waypoint; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LeaderboardType; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.ManeuverType; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.NoWindError; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.dto.BasicRaceDTO; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; -import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; -import com.sap.sailing.domain.common.dto.LegEntryDTO; -import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; -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.leaderboard.caching.LeaderboardDTOCache; -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; -import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; -import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; -import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; -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.MarkPassingManeuver; -import com.sap.sailing.domain.tracking.RaceChangeListener; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; -import com.sap.sailing.domain.tracking.WindPositionMode; -import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; -import com.sap.sailing.util.impl.RaceColumnListeners; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; -import com.sap.sse.util.impl.ThreadFactoryWithPriority; - -/** - * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events - * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes - * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful - * candidates. - * - * @author Axel Uhl (d043530) - * - */ -public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { - private static final long serialVersionUID = 330156778603279333L; - - private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); - - static final Double DOUBLE_0 = new Double(0); - - private final SettableScoreCorrection scoreCorrection; - - private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; - - /** - * 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 displayNames; - - /** the display name of the leaderboard */ - private String displayName; - - /** - * 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 carriedPoints; - - private final RaceColumnListeners raceColumnListeners; - - /** - * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access - * is controlled by the {@link #suppressedCompetitorsLock} lock. - */ - private final Set suppressedCompetitors; - private final NamedReentrantReadWriteLock suppressedCompetitorsLock; - - private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; - - /** - * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} - * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't - * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, - * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). - */ - private transient Executor raceDetailsExecutor; - - /** - * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. - */ - private transient Set cacheInvalidationListeners; - - private transient ThreadPoolExecutor executor; - - private transient LiveLeaderboardUpdater liveLeaderboardUpdater; - - private transient LeaderboardDTOCache leaderboardDTOCache; - - /** - * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. - * - * @author Axel Uhl (d043530) - * - */ - class EntryImpl implements Entry { - private final Callable trackedRankProvider; - private final Double netPoints; - private final Callable netPointsUncorrectedProvider; - private final boolean isNetPointsCorrected; - private final Double totalPoints; - private final MaxPointsReason maxPointsReason; - private final boolean discarded; - private final Fleet fleet; - - private EntryImpl(Callable trackedRankProvider, Double netPoints, - Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, - Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { - super(); - this.trackedRankProvider = trackedRankProvider; - this.netPoints = netPoints; - this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; - this.isNetPointsCorrected = isNetPointsCorrected; - this.totalPoints = totalPoints; - this.maxPointsReason = maxPointsReason; - this.discarded = discarded; - this.fleet = fleet; - } - @Override - public int getTrackedRank() { - try { - return trackedRankProvider.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - @Override - public Double getNetPoints() { - return netPoints; - } - @Override - public boolean isNetPointsCorrected() { - return isNetPointsCorrected; - } - @Override - public Double getTotalPoints() { - return totalPoints; - } - @Override - public MaxPointsReason getMaxPointsReason() { - return maxPointsReason; - } - @Override - public boolean isDiscarded() { - return discarded; - } - @Override - public Fleet getFleet() { - return fleet; - } - @Override - public Double getNetPointsUncorrected() { - try { - return netPointsUncorrectedProvider.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed - * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have - * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list - * would be cached, but that is again difficult because we would have to monitor all changes in all dependent - * leaderboards and columns and tracked races properly. - *

- * - * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} - * competitors list is to determine their number which in turn is only required for high-point scoring systems and - * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes - * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that - * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the - * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be - * constructed each time the number shall be computed. - * - * @author Axel Uhl (D043530) - * - */ - public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { - private int numberOfCompetitors = -1; - - @Override - public int getNumberOfCompetitorsInLeaderboard() { - if (numberOfCompetitors == -1) { - numberOfCompetitors = Util.size(getCompetitors()); - } - return numberOfCompetitors; - } - } - - /** - * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked - * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, - * calculations may start or resume, making it necessary to clear the cache. - * - * @author Axel Uhl (D043530) - * - */ - private class CacheInvalidationListener extends AbstractRaceChangeListener { - private final TrackedRace trackedRace; - private final Competitor competitor; - - public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { - this.trackedRace = trackedRace; - this.competitor = competitor; - } - - public TrackedRace getTrackedRace() { - return trackedRace; - } - - public void removeFromTrackedRace() { - trackedRace.removeListener(this); - } - - private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { - synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); - removeFromTrackedRace(); - } - } - - @Override - protected void defaultAction() { - invalidateCacheAndRemoveThisListenerFromTrackedRace(); - } - } - - private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { - @Override - public String generateRandomUUID() { - return UUID.randomUUID().toString(); - } - } - - public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { - this.carriedPoints = new HashMap(); - this.scoreCorrection = createScoreCorrection(); - this.displayNames = new HashMap(); - this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; - this.suppressedCompetitors = new HashSet(); - this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); - this.raceColumnListeners = new RaceColumnListeners(); - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); - initTransientFields(); - } - - /** - * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override - * this method to create a more specific type of score correction. This implementation produces an object of type - * {@link ScoreCorrectionImpl}. - */ - protected SettableScoreCorrection createScoreCorrection() { - return new ScoreCorrectionImpl(this); - } - - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - initTransientFields(); - } - - private void initTransientFields() { - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); - this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - this.cacheInvalidationListeners = new HashSet(); - // When many updates are triggered in a short period of time by a single thread, ensure that the single thread - // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one - // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. - final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); - executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, - /* maximumPoolSize */ THREAD_POOL_SIZE, - /* keepAliveTime */ 60, TimeUnit.SECONDS, - /* workQueue */ new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); - } - - @Override - public void destroy() { - for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { - cacheInvalidationListener.removeFromTrackedRace(); - } - } - - @Override - public SettableScoreCorrection getScoreCorrection() { - return scoreCorrection; - } - - @Override - public String getDisplayName(Competitor competitor) { - return displayNames.get(competitor); - } - - @Override - public String getDisplayName() { - return displayName; - } - - @Override - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - @Override - public ResultDiscardingRule getResultDiscardingRule() { - return crossLeaderboardResultDiscardingRule; - } - - @Override - public void setCarriedPoints(Competitor competitor, double carriedPoints) { - Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); - if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); - } - } - - @Override - public double getCarriedPoints(Competitor competitor) { - Double result = carriedPoints.get(competitor); - return result == null ? 0 : result; - } - - @Override - public Map getCompetitorsForWhichThereAreCarriedPoints() { - return Collections.unmodifiableMap(carriedPoints); - } - - @Override - public void unsetCarriedPoints(Competitor competitor) { - Double oldCarriedPoints = carriedPoints.remove(competitor); - if (oldCarriedPoints != null) { - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); - } - } - - @Override - public boolean hasCarriedPoints() { - return !carriedPoints.isEmpty(); - } - - @Override - public boolean hasCarriedPoints(Competitor competitor) { - return carriedPoints.containsKey(competitor); - } - - @Override - public void setDisplayName(Competitor competitor, String displayName) { - String oldDisplayName = displayNames.get(competitor); - displayNames.put(competitor, displayName); - if (!Util.equalsWithNull(oldDisplayName, displayName)) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); - } - } - - @Override - public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { - ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); - this.crossLeaderboardResultDiscardingRule = discardingRule; - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); - } - - @Override - public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { - return getScoreCorrection().getCorrectedScore( - new Callable() { - public Integer call() throws NoWindException { - return getTrackedRank(competitor, raceColumn, timePoint); - } - }, competitor, - raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); - } - - @Override - public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); - } - - @Override - public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); - } - - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); - } - - /** - * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). - * - * @param discardedRaceColumns - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. - */ - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, - final Set discardedRaceColumns) { - return !raceColumn.isMedalRace() - && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() - && discardedRaceColumns.contains(raceColumn); - } - - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); - } - - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - } - - /** - * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). - * - * @param discardedRaceColumns - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. - */ - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { - Double result; - if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { - result = 0.0; - } else { - final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints == null) { - result = null; - } else { - result = raceColumn.getFactor() * netPoints; - } - } - return result; - } - - @Override - public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { - return getTotalPoints(competitor, getRaceColumns(), timePoint); - } - - @Override - public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, - TimePoint timePoint) { - // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on - boolean needToResetScoreUponNextNonEmptyEntry = false; - double result = getCarriedPoints(competitor); - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - for (RaceColumn raceColumn : raceColumnsToConsider) { - if (raceColumn.isStartsWithZeroScore()) { - needToResetScoreUponNextNonEmptyEntry = true; - } - if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { - final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - if (totalPoints != null) { - if (needToResetScoreUponNextNonEmptyEntry) { - result = 0; - needToResetScoreUponNextNonEmptyEntry = false; - } - result += totalPoints; - } - } - } - return result; - } - - /** - * All competitors with non-null net points are added to the result which is then sorted by net points in ascending - * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. - */ - @Override - public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - final Map> netPointsAndFleet = new HashMap>(); - for (Competitor competitor : getCompetitors()) { - Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints != null) { - netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); - } - } - List result = new ArrayList(netPointsAndFleet.keySet()); - Collections.sort(result, new Comparator() { - @Override - public int compare(Competitor o1, Competitor o2) { - int comparisonResult; - if (o1 == o2) { - comparisonResult = 0; - } else { - if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { - // only check fleets if there are more than one and the column is not to be contiguously scored even in case - // of split fleets - final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); - final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); - if (o1Fleet == null) { - if (o2Fleet == null) { - comparisonResult = 0; - } else { - comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 - // has - } - } else { - if (o2Fleet == null) { - comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't - } else { - comparisonResult = o1Fleet.compareTo(o2Fleet); - } - } - } else { - // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets - // the scoring runs contiguously from top to bottom - comparisonResult = 0; - } - if (comparisonResult == 0) { - comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); - } - } - return comparisonResult; - } - }); - return result; - } - - @Override - public List getCompetitorsFromBestToWorst(TimePoint timePoint) { - return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); - } - - private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { - List result = new ArrayList(); - for (Competitor competitor : getCompetitors()) { - result.add(competitor); - } - Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); - return result; - } - - @Override - public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { - List competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); - return competitorsFromBestToWorst.indexOf(competitor) + 1; - } - - protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { - return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); - } - - @Override - public RaceColumn getRaceColumnByName(String columnName) { - RaceColumn result = null; - for (RaceColumn r : getRaceColumns()) { - if (r.getName().equals(columnName)) { - result = r; - break; - } - } - return result; - } - - @Override - public Competitor getCompetitorByName(String competitorName) { - for (Competitor competitor : getAllCompetitors()) { - if (competitor.getName().equals(competitorName)) { - return competitor; - } - } - return null; - } - - @Override - public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - TrackedRace trackedRaceForCompetitorInColumn; - return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && - (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || - ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && - trackedRaceForCompetitorInColumn.hasStarted(timePoint) && - trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); - } - - @Override - public void addRaceColumnListener(RaceColumnListener listener) { - getRaceColumnListeners().addRaceColumnListener(listener); - } - - @Override - public void removeRaceColumnListener(RaceColumnListener listener) { - getRaceColumnListeners().removeRaceColumnListener(listener); - } - - @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - return getEntry(competitor, race, timePoint, discardedRaceColumns); - } - - @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, - Set discardedRaceColumns) throws NoWindException { - Callable trackedRankProvider = new Callable() { - public Integer call() throws NoWindException { - return getTrackedRank(competitor, race, timePoint); - } - }; - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); - boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); - final Double correctedScore = correctedResults.getCorrectedScore(); - return new EntryImpl(trackedRankProvider, correctedScore, new Callable() { - @Override - public Double call() { - return correctedResults.getUncorrectedScore(); - } - }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null - : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, - race.getFleetOfCompetitor(competitor)); - } - - @Override - public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { - Map> result = new LinkedHashMap<>(); - List raceColumnsToConsider = new ArrayList<>(); - for (RaceColumn raceColumn : getRaceColumns()) { - raceColumnsToConsider.add(raceColumn); - result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); - } - return result; - } - - @Override - public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) - throws NoWindException { - final Map> result = new LinkedHashMap<>(); - List raceColumnsToConsider = new ArrayList<>(); - Map>> futures = new HashMap<>(); - for (final RaceColumn raceColumn : getRaceColumns()) { - raceColumnsToConsider.add(raceColumn); - final Iterable finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); - futures.put(raceColumn, executor.submit(new Callable>() { - @Override - public Map call() { - Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); - for (Competitor competitor : getCompetitors()) { - totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); - } - synchronized (result) { - return totalPointsSumPerCompetitorInColumn; - } - } - })); - } - for (RaceColumn raceColumn : getRaceColumns()) { - try { - result.put(raceColumn, futures.get(raceColumn).get()); - } catch (InterruptedException | ExecutionException e) { - if (e.getCause() instanceof NoWindError) { - throw ((NoWindError) e.getCause()).getCause(); - } else { - throw new RuntimeException(e); // no caught exceptions occur in the futures executed - } - } - } - return result; - } - - @Override - public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { - Map, Entry> result = new HashMap, Entry>(); - Map> discardedRaces = new HashMap>(); - for (final RaceColumn raceColumn : getRaceColumns()) { - for (final Competitor competitor : getCompetitors()) { - Callable trackedRankProvider = new Callable() { - @Override - public Integer call() throws Exception { - return getTrackedRank(competitor, raceColumn, timePoint); - } - }; - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); - Set discardedRacesForCompetitor = discardedRaces.get(competitor); - if (discardedRacesForCompetitor == null) { - discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - discardedRaces.put(competitor, discardedRacesForCompetitor); - } - boolean discarded = discardedRacesForCompetitor.contains(raceColumn); - final Double correctedScore = correctedResults.getCorrectedScore(); - Entry entry = new EntryImpl(trackedRankProvider, correctedScore, - new Callable() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, - correctedResults.isCorrected(), - discarded ? DOUBLE_0 : (correctedScore==null?null: - Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), - discarded, raceColumn.getFleetOfCompetitor(competitor)); - result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); - } - } - return result; - } - - @Override - public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); - } - - @Override - public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); - // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; - // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink - // all cache invalidation listeners for said trackedRace - if (!Util.contains(getTrackedRaces(), trackedRace)) { - synchronized (cacheInvalidationListeners) { - for (Iterator cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); - cacheInvalidationListenerIter.hasNext(); ) { - CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); - if (cacheInvalidationListener.getTrackedRace() == trackedRace) { - cacheInvalidationListener.removeFromTrackedRace(); - cacheInvalidationListenerIter.remove(); - } - } - } - } - } - - @Override - public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { - getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); - } - - @Override - public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { - getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); - } - - @Override - public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { - getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); - } - - @Override - public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { - getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); - } - - @Override - public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); - } - - @Override - public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { - getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); - } - - /** - * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. - */ - @Override - public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { - boolean result = true; - for (RaceColumn raceColumn : getRaceColumns()) { - if (raceColumn.getName().equals(newRaceColumn.getName())) { - result = false; - break; - } - } - return result; - } - - @Override - public void raceColumnAddedToContainer(RaceColumn raceColumn) { - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); - } - - @Override - public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); - } - - @Override - public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); - } - - @Override - public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); - } - - @Override - public boolean isTransient() { - return false; - } - - /** - * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to - * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been - * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, - * null is returned. The time point computed this way is a good choice for normalizing queries for later time - * points in an attempt to achieve more cache hits.

- * - * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used - * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point - * returned by this method but that column's attached tracked race has finished before the time point returned by this method, - * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. - * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache - * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and - * the column structure changing. - * - * @see TrackedRace#getTimePointOfNewestEvent() - * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() - */ - @Override - public TimePoint getTimePointOfLatestModification() { - TimePoint result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { - result = trackedRace.getTimePointOfNewestEvent(); - } - } - TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); - if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { - result = timePointOfLastScoreCorrection; - } - return result; - } - - @Override - public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { - com.sap.sse.common.Util.Pair result = null; - // TODO should we ensure that competitor participated in all race columns? - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - to = timePoint; - } - com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); - if (result == null || result.getB() == null || - (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { - result = maxSpeed; - } - } - } - } - return result; - } - - @Override - public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { - Speed result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && - timePoint.after(markPassings.last().getTimePoint())) { - result = null; - break; - } - to = timePoint; - } - Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceTraveled != null) { - result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); - } - } - } - } - return result; - } - - @Override - public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { - return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap()); - } - - private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map legTypeCache) throws NoWindException { - Duration result = null; - // TODO should we ensure that competitor participated in all race columns? - outerLoop: - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - trackedRace.getRace().getCourse().lockForRead(); - try { - for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { - TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { - // find out leg type at the time the competitor started the leg - try { - final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); - LegType trackedLegType = legTypeCache.get(trackedLeg); - if (trackedLegType == null) { - final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); - TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); - if (finishTime == null) { - finishTime = timePoint; - } - trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg - legTypeCache.put(trackedLeg, trackedLegType); - } - if (legType == trackedLegType) { - Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); - if (timeSpentOnDownwind != null) { - if (result == null) { - result = timeSpentOnDownwind; - } else { - result = result.plus(timeSpentOnDownwind); - } - } else { - // Although the competitor has started the leg, no value was produced. This - // means that the competitor didn't finish the leg before tracking ended. No useful value - // can be obtained for this competitor anymore. - result = null; - break outerLoop; - } - } - } catch (NoWindException nwe) { - // without wind there is no leg type and hence there is no reasonable value for this: - result = null; - break outerLoop; - } - } - } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); - } - } - } - return result; - } - - @Override - public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { - Duration result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line - if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) - && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() - .getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (trackedRace.getEndOfTracking() != null - && timePoint.after(trackedRace.getEndOfTracking())) { - result = null; // race not finished until end of tracking; no reasonable value can be - // computed for competitor - break; - } else { - to = timePoint; - } - } - Duration timeSpent = from.until(to); - if (result == null) { - result = timeSpent; - } else { - result=result.plus(timeSpent); - } - } - } - } - } - return result; - } - - @Override - public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { - Distance result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - TimePoint startOfRace; - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && - (startOfRace=trackedRace.getStartOfRace()) != null && - !startOfRace.after(timePoint)) { - Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceSailedInRace != null) { - if (result == null) { - result = distanceSailedInRace; - } else { - result = result.add(distanceSailedInRace); - } - } else { - // if competitor has not finished one single race in the whole - // series then we can not return a meaningful value for all - // all races - return null; - } - } - } - return result; - } - - protected RaceColumnListeners getRaceColumnListeners() { - return raceColumnListeners; - } - - @Override - public Iterable getCompetitors() { - final Iterable result; - // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the - // potentially large set of competitors - if (Util.isEmpty(getSuppressedCompetitors())) { - result = getAllCompetitors(); - } else { - final Iterable allCompetitors = getAllCompetitors(); - final Set suppressed = new HashSet<>(); - Util.addAll(getSuppressedCompetitors(), suppressed); - result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); - } - return result; - } - - @Override - public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { - return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); - } - - /** - * return an iterable with a smart iterator that filters out the suppressed elements on demand - */ - protected Iterable getCompetitorIterableSkippingSuppressed(final Iterable allCompetitors, - final Iterable suppressed) { - final Iterable result; - result = new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { - private final Iterator allIter = allCompetitors.iterator(); - private Competitor next = advance(); - - private Competitor advance() { - next = null; - while (allIter.hasNext() && next == null) { - next = allIter.next(); - if (Util.contains(suppressed, next)) { - next = null; - } - } - return next; - } - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public Competitor next() { - if (next == null) { - throw new NoSuchElementException(); - } - final Competitor result = next; - advance(); - return result; - } - }; - } - }; - return result; - } - - @Override - public Iterable getSuppressedCompetitors() { - LockUtil.lockForRead(suppressedCompetitorsLock); - try { - return new HashSet(suppressedCompetitors); - } finally { - LockUtil.unlockAfterRead(suppressedCompetitorsLock); - } - } - - @Override - public void setSuppressed(Competitor competitor, boolean suppressed) { - LockUtil.lockForWrite(suppressedCompetitorsLock); - try { - if (suppressed) { - suppressedCompetitors.add(competitor); - } else { - suppressedCompetitors.remove(competitor); - } - } finally { - LockUtil.unlockAfterWrite(suppressedCompetitorsLock); - } - getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); - } - - @Override - public TimePoint getNowMinusDelay() { - final TimePoint now = MillisecondsTimePoint.now(); - final Long delayToLiveInMillis = getDelayToLiveInMillis(); - TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); - return timePoint; - } - - @Override - public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { - getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); - if (event instanceof InvalidatesLeaderboardCache) { - // make sure to invalidate the cache as this event indicates that - // it changes values the cache could still hold - if (leaderboardDTOCache != null) { - leaderboardDTOCache.invalidate(this); - } - } - } - - @Override - public void regattaLogEventAdded(RegattaLogEvent event) { - getRaceColumnListeners().notifyListenersAboutRegattaLogEventAdded(event); - } - - @Override - public LeaderboardDTO computeDTO(final TimePoint timePoint, - final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, - final boolean fillNetPointsUncorrected) - throws NoWindException { - long startOfRequestHandling = System.currentTimeMillis(); - final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); - final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null - : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), - this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), - this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this - .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); - result.type = getLeaderboardType(); - result.competitors = new ArrayList(); - result.name = this.getName(); - result.competitorDisplayNames = new HashMap(); - for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { - result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); - } - // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these - // futures to finish: - Map>> competitorsFromBestToWorstTasks = new HashMap<>(); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; - RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, - isMetaLeaderboardColumn); - if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) { - calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, - trackedRegattaRegistry, baseDomainFactory); - } - for (Fleet fleet : raceColumn.getFleets()) { - RegattaAndRaceIdentifier raceIdentifier = null; - RaceDTO race = null; - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); - if (trackedRace != null) { - raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), - trackedRace.getRace().getName()); - race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); - } - // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created - // above by the result.createEmptyRaceColumn call - result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); - } - FutureTask> task = new FutureTask>( - () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); - executor.execute(task); - competitorsFromBestToWorstTasks.put(raceColumn, task); - } - // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data - for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { - try { - result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept - // null values. - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So - // this helps robustness from a user's perspective. - logger.log( - Level.SEVERE, - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails - + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); - } - } - result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); - result.rows = new HashMap(); - result.hasCarriedPoints = this.hasCarriedPoints(); - if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { - result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) - .getDiscardIndexResultsStartingWithHowManyRaces(); - } else { - result.discardThresholds = null; - } - // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep - // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for - // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg - // details are requested only once and pass them into getLeaderboardEntryDTO - final Map> legRanksCache = new HashMap>(); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - // if details for the column are requested, cache the leg's ranks - if (namesOfRaceColumnsForWhichToLoadLegDetails != null - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - trackedRace.getRace().getCourse().lockForRead(); - try { - for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { - legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); - } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); - } - } - } - } - } - for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { - CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); - LeaderboardRowDTO row = new LeaderboardRowDTO(); - row.competitor = competitorDTO; - row.fieldsByRaceColumnName = new HashMap(); - row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; - row.totalPoints = this.getTotalPoints(competitor, timePoint); - if (addOverallDetails) { - addOverallDetailsToRow(timePoint, competitor, row); - } - result.competitors.add(competitorDTO); - Map> futuresForColumnName = new HashMap>(); - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - RunnableFuture future = new FutureTask(() -> { - Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); - return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails != null - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn - .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, - fillNetPointsUncorrected, cache); - }); - executor.execute(future); - futuresForColumnName.put(raceColumn.getName(), future); - } - for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { - try { - row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept - // null values. - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So - // this helps robustness from a user's perspective. - logger.log( - Level.SEVERE, - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails - + "): exception during computing leaderboard entry for competitor " - + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() - + ". Leaving empty.", e); - } - } - result.rows.put(competitorDTO, row); - String displayName = this.getDisplayName(competitor); - if (displayName != null) { - result.competitorDisplayNames.put(competitorDTO, displayName); - } - } - logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " - + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " - + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); - return result; - } - - private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO, - TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) { - for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName(); - String raceName = trackedRace.getRace().getName(); - RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); - columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace))); - } - } - } - } - - private void addOverallDetailsToRow(final TimePoint timePoint, - final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { - final com.sap.sse.common.Util.Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); - if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { - row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); - row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); - } - Map legTypeCache = new HashMap<>(); - final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); - row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); - final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); - row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); - final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); - row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); - final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); - row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); - final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); - row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); - } - - /** - * @param waitForLatestAnalyses - * if false, this method is allowed to read the maneuver analysis results from a cache that - * may not reflect all data already received; otherwise, the method will always block for the latest - * cache updates to have happened before returning. - * @param fillNetPointsUncorrected - * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather - * expensive, especially when compared to simply retrieving a score correction, and particularly if in a - * larger fleet a number of competitors haven't properly finished the race. This should only be used for - * leaderboard editing where a user needs to see what the uncorrected score was that would be used when - * the correction was removed. - */ - private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, - TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, - Map> legRanksCache, DomainFactory baseDomainFactory, - boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { - LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); - TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); - entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); - entryDTO.netPoints = entry.getNetPoints(); - if (fillNetPointsUncorrected) { - entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); - } - entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); - entryDTO.totalPoints = entry.getTotalPoints(); - entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); - entryDTO.discarded = entry.isDiscarded(); - final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); - if (trackedRace != null) { - Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); - if (track != null) { - entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); - } - if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { - long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); - entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; - } else { - entryDTO.timeSinceLastPositionFixInSeconds = null; - } - } - if (addLegDetails && trackedRace != null) { - try { - final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); - RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, - legRanksCache, rankingInfo, cache); - entryDTO.legDetails = raceDetails.getLegDetails(); - entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null - : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); - entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); - entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null - : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); - entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null - : raceDetails.getAverageSignedCrossTrackError().getMeters(); - entryDTO.calculatedTime = raceDetails.getCorrectedTime(); - entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); - entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); - final TimePoint startOfRace = trackedRace.getStartOfRace(); - if (startOfRace != null) { - Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); - NavigableSet competitorMarkPassings = trackedRace.getMarkPassings(competitor); - trackedRace.lockForRead(competitorMarkPassings); - try { - if (!competitorMarkPassings.isEmpty()) { - final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); - if (firstMarkPassing.getWaypoint() == startWaypoint) { - Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); - entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null - : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); - Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); - entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null - : speedFiveSecondsBeforeStartOfRace.getKnots(); - Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( - competitor, startOfRace); - entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null - : distanceToStartLineAtStartOfRace.getMeters(); - Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); - entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null - : speedAtStartTime.getKnots(); - TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); - entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); - Speed competitorSpeedWhenPassingStart = track == null ? null : track - .getEstimatedSpeed(competitorStartTime); - entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null - : competitorSpeedWhenPassingStart.getKnots(); - try { - entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); - } catch (NoWindException nwe) { - entryDTO.startTack = null; // leave empty in case no wind information is available - } - Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace - .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); - entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null - : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); - } - } - } finally { - trackedRace.unlockAfterRead(competitorMarkPassings); - } - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception - } - } - final Fleet fleet = entry.getFleet(); - entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); - return entryDTO; - } - - /** - * Determines the time point of the last raw fix (with outliers not removed) for competitor in - * trackedRace. If the competitor's track is null or empty, null is returned. - * @param trackedRace must not be null - * @param atOrBefore find the last fix at or before the time point specified - */ - private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { - assert trackedRace != null; - final Date timePointOfLastPositionFix; - GPSFixTrack track = trackedRace.getTrack(competitor); - if (track == null) { - timePointOfLastPositionFix = null; - } else { - GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); - if (lastFix == null) { - timePointOfLastPositionFix = null; - } else { - timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); - } - } - return timePointOfLastPositionFix; - } - - private static class RaceDetails { - private final List legDetails; - private final Distance windwardDistanceToCompetitorFarthestAhead; - private final Distance averageAbsoluteCrossTrackError; - private final Distance averageSignedCrossTrackError; - private final Duration gapToLeaderInOwnTime; - private final Duration correctedTime; - private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - - public RaceDetails(List legDetails, Distance windwardDistanceToCompetitorFarthestAhead, - Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, - Duration gapToLeaderInOwnTime, Duration correctedTime, - Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { - super(); - this.legDetails = legDetails; - this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; - this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; - this.averageSignedCrossTrackError = averageSignedCrossTrackError; - this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; - this.correctedTime = correctedTime; - this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - } - public List getLegDetails() { - return legDetails; - } - public Distance getWindwardDistanceToCompetitorFarthestAhead() { - return windwardDistanceToCompetitorFarthestAhead; - } - public Distance getAverageAbsoluteCrossTrackError() { - return averageAbsoluteCrossTrackError; - } - public Distance getAverageSignedCrossTrackError() { - return averageSignedCrossTrackError; - } - public Duration getGapToLeaderInOwnTime() { - return gapToLeaderInOwnTime; - } - public Duration getCorrectedTime() { - return correctedTime; - } - public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { - return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - } - } - - /** - * If timePoint is after the end of the race's tracking the query will be adjusted to obtain the values - * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this - * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, - * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} - * will be registered with the race which will be triggered for any event received by the race. - * @param waitForLatestAnalyses - * if false, this method is allowed to read the maneuver analysis results from a cache that - * may not reflect all data already received; otherwise, the method will always block for the latest - * cache updates to have happened before returning. - */ - private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { - final RaceDetails raceDetails; - if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { - raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); - } else { - raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); - } - return raceDetails; - } - - private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, - final Competitor competitor, final Map> legRanksCache, - RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { - final com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(trackedRace, competitor); - RunnableFuture raceDetails; - synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetails = raceDetailsAtEndOfTrackingCache.get(key); - if (raceDetails == null) { - raceDetails = new FutureTask(new Callable() { - @Override - public RaceDetails call() throws Exception { - TimePoint end = trackedRace.getEndOfRace(); - if (end == null) { - end = trackedRace.getEndOfTracking(); - } - return calculateRaceDetails(trackedRace, competitor, end, - // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock - /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ - /* waitForLatestAnalyses (maneuver and cross track error) */ false, - legRanksCache, cache, rankingInfo); - } - }); - raceDetailsExecutor.execute(raceDetails); - raceDetailsAtEndOfTrackingCache.put(key, raceDetails); - final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); - trackedRace.addListener(cacheInvalidationListener); - cacheInvalidationListeners.add(cacheInvalidationListener); - } - } - return raceDetails.get(); - } - - private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { - final List legDetails = new ArrayList(); - final Course course = trackedRace.getRace().getCourse(); - course.lockForRead(); // hold back any course re-configurations while looping over the legs - try { - for (Leg leg : course.getLegs()) { - LegEntryDTO legEntry; - // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with - // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change - // immediately. That's why we've acquired a read lock for the course above. - TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { - legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); - } else { - legEntry = null; - } - legDetails.add(legEntry); - } - final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace - .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); - Distance averageAbsoluteCrossTrackError; - try { - averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( - competitor, timePoint, waitForLatestAnalyses, cache); - } catch (NoWindException nwe) { - // without wind information, use null meaning "unknown" - averageAbsoluteCrossTrackError = null; - } - Distance averageSignedCrossTrackError; - try { - averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( - competitor, timePoint, waitForLatestAnalyses, cache); - } catch (NoWindException nwe) { - // without wind information, use null meaning "unknown" - averageSignedCrossTrackError = null; - } - final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); - return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, - trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), - trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), - competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); - } finally { - course.unlockAfterRead(); - } - } - - private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { - LegEntryDTO result; - final Duration time = trackedLeg.getTime(timePoint); - if (trackedLeg == null || time == null) { - result = null; - } else { - result = new LegEntryDTO(); - try { - result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); - } catch (NoWindException nwe) { - result.legType = null; // can't determine leg type without wind data - } - final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); - result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - Distance averageAbsoluteCrossTrackError; - try { - averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); - } catch (NoWindException nwe) { - // leave averageAbsoluteCrossTrackError as null, meaning "unknown" - averageAbsoluteCrossTrackError = null; - } - result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); - Distance averageSignedCrossTrackError; - try { - averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); - } catch (NoWindException nwe) { - // leave averageSignedCrossTrackError as null, meaning "unknown" - averageSignedCrossTrackError = null; - } - result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); - Double speedOverGroundInKnots; - if (trackedLeg.hasFinishedLeg(timePoint)) { - speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - } else { - final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); - speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); - } - result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; - Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); - result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); - Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); - result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); - final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); - result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); - result.timeInMilliseconds = time.asMillis(); - result.finished = trackedLeg.hasFinishedLeg(timePoint); - final TimePoint legFinishTime = trackedLeg.getFinishTime(); - result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), - trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; - // fetch the leg gap in own corrected time from the ranking metric - final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). - getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); - result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); - if (result.gapToLeaderInSeconds != null) { - final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); - if (gapAtEndOfPreviousLeg != null) { - result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); - } - } - LinkedHashMap legRanks = legRanksCache.get(trackedLeg.getLeg()); - if (legRanks != null) { - result.rank = legRanks.get(trackedLeg.getCompetitor()); - } else { - result.rank = trackedLeg.getRank(timePoint, cache); - } - result.started = trackedLeg.hasStartedLeg(timePoint); - Speed velocityMadeGood; - if (trackedLeg.hasFinishedLeg(timePoint)) { - velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); - } else { - velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); - } - result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); - Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); - result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo - .getMeters(); - final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); - if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { - // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg - // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg - List maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() - .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); - if (maneuvers != null) { - result.numberOfManeuvers = new HashMap(); - result.numberOfManeuvers.put(ManeuverType.TACK, 0); - result.numberOfManeuvers.put(ManeuverType.JIBE, 0); - result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); - Map totalManeuverLossInMeters = new HashMap(); - totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); - totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); - totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); - TimePoint startOfLeg = trackedLeg.getStartTime(); - for (Maneuver maneuver : maneuvers) { - // don't count maneuvers that were in previous legs - switch (maneuver.getType()) { - case TACK: - case JIBE: - case PENALTY_CIRCLE: - if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || - maneuver.getTimePoint().before(legFinishTime))) { - if (maneuver.getManeuverLoss() != null) { - result.numberOfManeuvers.put(maneuver.getType(), - result.numberOfManeuvers.get(maneuver.getType()) + 1); - totalManeuverLossInMeters.put(maneuver.getType(), - totalManeuverLossInMeters.get(maneuver.getType()) - + maneuver.getManeuverLoss().getMeters()); - } - } - break; - case MARK_PASSING: - // analyze all mark passings, not only those after this leg's start, to catch the mark passing - // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg - MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; - if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { - result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); - } - break; - default: - /* Do nothing here. - * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. - */ - } - } - result.averageManeuverLossInMeters = new HashMap(); - for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, - ManeuverType.PENALTY_CIRCLE }) { - if (result.numberOfManeuvers.get(maneuverType) != 0) { - result.averageManeuverLossInMeters.put( - maneuverType, - totalManeuverLossInMeters.get(maneuverType) - / result.numberOfManeuvers.get(maneuverType)); - } - } - } - } - } - return result; - } - - private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { - final Duration result; - final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); - // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap - // at the end of the previous leg - final TimePoint timePoint = trackedLeg.getStartTime(); - final TrackedLegOfCompetitor tloc; - if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { - tloc = trackedLeg; - } else { - tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) - .getTrackedLeg(trackedLeg.getCompetitor()); - } - result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); - return result; - } - - private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, - boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { - LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, - baseDomainFactory); - return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); - } - - private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, - DomainFactory baseDomainFactory) { - LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; - if (result == null) { - synchronized (this) { - result = this.liveLeaderboardUpdater; - if (result == null) { - this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); - result = this.liveLeaderboardUpdater; - } - } - } - return result; - } - - private LeaderboardDTOCache getLeaderboardDTOCache() { - LeaderboardDTOCache result = this.leaderboardDTOCache; - if (result == null) { - synchronized (this) { - result = this.leaderboardDTOCache; - if (result == null) { - // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes - // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a - // single fix was updated is too expensive if users use the replay feature while a race is still running. - // Therefore, using waitForLatestAnalyses==false seems appropriate here. - this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); - result = this.leaderboardDTOCache; - } - } - } - return result; - } - - @Override - public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, - Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, - InterruptedException, ExecutionException { - LeaderboardDTO result = null; - if (timePoint == null) { - // timePoint==null means live mode; however, if we're after the end of all races and after all score - // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map - // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly - // when the tracked race associations or score corrections or tracked race contents changes: - final TimePoint nowMinusDelay = this.getNowMinusDelay(); - final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); - if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { - // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode - // and pull the result from the regular leaderboard cache: - timePoint = timePointOfLatestModification; - } else { - // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: - timePoint = null; - result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); - } - } - if (timePoint != null) { - if (fillNetPointsUncorrected) { - // explicitly filling the uncorrected net points requires uncached recalculation - result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, - trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); - } else { - // in replay we'd like up-to-date results; they are still cached - // which is OK because the cache is invalidated whenever any of the tracked races attached to the - // leaderboard changes. - result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, - trackedRegattaRegistry); - } - } - return result; - } - - public String toString() { - return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); - } - - @Override - public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { - return new NumberOfCompetitorsFetcherImpl(); - } - - protected abstract LeaderboardType getLeaderboardType(); - -} +package com.sap.sailing.domain.leaderboard.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Leg; +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.Waypoint; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.NoWindError; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.dto.BasicRaceDTO; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; +import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; +import com.sap.sailing.domain.common.dto.LegEntryDTO; +import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; +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.leaderboard.caching.LeaderboardDTOCache; +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; +import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; +import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; +import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; +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.MarkPassingManeuver; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; +import com.sap.sailing.domain.tracking.WindPositionMode; +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; +import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; +import com.sap.sse.util.impl.ThreadFactoryWithPriority; + +/** + * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events + * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes + * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful + * candidates. + * + * @author Axel Uhl (d043530) + * + */ +public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { + private static final long serialVersionUID = 330156778603279333L; + + private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); + + static final Double DOUBLE_0 = new Double(0); + + private final SettableScoreCorrection scoreCorrection; + + private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; + + /** + * 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 displayNames; + + /** the display name of the leaderboard */ + private String displayName; + + /** + * 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 carriedPoints; + + private final RaceColumnListeners raceColumnListeners; + + /** + * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access + * is controlled by the {@link #suppressedCompetitorsLock} lock. + */ + private final Set suppressedCompetitors; + private final NamedReentrantReadWriteLock suppressedCompetitorsLock; + + private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; + + /** + * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} + * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't + * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, + * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). + */ + private transient Executor raceDetailsExecutor; + + /** + * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. + */ + private transient Set cacheInvalidationListeners; + + private transient ThreadPoolExecutor executor; + + private transient LiveLeaderboardUpdater liveLeaderboardUpdater; + + private transient LeaderboardDTOCache leaderboardDTOCache; + + /** + * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. + * + * @author Axel Uhl (d043530) + * + */ + class EntryImpl implements Entry { + private final Callable trackedRankProvider; + private final Double netPoints; + private final Callable netPointsUncorrectedProvider; + private final boolean isNetPointsCorrected; + private final Double totalPoints; + private final MaxPointsReason maxPointsReason; + private final boolean discarded; + private final Fleet fleet; + + private EntryImpl(Callable trackedRankProvider, Double netPoints, + Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, + Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { + super(); + this.trackedRankProvider = trackedRankProvider; + this.netPoints = netPoints; + this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; + this.isNetPointsCorrected = isNetPointsCorrected; + this.totalPoints = totalPoints; + this.maxPointsReason = maxPointsReason; + this.discarded = discarded; + this.fleet = fleet; + } + @Override + public int getTrackedRank() { + try { + return trackedRankProvider.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override + public Double getNetPoints() { + return netPoints; + } + @Override + public boolean isNetPointsCorrected() { + return isNetPointsCorrected; + } + @Override + public Double getTotalPoints() { + return totalPoints; + } + @Override + public MaxPointsReason getMaxPointsReason() { + return maxPointsReason; + } + @Override + public boolean isDiscarded() { + return discarded; + } + @Override + public Fleet getFleet() { + return fleet; + } + @Override + public Double getNetPointsUncorrected() { + try { + return netPointsUncorrectedProvider.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed + * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have + * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list + * would be cached, but that is again difficult because we would have to monitor all changes in all dependent + * leaderboards and columns and tracked races properly. + *

+ * + * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} + * competitors list is to determine their number which in turn is only required for high-point scoring systems and + * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes + * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that + * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the + * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be + * constructed each time the number shall be computed. + * + * @author Axel Uhl (D043530) + * + */ + public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { + private int numberOfCompetitors = -1; + + @Override + public int getNumberOfCompetitorsInLeaderboard() { + if (numberOfCompetitors == -1) { + numberOfCompetitors = Util.size(getCompetitors()); + } + return numberOfCompetitors; + } + } + + /** + * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked + * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, + * calculations may start or resume, making it necessary to clear the cache. + * + * @author Axel Uhl (D043530) + * + */ + private class CacheInvalidationListener extends AbstractRaceChangeListener { + private final TrackedRace trackedRace; + private final Competitor competitor; + + public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { + this.trackedRace = trackedRace; + this.competitor = competitor; + } + + public TrackedRace getTrackedRace() { + return trackedRace; + } + + public void removeFromTrackedRace() { + trackedRace.removeListener(this); + } + + private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { + synchronized (raceDetailsAtEndOfTrackingCache) { + raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); + removeFromTrackedRace(); + } + } + + @Override + protected void defaultAction() { + invalidateCacheAndRemoveThisListenerFromTrackedRace(); + } + } + + private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { + @Override + public String generateRandomUUID() { + return UUID.randomUUID().toString(); + } + } + + public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { + this.carriedPoints = new HashMap(); + this.scoreCorrection = createScoreCorrection(); + this.displayNames = new HashMap(); + this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; + this.suppressedCompetitors = new HashSet(); + this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); + this.raceColumnListeners = new RaceColumnListeners(); + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + initTransientFields(); + } + + /** + * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override + * this method to create a more specific type of score correction. This implementation produces an object of type + * {@link ScoreCorrectionImpl}. + */ + protected SettableScoreCorrection createScoreCorrection() { + return new ScoreCorrectionImpl(this); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + initTransientFields(); + } + + private void initTransientFields() { + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + this.cacheInvalidationListeners = new HashSet(); + // When many updates are triggered in a short period of time by a single thread, ensure that the single thread + // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one + // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. + final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); + executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, + /* maximumPoolSize */ THREAD_POOL_SIZE, + /* keepAliveTime */ 60, TimeUnit.SECONDS, + /* workQueue */ new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); + } + + @Override + public void destroy() { + for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { + cacheInvalidationListener.removeFromTrackedRace(); + } + } + + @Override + public SettableScoreCorrection getScoreCorrection() { + return scoreCorrection; + } + + @Override + public String getDisplayName(Competitor competitor) { + return displayNames.get(competitor); + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Override + public ResultDiscardingRule getResultDiscardingRule() { + return crossLeaderboardResultDiscardingRule; + } + + @Override + public void setCarriedPoints(Competitor competitor, double carriedPoints) { + Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); + if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); + } + } + + @Override + public double getCarriedPoints(Competitor competitor) { + Double result = carriedPoints.get(competitor); + return result == null ? 0 : result; + } + + @Override + public Map getCompetitorsForWhichThereAreCarriedPoints() { + return Collections.unmodifiableMap(carriedPoints); + } + + @Override + public void unsetCarriedPoints(Competitor competitor) { + Double oldCarriedPoints = carriedPoints.remove(competitor); + if (oldCarriedPoints != null) { + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); + } + } + + @Override + public boolean hasCarriedPoints() { + return !carriedPoints.isEmpty(); + } + + @Override + public boolean hasCarriedPoints(Competitor competitor) { + return carriedPoints.containsKey(competitor); + } + + @Override + public void setDisplayName(Competitor competitor, String displayName) { + String oldDisplayName = displayNames.get(competitor); + displayNames.put(competitor, displayName); + if (!Util.equalsWithNull(oldDisplayName, displayName)) { + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + } + } + + @Override + public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { + ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); + this.crossLeaderboardResultDiscardingRule = discardingRule; + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); + } + + @Override + public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { + return getScoreCorrection().getCorrectedScore( + new Callable() { + public Integer call() throws NoWindException { + return getTrackedRank(competitor, raceColumn, timePoint); + } + }, competitor, + raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); + } + + @Override + public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); + } + + @Override + public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); + } + + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, + Iterable raceColumnsToConsider, TimePoint timePoint) { + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); + } + + /** + * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when total points are to be computed for more than one column for the same + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done + * once and not again for each column (which would lead to quadratic effort). + * + * @param discardedRaceColumns + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. + */ + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, + final Set discardedRaceColumns) { + return !raceColumn.isMedalRace() + && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() + && discardedRaceColumns.contains(raceColumn); + } + + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); + } + + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, + Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + } + + /** + * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when total points are to be computed for more than one column for the same + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done + * once and not again for each column (which would lead to quadratic effort). + * + * @param discardedRaceColumns + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. + */ + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { + Double result; + if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { + result = 0.0; + } else { + final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); + if (netPoints == null) { + result = null; + } else { + result = raceColumn.getFactor() * netPoints; + } + } + return result; + } + + @Override + public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { + return getTotalPoints(competitor, getRaceColumns(), timePoint); + } + + @Override + public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, + TimePoint timePoint) { + // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on + boolean needToResetScoreUponNextNonEmptyEntry = false; + double result = getCarriedPoints(competitor); + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + for (RaceColumn raceColumn : raceColumnsToConsider) { + if (raceColumn.isStartsWithZeroScore()) { + needToResetScoreUponNextNonEmptyEntry = true; + } + if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { + final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + if (totalPoints != null) { + if (needToResetScoreUponNextNonEmptyEntry) { + result = 0; + needToResetScoreUponNextNonEmptyEntry = false; + } + result += totalPoints; + } + } + } + return result; + } + + /** + * All competitors with non-null net points are added to the result which is then sorted by net points in ascending + * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. + */ + @Override + public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + final Map> netPointsAndFleet = new HashMap>(); + for (Competitor competitor : getCompetitors()) { + Double netPoints = getNetPoints(competitor, raceColumn, timePoint); + if (netPoints != null) { + netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); + } + } + List result = new ArrayList(netPointsAndFleet.keySet()); + Collections.sort(result, new Comparator() { + @Override + public int compare(Competitor o1, Competitor o2) { + int comparisonResult; + if (o1 == o2) { + comparisonResult = 0; + } else { + if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { + // only check fleets if there are more than one and the column is not to be contiguously scored even in case + // of split fleets + final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); + final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); + if (o1Fleet == null) { + if (o2Fleet == null) { + comparisonResult = 0; + } else { + comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 + // has + } + } else { + if (o2Fleet == null) { + comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't + } else { + comparisonResult = o1Fleet.compareTo(o2Fleet); + } + } + } else { + // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets + // the scoring runs contiguously from top to bottom + comparisonResult = 0; + } + if (comparisonResult == 0) { + comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( + netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); + } + } + return comparisonResult; + } + }); + return result; + } + + @Override + public List getCompetitorsFromBestToWorst(TimePoint timePoint) { + return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); + } + + private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { + List result = new ArrayList(); + for (Competitor competitor : getCompetitors()) { + result.add(competitor); + } + Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); + return result; + } + + @Override + public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { + List competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); + return competitorsFromBestToWorst.indexOf(competitor) + 1; + } + + protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { + return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); + } + + @Override + public RaceColumn getRaceColumnByName(String columnName) { + RaceColumn result = null; + for (RaceColumn r : getRaceColumns()) { + if (r.getName().equals(columnName)) { + result = r; + break; + } + } + return result; + } + + @Override + public Competitor getCompetitorByName(String competitorName) { + for (Competitor competitor : getAllCompetitors()) { + if (competitor.getName().equals(competitorName)) { + return competitor; + } + } + return null; + } + + @Override + public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + TrackedRace trackedRaceForCompetitorInColumn; + return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && + (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || + ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && + trackedRaceForCompetitorInColumn.hasStarted(timePoint) && + trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); + } + + @Override + public void addRaceColumnListener(RaceColumnListener listener) { + getRaceColumnListeners().addRaceColumnListener(listener); + } + + @Override + public void removeRaceColumnListener(RaceColumnListener listener) { + getRaceColumnListeners().removeRaceColumnListener(listener); + } + + @Override + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + return getEntry(competitor, race, timePoint, discardedRaceColumns); + } + + @Override + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, + Set discardedRaceColumns) throws NoWindException { + Callable trackedRankProvider = new Callable() { + public Integer call() throws NoWindException { + return getTrackedRank(competitor, race, timePoint); + } + }; + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); + final Double correctedScore = correctedResults.getCorrectedScore(); + return new EntryImpl(trackedRankProvider, correctedScore, new Callable() { + @Override + public Double call() { + return correctedResults.getUncorrectedScore(); + } + }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null + : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, + race.getFleetOfCompetitor(competitor)); + } + + @Override + public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { + Map> result = new LinkedHashMap<>(); + List raceColumnsToConsider = new ArrayList<>(); + for (RaceColumn raceColumn : getRaceColumns()) { + raceColumnsToConsider.add(raceColumn); + result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); + } + return result; + } + + @Override + public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) + throws NoWindException { + final Map> result = new LinkedHashMap<>(); + List raceColumnsToConsider = new ArrayList<>(); + Map>> futures = new HashMap<>(); + for (final RaceColumn raceColumn : getRaceColumns()) { + raceColumnsToConsider.add(raceColumn); + final Iterable finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); + futures.put(raceColumn, executor.submit(new Callable>() { + @Override + public Map call() { + Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); + for (Competitor competitor : getCompetitors()) { + totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); + } + synchronized (result) { + return totalPointsSumPerCompetitorInColumn; + } + } + })); + } + for (RaceColumn raceColumn : getRaceColumns()) { + try { + result.put(raceColumn, futures.get(raceColumn).get()); + } catch (InterruptedException | ExecutionException e) { + if (e.getCause() instanceof NoWindError) { + throw ((NoWindError) e.getCause()).getCause(); + } else { + throw new RuntimeException(e); // no caught exceptions occur in the futures executed + } + } + } + return result; + } + + @Override + public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { + Map, Entry> result = new HashMap, Entry>(); + Map> discardedRaces = new HashMap>(); + for (final RaceColumn raceColumn : getRaceColumns()) { + for (final Competitor competitor : getCompetitors()) { + Callable trackedRankProvider = new Callable() { + @Override + public Integer call() throws Exception { + return getTrackedRank(competitor, raceColumn, timePoint); + } + }; + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + Set discardedRacesForCompetitor = discardedRaces.get(competitor); + if (discardedRacesForCompetitor == null) { + discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + discardedRaces.put(competitor, discardedRacesForCompetitor); + } + boolean discarded = discardedRacesForCompetitor.contains(raceColumn); + final Double correctedScore = correctedResults.getCorrectedScore(); + Entry entry = new EntryImpl(trackedRankProvider, correctedScore, + new Callable() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, + correctedResults.isCorrected(), + discarded ? DOUBLE_0 : (correctedScore==null?null: + Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), + discarded, raceColumn.getFleetOfCompetitor(competitor)); + result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); + } + } + return result; + } + + @Override + public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); + } + + @Override + public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); + // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; + // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink + // all cache invalidation listeners for said trackedRace + if (!Util.contains(getTrackedRaces(), trackedRace)) { + synchronized (cacheInvalidationListeners) { + for (Iterator cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); + cacheInvalidationListenerIter.hasNext(); ) { + CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); + if (cacheInvalidationListener.getTrackedRace() == trackedRace) { + cacheInvalidationListener.removeFromTrackedRace(); + cacheInvalidationListenerIter.remove(); + } + } + } + } + } + + @Override + public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { + getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); + } + + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + + @Override + public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { + getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); + } + + @Override + public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { + getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); + } + + @Override + public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { + getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); + } + + @Override + public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); + } + + @Override + public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { + getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); + } + + /** + * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. + */ + @Override + public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { + boolean result = true; + for (RaceColumn raceColumn : getRaceColumns()) { + if (raceColumn.getName().equals(newRaceColumn.getName())) { + result = false; + break; + } + } + return result; + } + + @Override + public void raceColumnAddedToContainer(RaceColumn raceColumn) { + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); + } + + @Override + public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); + } + + @Override + public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + } + + @Override + public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); + } + + @Override + public boolean isTransient() { + return false; + } + + /** + * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to + * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been + * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, + * null is returned. The time point computed this way is a good choice for normalizing queries for later time + * points in an attempt to achieve more cache hits.

+ * + * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used + * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point + * returned by this method but that column's attached tracked race has finished before the time point returned by this method, + * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. + * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache + * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and + * the column structure changing. + * + * @see TrackedRace#getTimePointOfNewestEvent() + * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() + */ + @Override + public TimePoint getTimePointOfLatestModification() { + TimePoint result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { + result = trackedRace.getTimePointOfNewestEvent(); + } + } + TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); + if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { + result = timePointOfLastScoreCorrection; + } + return result; + } + + @Override + public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { + com.sap.sse.common.Util.Pair result = null; + // TODO should we ensure that competitor participated in all race columns? + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = markPassings.first().getTimePoint(); + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) && + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + to = timePoint; + } + com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); + if (result == null || result.getB() == null || + (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { + result = maxSpeed; + } + } + } + } + return result; + } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + Speed result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = markPassings.first().getTimePoint(); + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) && + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && + timePoint.after(markPassings.last().getTimePoint())) { + result = null; + break; + } + to = timePoint; + } + Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); + if (distanceTraveled != null) { + result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); + } + } + } + } + return result; + } + + @Override + public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { + return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap()); + } + + private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map legTypeCache) throws NoWindException { + Duration result = null; + // TODO should we ensure that competitor participated in all race columns? + outerLoop: + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + trackedRace.getRace().getCourse().lockForRead(); + try { + for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { + TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { + // find out leg type at the time the competitor started the leg + try { + final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); + LegType trackedLegType = legTypeCache.get(trackedLeg); + if (trackedLegType == null) { + final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); + TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); + if (finishTime == null) { + finishTime = timePoint; + } + trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg + legTypeCache.put(trackedLeg, trackedLegType); + } + if (legType == trackedLegType) { + Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); + if (timeSpentOnDownwind != null) { + if (result == null) { + result = timeSpentOnDownwind; + } else { + result = result.plus(timeSpentOnDownwind); + } + } else { + // Although the competitor has started the leg, no value was produced. This + // means that the competitor didn't finish the leg before tracking ended. No useful value + // can be obtained for this competitor anymore. + result = null; + break outerLoop; + } + } + } catch (NoWindException nwe) { + // without wind there is no leg type and hence there is no reasonable value for this: + result = null; + break outerLoop; + } + } + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + } + } + return result; + } + + @Override + public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { + Duration result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line + if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) + && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() + .getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + if (trackedRace.getEndOfTracking() != null + && timePoint.after(trackedRace.getEndOfTracking())) { + result = null; // race not finished until end of tracking; no reasonable value can be + // computed for competitor + break; + } else { + to = timePoint; + } + } + Duration timeSpent = from.until(to); + if (result == null) { + result = timeSpent; + } else { + result=result.plus(timeSpent); + } + } + } + } + } + return result; + } + + @Override + public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { + Distance result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + TimePoint startOfRace; + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && + (startOfRace=trackedRace.getStartOfRace()) != null && + !startOfRace.after(timePoint)) { + Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); + if (distanceSailedInRace != null) { + if (result == null) { + result = distanceSailedInRace; + } else { + result = result.add(distanceSailedInRace); + } + } else { + // if competitor has not finished one single race in the whole + // series then we can not return a meaningful value for all + // all races + return null; + } + } + } + return result; + } + + protected RaceColumnListeners getRaceColumnListeners() { + return raceColumnListeners; + } + + @Override + public Iterable getCompetitors() { + final Iterable result; + // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the + // potentially large set of competitors + if (Util.isEmpty(getSuppressedCompetitors())) { + result = getAllCompetitors(); + } else { + final Iterable allCompetitors = getAllCompetitors(); + final Set suppressed = new HashSet<>(); + Util.addAll(getSuppressedCompetitors(), suppressed); + result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); + } + return result; + } + + @Override + public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { + return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); + } + + /** + * return an iterable with a smart iterator that filters out the suppressed elements on demand + */ + protected Iterable getCompetitorIterableSkippingSuppressed(final Iterable allCompetitors, + final Iterable suppressed) { + final Iterable result; + result = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator allIter = allCompetitors.iterator(); + private Competitor next = advance(); + + private Competitor advance() { + next = null; + while (allIter.hasNext() && next == null) { + next = allIter.next(); + if (Util.contains(suppressed, next)) { + next = null; + } + } + return next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Competitor next() { + if (next == null) { + throw new NoSuchElementException(); + } + final Competitor result = next; + advance(); + return result; + } + }; + } + }; + return result; + } + + @Override + public Iterable getSuppressedCompetitors() { + LockUtil.lockForRead(suppressedCompetitorsLock); + try { + return new HashSet(suppressedCompetitors); + } finally { + LockUtil.unlockAfterRead(suppressedCompetitorsLock); + } + } + + @Override + public void setSuppressed(Competitor competitor, boolean suppressed) { + LockUtil.lockForWrite(suppressedCompetitorsLock); + try { + if (suppressed) { + suppressedCompetitors.add(competitor); + } else { + suppressedCompetitors.remove(competitor); + } + } finally { + LockUtil.unlockAfterWrite(suppressedCompetitorsLock); + } + getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); + } + + @Override + public TimePoint getNowMinusDelay() { + final TimePoint now = MillisecondsTimePoint.now(); + final Long delayToLiveInMillis = getDelayToLiveInMillis(); + TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); + return timePoint; + } + + @Override + public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { + getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); + if (event instanceof InvalidatesLeaderboardCache) { + // make sure to invalidate the cache as this event indicates that + // it changes values the cache could still hold + if (leaderboardDTOCache != null) { + leaderboardDTOCache.invalidate(this); + } + } + } + + @Override + public void regattaLogEventAdded(RegattaLogEvent event) { + getRaceColumnListeners().notifyListenersAboutRegattaLogEventAdded(event); + } + + @Override + public LeaderboardDTO computeDTO(final TimePoint timePoint, + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, + final boolean fillNetPointsUncorrected) + throws NoWindException { + long startOfRequestHandling = System.currentTimeMillis(); + final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); + final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null + : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), + this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), + this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this + .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); + result.type = getLeaderboardType(); + result.competitors = new ArrayList(); + result.name = this.getName(); + result.competitorDisplayNames = new HashMap(); + for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { + result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); + } + // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these + // futures to finish: + Map>> competitorsFromBestToWorstTasks = new HashMap<>(); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; + RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + isMetaLeaderboardColumn); + if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) { + calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, + trackedRegattaRegistry, baseDomainFactory); + } + for (Fleet fleet : raceColumn.getFleets()) { + RegattaAndRaceIdentifier raceIdentifier = null; + RaceDTO race = null; + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); + if (trackedRace != null) { + raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), + trackedRace.getRace().getName()); + race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); + } + // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created + // above by the result.createEmptyRaceColumn call + result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); + } + FutureTask> task = new FutureTask>( + () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); + executor.execute(task); + competitorsFromBestToWorstTasks.put(raceColumn, task); + } + // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data + for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { + try { + result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept + // null values. + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So + // this helps robustness from a user's perspective. + logger.log( + Level.SEVERE, + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); + } + } + result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); + result.rows = new HashMap(); + result.hasCarriedPoints = this.hasCarriedPoints(); + if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { + result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) + .getDiscardIndexResultsStartingWithHowManyRaces(); + } else { + result.discardThresholds = null; + } + // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep + // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for + // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg + // details are requested only once and pass them into getLeaderboardEntryDTO + final Map> legRanksCache = new HashMap>(); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + // if details for the column are requested, cache the leg's ranks + if (namesOfRaceColumnsForWhichToLoadLegDetails != null + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + trackedRace.getRace().getCourse().lockForRead(); + try { + for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { + legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + } + } + } + } + for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { + CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); + LeaderboardRowDTO row = new LeaderboardRowDTO(); + row.competitor = competitorDTO; + row.fieldsByRaceColumnName = new HashMap(); + row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; + row.totalPoints = this.getTotalPoints(competitor, timePoint); + if (addOverallDetails) { + addOverallDetailsToRow(timePoint, competitor, row); + } + result.competitors.add(competitorDTO); + Map> futuresForColumnName = new HashMap>(); + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + RunnableFuture future = new FutureTask(() -> { + Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); + return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, + namesOfRaceColumnsForWhichToLoadLegDetails != null + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn + .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, + fillNetPointsUncorrected, cache); + }); + executor.execute(future); + futuresForColumnName.put(raceColumn.getName(), future); + } + for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { + try { + row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept + // null values. + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So + // this helps robustness from a user's perspective. + logger.log( + Level.SEVERE, + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + + "): exception during computing leaderboard entry for competitor " + + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() + + ". Leaving empty.", e); + } + } + result.rows.put(competitorDTO, row); + String displayName = this.getDisplayName(competitor); + if (displayName != null) { + result.competitorDisplayNames.put(competitorDTO, displayName); + } + } + logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " + + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " + + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); + return result; + } + + private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO, + TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) { + for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName(); + String raceName = trackedRace.getRace().getName(); + RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); + columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace))); + } + } + } + } + + private void addOverallDetailsToRow(final TimePoint timePoint, + final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { + final com.sap.sse.common.Util.Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); + if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { + row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); + row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); + } + Map legTypeCache = new HashMap<>(); + final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); + row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); + final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); + row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); + final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); + row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); + final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); + row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); + final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); + row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); + } + + /** + * @param waitForLatestAnalyses + * if false, this method is allowed to read the maneuver analysis results from a cache that + * may not reflect all data already received; otherwise, the method will always block for the latest + * cache updates to have happened before returning. + * @param fillNetPointsUncorrected + * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather + * expensive, especially when compared to simply retrieving a score correction, and particularly if in a + * larger fleet a number of competitors haven't properly finished the race. This should only be used for + * leaderboard editing where a user needs to see what the uncorrected score was that would be used when + * the correction was removed. + */ + private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, + TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, + Map> legRanksCache, DomainFactory baseDomainFactory, + boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { + LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); + TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); + entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); + entryDTO.netPoints = entry.getNetPoints(); + if (fillNetPointsUncorrected) { + entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); + } + entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); + entryDTO.totalPoints = entry.getTotalPoints(); + entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); + entryDTO.discarded = entry.isDiscarded(); + final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); + if (trackedRace != null) { + Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); + if (track != null) { + entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); + } + if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { + long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); + entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; + } else { + entryDTO.timeSinceLastPositionFixInSeconds = null; + } + } + if (addLegDetails && trackedRace != null) { + try { + final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); + RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, + legRanksCache, rankingInfo, cache); + entryDTO.legDetails = raceDetails.getLegDetails(); + entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null + : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); + entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); + entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null + : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); + entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null + : raceDetails.getAverageSignedCrossTrackError().getMeters(); + entryDTO.calculatedTime = raceDetails.getCorrectedTime(); + entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); + entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); + final TimePoint startOfRace = trackedRace.getStartOfRace(); + if (startOfRace != null) { + Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + NavigableSet competitorMarkPassings = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(competitorMarkPassings); + try { + if (!competitorMarkPassings.isEmpty()) { + final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); + if (firstMarkPassing.getWaypoint() == startWaypoint) { + Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); + entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null + : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); + Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); + entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null + : speedFiveSecondsBeforeStartOfRace.getKnots(); + Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( + competitor, startOfRace); + entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null + : distanceToStartLineAtStartOfRace.getMeters(); + Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); + entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null + : speedAtStartTime.getKnots(); + TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); + entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); + Speed competitorSpeedWhenPassingStart = track == null ? null : track + .getEstimatedSpeed(competitorStartTime); + entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null + : competitorSpeedWhenPassingStart.getKnots(); + try { + entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); + } catch (NoWindException nwe) { + entryDTO.startTack = null; // leave empty in case no wind information is available + } + Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace + .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); + entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null + : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); + } + } + } finally { + trackedRace.unlockAfterRead(competitorMarkPassings); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception + } + } + final Fleet fleet = entry.getFleet(); + entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); + return entryDTO; + } + + /** + * Determines the time point of the last raw fix (with outliers not removed) for competitor in + * trackedRace. If the competitor's track is null or empty, null is returned. + * @param trackedRace must not be null + * @param atOrBefore find the last fix at or before the time point specified + */ + private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { + assert trackedRace != null; + final Date timePointOfLastPositionFix; + GPSFixTrack track = trackedRace.getTrack(competitor); + if (track == null) { + timePointOfLastPositionFix = null; + } else { + GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); + if (lastFix == null) { + timePointOfLastPositionFix = null; + } else { + timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); + } + } + return timePointOfLastPositionFix; + } + + private static class RaceDetails { + private final List legDetails; + private final Distance windwardDistanceToCompetitorFarthestAhead; + private final Distance averageAbsoluteCrossTrackError; + private final Distance averageSignedCrossTrackError; + private final Duration gapToLeaderInOwnTime; + private final Duration correctedTime; + private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + + public RaceDetails(List legDetails, Distance windwardDistanceToCompetitorFarthestAhead, + Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, + Duration gapToLeaderInOwnTime, Duration correctedTime, + Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { + super(); + this.legDetails = legDetails; + this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; + this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; + this.averageSignedCrossTrackError = averageSignedCrossTrackError; + this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; + this.correctedTime = correctedTime; + this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + } + public List getLegDetails() { + return legDetails; + } + public Distance getWindwardDistanceToCompetitorFarthestAhead() { + return windwardDistanceToCompetitorFarthestAhead; + } + public Distance getAverageAbsoluteCrossTrackError() { + return averageAbsoluteCrossTrackError; + } + public Distance getAverageSignedCrossTrackError() { + return averageSignedCrossTrackError; + } + public Duration getGapToLeaderInOwnTime() { + return gapToLeaderInOwnTime; + } + public Duration getCorrectedTime() { + return correctedTime; + } + public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { + return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + } + } + + /** + * If timePoint is after the end of the race's tracking the query will be adjusted to obtain the values + * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this + * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, + * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} + * will be registered with the race which will be triggered for any event received by the race. + * @param waitForLatestAnalyses + * if false, this method is allowed to read the maneuver analysis results from a cache that + * may not reflect all data already received; otherwise, the method will always block for the latest + * cache updates to have happened before returning. + */ + private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { + final RaceDetails raceDetails; + if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { + raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); + } else { + raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); + } + return raceDetails; + } + + private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, + final Competitor competitor, final Map> legRanksCache, + RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { + final com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(trackedRace, competitor); + RunnableFuture raceDetails; + synchronized (raceDetailsAtEndOfTrackingCache) { + raceDetails = raceDetailsAtEndOfTrackingCache.get(key); + if (raceDetails == null) { + raceDetails = new FutureTask(new Callable() { + @Override + public RaceDetails call() throws Exception { + TimePoint end = trackedRace.getEndOfRace(); + if (end == null) { + end = trackedRace.getEndOfTracking(); + } + return calculateRaceDetails(trackedRace, competitor, end, + // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock + /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ + /* waitForLatestAnalyses (maneuver and cross track error) */ false, + legRanksCache, cache, rankingInfo); + } + }); + raceDetailsExecutor.execute(raceDetails); + raceDetailsAtEndOfTrackingCache.put(key, raceDetails); + final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); + trackedRace.addListener(cacheInvalidationListener); + cacheInvalidationListeners.add(cacheInvalidationListener); + } + } + return raceDetails.get(); + } + + private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { + final List legDetails = new ArrayList(); + final Course course = trackedRace.getRace().getCourse(); + course.lockForRead(); // hold back any course re-configurations while looping over the legs + try { + for (Leg leg : course.getLegs()) { + LegEntryDTO legEntry; + // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with + // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change + // immediately. That's why we've acquired a read lock for the course above. + TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { + legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); + } else { + legEntry = null; + } + legDetails.add(legEntry); + } + final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace + .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); + Distance averageAbsoluteCrossTrackError; + try { + averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( + competitor, timePoint, waitForLatestAnalyses, cache); + } catch (NoWindException nwe) { + // without wind information, use null meaning "unknown" + averageAbsoluteCrossTrackError = null; + } + Distance averageSignedCrossTrackError; + try { + averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( + competitor, timePoint, waitForLatestAnalyses, cache); + } catch (NoWindException nwe) { + // without wind information, use null meaning "unknown" + averageSignedCrossTrackError = null; + } + final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); + return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, + trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), + trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), + competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); + } finally { + course.unlockAfterRead(); + } + } + + private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { + LegEntryDTO result; + final Duration time = trackedLeg.getTime(timePoint); + if (trackedLeg == null || time == null) { + result = null; + } else { + result = new LegEntryDTO(); + try { + result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); + } catch (NoWindException nwe) { + result.legType = null; // can't determine leg type without wind data + } + final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); + result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); + Distance averageAbsoluteCrossTrackError; + try { + averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); + } catch (NoWindException nwe) { + // leave averageAbsoluteCrossTrackError as null, meaning "unknown" + averageAbsoluteCrossTrackError = null; + } + result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); + Distance averageSignedCrossTrackError; + try { + averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); + } catch (NoWindException nwe) { + // leave averageSignedCrossTrackError as null, meaning "unknown" + averageSignedCrossTrackError = null; + } + result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); + Double speedOverGroundInKnots; + if (trackedLeg.hasFinishedLeg(timePoint)) { + speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); + } else { + final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); + speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); + } + result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; + Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); + result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); + Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); + result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); + final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); + result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); + result.timeInMilliseconds = time.asMillis(); + result.finished = trackedLeg.hasFinishedLeg(timePoint); + final TimePoint legFinishTime = trackedLeg.getFinishTime(); + result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), + trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; + // fetch the leg gap in own corrected time from the ranking metric + final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). + getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); + result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); + if (result.gapToLeaderInSeconds != null) { + final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); + if (gapAtEndOfPreviousLeg != null) { + result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); + } + } + LinkedHashMap legRanks = legRanksCache.get(trackedLeg.getLeg()); + if (legRanks != null) { + result.rank = legRanks.get(trackedLeg.getCompetitor()); + } else { + result.rank = trackedLeg.getRank(timePoint, cache); + } + result.started = trackedLeg.hasStartedLeg(timePoint); + Speed velocityMadeGood; + if (trackedLeg.hasFinishedLeg(timePoint)) { + velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); + } else { + velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); + } + result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); + Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); + result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo + .getMeters(); + final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); + if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { + // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg + // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg + List maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() + .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); + if (maneuvers != null) { + result.numberOfManeuvers = new HashMap(); + result.numberOfManeuvers.put(ManeuverType.TACK, 0); + result.numberOfManeuvers.put(ManeuverType.JIBE, 0); + result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); + Map totalManeuverLossInMeters = new HashMap(); + totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); + totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); + totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); + TimePoint startOfLeg = trackedLeg.getStartTime(); + for (Maneuver maneuver : maneuvers) { + // don't count maneuvers that were in previous legs + switch (maneuver.getType()) { + case TACK: + case JIBE: + case PENALTY_CIRCLE: + if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || + maneuver.getTimePoint().before(legFinishTime))) { + if (maneuver.getManeuverLoss() != null) { + result.numberOfManeuvers.put(maneuver.getType(), + result.numberOfManeuvers.get(maneuver.getType()) + 1); + totalManeuverLossInMeters.put(maneuver.getType(), + totalManeuverLossInMeters.get(maneuver.getType()) + + maneuver.getManeuverLoss().getMeters()); + } + } + break; + case MARK_PASSING: + // analyze all mark passings, not only those after this leg's start, to catch the mark passing + // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg + MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; + if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { + result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); + } + break; + default: + /* Do nothing here. + * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. + */ + } + } + result.averageManeuverLossInMeters = new HashMap(); + for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, + ManeuverType.PENALTY_CIRCLE }) { + if (result.numberOfManeuvers.get(maneuverType) != 0) { + result.averageManeuverLossInMeters.put( + maneuverType, + totalManeuverLossInMeters.get(maneuverType) + / result.numberOfManeuvers.get(maneuverType)); + } + } + } + } + } + return result; + } + + private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { + final Duration result; + final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); + // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap + // at the end of the previous leg + final TimePoint timePoint = trackedLeg.getStartTime(); + final TrackedLegOfCompetitor tloc; + if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { + tloc = trackedLeg; + } else { + tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) + .getTrackedLeg(trackedLeg.getCompetitor()); + } + result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); + return result; + } + + private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { + LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, + baseDomainFactory); + return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); + } + + private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, + DomainFactory baseDomainFactory) { + LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; + if (result == null) { + synchronized (this) { + result = this.liveLeaderboardUpdater; + if (result == null) { + this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); + result = this.liveLeaderboardUpdater; + } + } + } + return result; + } + + private LeaderboardDTOCache getLeaderboardDTOCache() { + LeaderboardDTOCache result = this.leaderboardDTOCache; + if (result == null) { + synchronized (this) { + result = this.leaderboardDTOCache; + if (result == null) { + // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes + // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a + // single fix was updated is too expensive if users use the replay feature while a race is still running. + // Therefore, using waitForLatestAnalyses==false seems appropriate here. + this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); + result = this.leaderboardDTOCache; + } + } + } + return result; + } + + @Override + public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, + Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, + InterruptedException, ExecutionException { + LeaderboardDTO result = null; + if (timePoint == null) { + // timePoint==null means live mode; however, if we're after the end of all races and after all score + // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map + // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly + // when the tracked race associations or score corrections or tracked race contents changes: + final TimePoint nowMinusDelay = this.getNowMinusDelay(); + final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); + if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { + // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode + // and pull the result from the regular leaderboard cache: + timePoint = timePointOfLatestModification; + } else { + // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: + timePoint = null; + result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); + } + } + if (timePoint != null) { + if (fillNetPointsUncorrected) { + // explicitly filling the uncorrected net points requires uncached recalculation + result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, + trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); + } else { + // in replay we'd like up-to-date results; they are still cached + // which is OK because the cache is invalidated whenever any of the tracked races attached to the + // leaderboard changes. + result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, + trackedRegattaRegistry); + } + } + return result; + } + + public String toString() { + return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); + } + + @Override + public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { + return new NumberOfCompetitorsFetcherImpl(); + } + + protected abstract LeaderboardType getLeaderboardType(); + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java index 15e2db0a7cf..067c30d255d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelayedLeaderboardCorrectionsImpl.java @@ -278,6 +278,10 @@ public class DelayedLeaderboardCorrectionsImpl implements DelayedLeaderboardCorr public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java index f9016dbef70..85f2cfa40fe 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/MetaLeaderboardColumn.java @@ -121,6 +121,11 @@ public class MetaLeaderboardColumn extends SimpleAbstractRaceColumn implements R getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java index 92629e3aaca..c138fdf058b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/impl/RaceColumnListeners.java @@ -86,6 +86,12 @@ public class RaceColumnListeners implements Serializable { } } + public void notifyListenersAboutIsFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + for (RaceColumnListener listener : getRaceColumnListeners()) { + listener.isFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + } + } + public void notifyListenersAboutIsStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { for (RaceColumnListener listener : getRaceColumnListeners()) { listener.isStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java index 8809a8d1916..08a42409376 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java @@ -1,53 +1,53 @@ -package com.sap.sailing.gwt.ui.test; - -import java.util.Collections; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Fleet; -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.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.HighPoint; -import com.sap.sailing.domain.ranking.RankingMetricsFactory; -import com.sap.sailing.gwt.ui.shared.MarkDTO; -import com.sap.sailing.server.RacingEventService; - -public class PingMarkViaRegattaLogTest { - private SailingServiceImplMock sailingService; - private RacingEventService service; - private final String columnName = "column"; - private final Fleet fleet = new FleetImpl("fleet"); - - @Before - public void prepare() { - sailingService = new SailingServiceImplMock(); - service = sailingService.getRacingEventService(); - } - - @Test - public void testPinging() throws DoesNotHaveRegattaLogException { - service.getMongoObjectFactory().getDatabase().dropDatabase(); - Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), - Collections.singletonList(columnName), service); - Regatta regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /*startDate*/ null, /*endDate*/ null, - UUID.randomUUID(), Collections.singletonList(series), - false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true, - RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); - RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); - - MarkDTO mark = new MarkDTO("mark", "mark"); - Position position = new DegreePosition(30, 40); - - sailingService.pingMark(leaderboard.getName(), mark, position); - } -} +package com.sap.sailing.gwt.ui.test; + +import java.util.Collections; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Fleet; +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.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.ranking.RankingMetricsFactory; +import com.sap.sailing.gwt.ui.shared.MarkDTO; +import com.sap.sailing.server.RacingEventService; + +public class PingMarkViaRegattaLogTest { + private SailingServiceImplMock sailingService; + private RacingEventService service; + private final String columnName = "column"; + private final Fleet fleet = new FleetImpl("fleet"); + + @Before + public void prepare() { + sailingService = new SailingServiceImplMock(); + service = sailingService.getRacingEventService(); + } + + @Test + public void testPinging() throws DoesNotHaveRegattaLogException { + service.getMongoObjectFactory().getDatabase().dropDatabase(); + Series series = new SeriesImpl("series", false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(fleet), + Collections.singletonList(columnName), service); + Regatta regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /*startDate*/ null, /*endDate*/ null, + UUID.randomUUID(), Collections.singletonList(series), + false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true, + RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); + RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); + + MarkDTO mark = new MarkDTO("mark", "mark"); + Position position = new DegreePosition(30, 40); + + sailingService.pingMark(leaderboard.getName(), mark, position); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java index 628bb577a3c..0908b933709 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java @@ -58,7 +58,7 @@ public class CreateRegattaCallback implements DialogCallback{ LinkedHashMap seriesStructure = new LinkedHashMap(); for (SeriesDTO seriesDTO : newRegatta.series) { SeriesCreationParametersDTO seriesPair = new SeriesCreationParametersDTO(seriesDTO.getFleets(), - seriesDTO.isMedal(), seriesDTO.isStartsWithZeroScore(), + seriesDTO.isMedal(), seriesDTO.isFleetsCanRunInParallel(), seriesDTO.isStartsWithZeroScore(), seriesDTO.isFirstColumnIsNonDiscardableCarryForward(), seriesDTO.getDiscardThresholds(), seriesDTO.hasSplitFleetContiguousScoring()); seriesStructure.put(seriesDTO.getName(), seriesPair); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java index 7303c1479cc..18e088d6ddc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java @@ -287,6 +287,7 @@ public class RegattaDetailsComposite extends Composite { final SeriesDTO series = seriesDescriptor.getSeries(); final List newRaceColumns = seriesDescriptor.getRaces(); final boolean isMedalChanged = series.isMedal() != seriesDescriptor.isMedal(); + final boolean isFleetsCanRunInParallelChanged = series.isFleetsCanRunInParallel() != seriesDescriptor.isFleetsCanRunInParallel(); final boolean isStartsWithZeroScoreChanged = series.isStartsWithZeroScore() != seriesDescriptor.isStartsWithZeroScore(); final boolean isFirstColumnIsNonDiscardableCarryForwardChanged = series.isFirstColumnIsNonDiscardableCarryForward() != seriesDescriptor.isFirstColumnIsNonDiscardableCarryForward(); final boolean hasSplitFleetContiguousScoringChanged = series.hasSplitFleetContiguousScoring() != seriesDescriptor.hasSplitFleetContiguousScoring(); @@ -350,11 +351,11 @@ public class RegattaDetailsComposite extends Composite { }); } }); - if (isMedalChanged || seriesResultDiscardingThresholdsChanged || isStartsWithZeroScoreChanged + if (isMedalChanged || isFleetsCanRunInParallelChanged || seriesResultDiscardingThresholdsChanged || isStartsWithZeroScoreChanged || isFirstColumnIsNonDiscardableCarryForwardChanged || hasSplitFleetContiguousScoringChanged || seriesNameChanged) { sailingService.updateSeries(regattaIdentifier, series.getName(), seriesDescriptor.getSeriesName(), - seriesDescriptor.isMedal(), seriesDescriptor.getResultDiscardingThresholds(), + seriesDescriptor.isMedal(), seriesDescriptor.isFleetsCanRunInParallel(), seriesDescriptor.getResultDiscardingThresholds(), seriesDescriptor.isStartsWithZeroScore(), seriesDescriptor.isFirstColumnIsNonDiscardableCarryForward(), seriesDescriptor.hasSplitFleetContiguousScoring(), series.getFleets(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java index ba20696021a..1b2e45f1b51 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java @@ -309,7 +309,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer if (seriesIter.hasNext()) { final SeriesDTO series = seriesIter.next(); sailingService.updateSeries(regattaName, series.getName(), series.getName(), series.isMedal(), - series.getDiscardThresholds(), series.isStartsWithZeroScore(), + series.isFleetsCanRunInParallel(), series.getDiscardThresholds(), series.isStartsWithZeroScore(), series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring(), series.getFleets(), new MarkedAsyncCallback(new AsyncCallback() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java index 80bfa4ce806..8f2e0bf25f1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesDescriptor.java @@ -1,69 +1,75 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.List; - -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.gwt.ui.shared.SeriesDTO; - -/** - * Used by {@link SeriesEditDialog} to capture the result of the user's configuration changes, - * including the race columns that the series shall define, the medal status and the discarding rules, if any. - * - * @author Axel Uhl (d043530) - * - */ -public class SeriesDescriptor { - private final SeriesDTO series; - private final List races; - private final String seriesName; - private final boolean isMedal; - private final boolean startsWithZeroScore; - private final boolean hasSplitFleetContiguousScoring; - private final boolean firstColumnIsNonDiscardableCarryForward; - private final int[] resultDiscardingThresholds; - - public SeriesDescriptor(SeriesDTO series, String seriesName, List races, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, - boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring) { - this.seriesName = seriesName; - this.series = series; - this.races = races; - this.isMedal = isMedal; - this.startsWithZeroScore = startsWithZeroScore; - this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; - this.resultDiscardingThresholds = resultDiscardingThresholds; - this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; - } - - public SeriesDTO getSeries() { - return series; - } - - public String getSeriesName() { - return seriesName; - } - - public List getRaces() { - return races; - } - - public boolean isMedal() { - return isMedal; - } - - public int[] getResultDiscardingThresholds() { - return resultDiscardingThresholds; - } - - public boolean isStartsWithZeroScore() { - return startsWithZeroScore; - } - - public boolean hasSplitFleetContiguousScoring() { - return hasSplitFleetContiguousScoring; - } - - public boolean isFirstColumnIsNonDiscardableCarryForward() { - return firstColumnIsNonDiscardableCarryForward; - } -} +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.shared.SeriesDTO; + +/** + * Used by {@link SeriesEditDialog} to capture the result of the user's configuration changes, + * including the race columns that the series shall define, the medal status and the discarding rules, if any. + * + * @author Axel Uhl (d043530) + * + */ +public class SeriesDescriptor { + private final SeriesDTO series; + private final List races; + private final String seriesName; + private final boolean isMedal; + private final boolean isFleetsCanRunInParallel; + private final boolean startsWithZeroScore; + private final boolean hasSplitFleetContiguousScoring; + private final boolean firstColumnIsNonDiscardableCarryForward; + private final int[] resultDiscardingThresholds; + + public SeriesDescriptor(SeriesDTO series, String seriesName, List races, boolean isMedal, boolean isFleetsCanRunInParallel, + int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring) { + this.seriesName = seriesName; + this.series = series; + this.races = races; + this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + this.startsWithZeroScore = startsWithZeroScore; + this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; + this.resultDiscardingThresholds = resultDiscardingThresholds; + this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; + } + + public SeriesDTO getSeries() { + return series; + } + + public String getSeriesName() { + return seriesName; + } + + public List getRaces() { + return races; + } + + public boolean isMedal() { + return isMedal; + } + + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + public int[] getResultDiscardingThresholds() { + return resultDiscardingThresholds; + } + + public boolean isStartsWithZeroScore() { + return startsWithZeroScore; + } + + public boolean hasSplitFleetContiguousScoring() { + return hasSplitFleetContiguousScoring; + } + + public boolean isFirstColumnIsNonDiscardableCarryForward() { + return firstColumnIsNonDiscardableCarryForward; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 5e5a0d1952c..aba2040308e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -39,6 +39,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class SeriesEditDialog extends DataEntryDialog { private TextBox seriesNameTextBox; private CheckBox isMedalCheckbox; + private CheckBox fleetsCanRunInParallelCheckbox; private CheckBox startWithZeroScoreCheckbox; private CheckBox hasSplitFleetContiguousScoringCheckbox; private CheckBox firstColumnIsNonDiscardableCarryForwardCheckbox; @@ -133,6 +134,7 @@ public class SeriesEditDialog extends DataEntryDialog { races.add(raceColumnDTO); } return new SeriesDescriptor(selectedSeries, seriesNameTextBox.getValue(), races, isMedalCheckbox.getValue(), + fleetsCanRunInParallelCheckbox.getValue(), useSeriesResultDiscardingThresholdsCheckbox.getValue() ? discardThresholdBoxes.getDiscardThresholds() : null, startWithZeroScoreCheckbox.getValue(), firstColumnIsNonDiscardableCarryForwardCheckbox.getValue(), hasSplitFleetContiguousScoringCheckbox.getValue()); @@ -171,7 +173,12 @@ public class SeriesEditDialog extends DataEntryDialog { isMedalCheckbox.ensureDebugId("MedalSeriesCheckbox"); isMedalCheckbox.setValue(selectedSeries.isMedal()); additionalWidgetPanel.add(isMedalCheckbox); - + + fleetsCanRunInParallelCheckbox = createCheckbox(stringMessages.canFleetsRunInParallel()); + fleetsCanRunInParallelCheckbox.ensureDebugId("FleetsCanRaceInParallelSeriesCheckbox"); + fleetsCanRunInParallelCheckbox.setValue(selectedSeries.isFleetsCanRunInParallel()); + additionalWidgetPanel.add(fleetsCanRunInParallelCheckbox); + startWithZeroScoreCheckbox = createCheckbox(stringMessages.startsWithZeroScore()); startWithZeroScoreCheckbox.ensureDebugId("StartsWithZeroScoreCheckbox"); startWithZeroScoreCheckbox.setValue(selectedSeries.isStartsWithZeroScore()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 923b0224751..9843ffe661f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -227,7 +227,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List> columnNames); void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore, boolean firstRaceIsNonDiscardableCarryForward, boolean hasSplitFleetScore, List fleets); RaceColumnInSeriesDTO addRaceColumnToSeries(RegattaIdentifier regattaIdentifier, String seriesName, String columnName); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 65fb4725221..1b4d9aad5a1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -443,7 +443,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan List> columnNames, AsyncCallback> callback); void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore, boolean firstRaceIsNonDiscardableCarryForward, boolean hasSplitFleetScore, List fleets, AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 7542b21290d..fc6a57e698a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -346,6 +346,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String splitFleet(); String color(); String medalSeries(); + String canFleetsRunInParallel(); String noColor(); String pleaseSelectARegatta(); String addFleet(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 6d693b221cb..0696d841995 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -358,6 +358,7 @@ series=Series splitFleet=Split fleet color=Color medalSeries=Medal Series +canFleetsRunInParallel=Fleets can run in parallel noColor=No color pleaseSelectARegatta=Please select a regatta. addFleet=Add fleet diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 80977349b2c..e9a77874f5a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -359,6 +359,7 @@ fleets=Flotten splitFleet=Aufteilung in Gruppen color=Farbe medalSeries=Medal Series +canFleetsRunInParallel=Flotten können parallel segeln noColor=Keine Farbe pleaseSelectARegatta=Bitte wählen Sie eine Regatta aus. addFleet=Gruppe hinzufügen diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 936efd1563b..03c2da16d74 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -356,6 +356,7 @@ series=Серия splitFleet=Разделить флот color=Цвет medalSeries=Серия на медали +canFleetsRunInParallel=Fleets can run in parallel noColor=Без цвета pleaseSelectARegatta=Выберите регату. addFleet=Добавить флот diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 3470236cd97..2eb21b55251 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -356,6 +356,7 @@ series=系列 splitFleet=船队区分 color=颜色 medalSeries=奖牌轮系列 +canFleetsRunInParallel=Fleets can run in parallel noColor=没有颜色 pleaseSelectARegatta=请选择比赛。 addFleet=添加船队 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties index 8965d3606a4..3daacf366e9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties @@ -356,6 +356,7 @@ series=系列 splitFleet=船队区分 color=颜色 medalSeries=奖牌轮系列 +canFleetsRunInParallel=Fleets can run in parallel noColor=没有颜色 pleaseSelectARegatta=请选择比赛。 addFleet=添加船队 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 4f1bf6e465b..610f4305165 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -846,7 +846,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S fleets.add(baseDomainFactory.convertToFleetDTO(fleet)); } List raceColumns = convertToRaceColumnDTOs(series.getRaceColumns()); - SeriesDTO result = new SeriesDTO(series.getName(), fleets, raceColumns, series.isMedal(), + SeriesDTO result = new SeriesDTO(series.getName(), fleets, raceColumns, series.isMedal(), series.isFleetsCanRunInParallel(), series.getResultDiscardingRule() == null ? null : series.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), series.isStartsWithZeroScore(), series.isFirstColumnIsNonDiscardableCarryForward(), series.hasSplitFleetContiguousScoring()); return result; @@ -2803,7 +2803,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S /* startDate*/ null, /*endDate*/ null, RegattaImpl.getDefaultName(replayRaceDTO.rsc, replayRaceDTO.boat_class), Collections.singletonList(new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, - /* isMedal */false, Collections.singletonList(new FleetImpl( + /* isMedal */false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(new FleetImpl( LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList(), getService())), false, baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, @@ -3917,7 +3917,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, + public void updateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, boolean isFleetsCanRunInParallel, int[] resultDiscardingThresholds, boolean startsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, List fleets) { @@ -3926,7 +3926,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S SecurityUtils.getSubject().checkPermission(Permission.REGATTA.getStringPermissionForObjects(Mode.UPDATE, regatta.getName())); } getService().apply( - new UpdateSeries(regattaIdentifier, seriesName, newSeriesName, isMedal, resultDiscardingThresholds, + new UpdateSeries(regattaIdentifier, seriesName, newSeriesName, isMedal, isFleetsCanRunInParallel, resultDiscardingThresholds, startsWithZeroScore, firstColumnIsNonDiscardableCarryForward, hasSplitFleetContiguousScoring, fleets)); } @@ -4257,7 +4257,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S for (SeriesDTO series : regattaDTO.series){ SeriesParameters seriesParameters = getSeriesParameters(series); seriesCreationParams.put(series.getName(), new SeriesCreationParametersDTO(series.getFleets(), - false, seriesParameters.isStartswithZeroScore(), seriesParameters.isFirstColumnIsNonDiscardableCarryForward(), + false, true, seriesParameters.isStartswithZeroScore(), seriesParameters.isFirstColumnIsNonDiscardableCarryForward(), seriesParameters.getDiscardingThresholds(), seriesParameters.isHasSplitFleetContiguousScoring())); } return seriesCreationParams; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java index 39125aa0e75..caf3aa7467b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SeriesDTO.java @@ -12,6 +12,7 @@ public class SeriesDTO extends NamedDTO { private List fleets; private List raceColumns; private boolean isMedal; + private boolean isFleetsCanRunInParallel; private int[] discardThresholds; private Boolean startsWithZeroScore; private boolean firstColumnIsNonDiscardableCarryForward; @@ -19,13 +20,14 @@ public class SeriesDTO extends NamedDTO { public SeriesDTO() {} - public SeriesDTO(String name, List fleets, List raceColumns, boolean isMedal, + public SeriesDTO(String name, List fleets, List raceColumns, boolean isMedal, boolean isFleetsCanRunInParallel, int[] discardThresholds, boolean startsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring) { super(name); this.fleets = fleets; this.raceColumns = raceColumns; this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; this.startsWithZeroScore = startsWithZeroScore; this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; this.discardThresholds = discardThresholds; @@ -40,7 +42,7 @@ public class SeriesDTO extends NamedDTO { public SeriesDTO(SeriesDTO otherSeries) { this(otherSeries.getName(), otherSeries.getFleets(), otherSeries.getRaceColumns() == null ? null : new ArrayList(otherSeries.getRaceColumns()), - otherSeries.isMedal(), otherSeries.getDiscardThresholds(), otherSeries.isStartsWithZeroScore(), + otherSeries.isMedal(), otherSeries.isFleetsCanRunInParallel(), otherSeries.getDiscardThresholds(), otherSeries.isStartsWithZeroScore(), otherSeries.isFirstColumnIsNonDiscardableCarryForward(), otherSeries.hasSplitFleetContiguousScoring()); } @@ -48,10 +50,6 @@ public class SeriesDTO extends NamedDTO { return hasSplitFleetContiguousScoring; } - public boolean isMedal() { - return isMedal; - } - public List getFleets() { return fleets; } @@ -60,10 +58,22 @@ public class SeriesDTO extends NamedDTO { this.fleets = fleets; } + public boolean isMedal() { + return isMedal; + } + public void setMedal(boolean isMedal) { this.isMedal = isMedal; } + public boolean isFleetsCanRunInParallel() { + return isFleetsCanRunInParallel; + } + + public void setFleetsCanRunInParallel(boolean isFleetsCanRunInParallel) { + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + } + public List getRaceColumns() { return raceColumns; } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java index ac1099c184c..f018f43c642 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java @@ -1,81 +1,81 @@ -package com.sap.sailing.mongodb.test; - -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.Before; - -import com.mongodb.MongoException; -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; -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.Fleet; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.persistence.DomainObjectFactory; -import com.sap.sailing.domain.persistence.MongoObjectFactory; -import com.sap.sailing.domain.persistence.PersistenceFactory; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public abstract class AbstractTestStoringAndRetrievingRaceLogInRegatta extends RaceLogMongoDBTest { - - protected final String raceColumnName = "My.First$Race$1"; - protected final String regattaName = "TestRegatta"; - protected final String yellowFleetName = "Yellow"; - protected final String seriesName = "Qualifying"; - protected MongoObjectFactory mongoObjectFactory = null; - protected DomainObjectFactory domainObjectFactory = null; - protected Regatta regatta = null; - protected AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); - - public AbstractTestStoringAndRetrievingRaceLogInRegatta() throws UnknownHostException, MongoException { - super(); - } - - @Before - public void setUp() { - now = MillisecondsTimePoint.now(); - mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); - domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); - regatta = createRegattaAndAddRaceColumns(1, regattaName, boatClass, true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); - } - - private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final String regattaBaseName, BoatClass boatClass, boolean persistent, ScoringScheme scoringScheme) { - Regatta regatta = createRegattaWithoutRaceColumns(regattaBaseName, boatClass, persistent, scoringScheme, null); - regatta.getSeriesByName(seriesName).addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null); - return regatta; - } - - private Regatta createRegattaWithoutRaceColumns(final String regattaBaseName, BoatClass boatClass, - boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea) { - List series = createSeriesForTestRegatta(); - Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), RegattaImpl.getDefaultName(regattaBaseName, - boatClass == null ? null : boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, series, persistent, scoringScheme, "123", - courseArea, /* useStartTimeInference */true, OneDesignRankingMetric::new); - return regatta; - } - - private List createSeriesForTestRegatta() { - List series = new ArrayList(); - // -------- qualifying series ------------ - List emptyRaceColumnNames = Collections.emptyList(); - List qualifyingFleets = createQualifyingFleets(); - Series qualifyingSeries = new SeriesImpl(seriesName, /* isMedal */false, qualifyingFleets, - emptyRaceColumnNames, /* trackedRegattaRegistry */ null); - series.add(qualifyingSeries); - return series; - } - - abstract protected List createQualifyingFleets(); -} +package com.sap.sailing.mongodb.test; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; + +import com.mongodb.MongoException; +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +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.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.persistence.DomainObjectFactory; +import com.sap.sailing.domain.persistence.MongoObjectFactory; +import com.sap.sailing.domain.persistence.PersistenceFactory; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public abstract class AbstractTestStoringAndRetrievingRaceLogInRegatta extends RaceLogMongoDBTest { + + protected final String raceColumnName = "My.First$Race$1"; + protected final String regattaName = "TestRegatta"; + protected final String yellowFleetName = "Yellow"; + protected final String seriesName = "Qualifying"; + protected MongoObjectFactory mongoObjectFactory = null; + protected DomainObjectFactory domainObjectFactory = null; + protected Regatta regatta = null; + protected AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); + + public AbstractTestStoringAndRetrievingRaceLogInRegatta() throws UnknownHostException, MongoException { + super(); + } + + @Before + public void setUp() { + now = MillisecondsTimePoint.now(); + mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); + regatta = createRegattaAndAddRaceColumns(1, regattaName, boatClass, true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT)); + } + + private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final String regattaBaseName, BoatClass boatClass, boolean persistent, ScoringScheme scoringScheme) { + Regatta regatta = createRegattaWithoutRaceColumns(regattaBaseName, boatClass, persistent, scoringScheme, null); + regatta.getSeriesByName(seriesName).addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null); + return regatta; + } + + private Regatta createRegattaWithoutRaceColumns(final String regattaBaseName, BoatClass boatClass, + boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea) { + List series = createSeriesForTestRegatta(); + Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), RegattaImpl.getDefaultName(regattaBaseName, + boatClass == null ? null : boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, series, persistent, scoringScheme, "123", + courseArea, /* useStartTimeInference */true, OneDesignRankingMetric::new); + return regatta; + } + + private List createSeriesForTestRegatta() { + List series = new ArrayList(); + // -------- qualifying series ------------ + List emptyRaceColumnNames = Collections.emptyList(); + List qualifyingFleets = createQualifyingFleets(); + Series qualifyingSeries = new SeriesImpl(seriesName, /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, + emptyRaceColumnNames, /* trackedRegattaRegistry */ null); + series.add(qualifyingSeries); + return series; + } + + abstract protected List createQualifyingFleets(); +} diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 197c9e784a6..6b7bb6bf1ec 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -637,6 +637,28 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest assertTrue(loadedRegatta.getSeriesByName("Medal").isStartsWithZeroScore()); } + @Test + public void testLoadStoreRegattaWithSeriesWhereFleetsCanRunInParallel() { + final int numberOfQualifyingRaces = 5; + final int numberOfFinalRaces = 7; + final String regattaBaseName = "Kieler Woche"; + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); + regatta.getSeriesByName("Qualifying").setIsFleetsCanRunInParallel(true); + regatta.getSeriesByName("Final").setIsFleetsCanRunInParallel(false); + regatta.getSeriesByName("Medal").setIsFleetsCanRunInParallel(false); + + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); + mof.storeRegatta(regatta); + + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); + assertTrue(loadedRegatta.getSeriesByName("Qualifying").isFleetsCanRunInParallel()); + assertFalse(loadedRegatta.getSeriesByName("Final").isFleetsCanRunInParallel()); + assertFalse(loadedRegatta.getSeriesByName("Medal").isFleetsCanRunInParallel()); + } + @Test public void testLoadStoreSimpleRegattaWithHighPointScoringScheme() { final int numberOfQualifyingRaces = 5; @@ -787,7 +809,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest List qualifyingFleets = new ArrayList(); qualifyingFleets.add(new FleetImpl("Yellow")); qualifyingFleets.add(new FleetImpl("Blue")); - Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets, + Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualifyingFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(qualifyingSeries); @@ -795,13 +817,13 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest List finalFleets = new ArrayList(); finalFleets.add(new FleetImpl("Gold", 1)); finalFleets.add(new FleetImpl("Silver", 2)); - Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); + Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(finalSeries); // ------------ medal -------------- List medalFleets = new ArrayList(); medalFleets.add(new FleetImpl("Medal")); - Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); + Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); series.add(medalSeries); Regatta regatta = new RegattaImpl(regattaName, boatClass, startDate, endDate, series, persistent, scoringScheme, "123", courseArea, rankingMetricConstructor); return regatta; diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java index ab8078a91b9..2663a47fc32 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java @@ -1,37 +1,38 @@ -package com.sap.sailing.server.gateway.deserialization.racegroup.impl; - -import java.util.ArrayList; -import java.util.Collection; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.racegroup.RaceRow; -import com.sap.sailing.domain.base.racegroup.SeriesWithRows; -import com.sap.sailing.domain.base.racegroup.impl.SeriesWithRowsImpl; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.Helpers; -import com.sap.sailing.server.gateway.serialization.racegroup.impl.RaceRowsOfSeriesWithRowsSerializer; - -public class SeriesWithRowsDeserializer implements JsonDeserializer { - - private JsonDeserializer raceRowDeserializer; - - public SeriesWithRowsDeserializer(JsonDeserializer raceRowDeserializer) { - this.raceRowDeserializer = raceRowDeserializer; - } - - public SeriesWithRows deserialize(JSONObject object) throws JsonDeserializationException { - String name = object.get("name").toString(); - boolean isMedal = (Boolean) object.get("isMedal"); - - Collection rows = new ArrayList(); - for (Object fleetObject : Helpers.getNestedArraySafe(object, RaceRowsOfSeriesWithRowsSerializer.FIELD_FLEETS)) { - JSONObject fleetJson = Helpers.toJSONObjectSafe(fleetObject); - rows.add(raceRowDeserializer.deserialize(fleetJson)); - } - - return new SeriesWithRowsImpl(name, isMedal, rows); - } - +package com.sap.sailing.server.gateway.deserialization.racegroup.impl; + +import java.util.ArrayList; +import java.util.Collection; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.racegroup.RaceRow; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; +import com.sap.sailing.domain.base.racegroup.impl.SeriesWithRowsImpl; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.Helpers; +import com.sap.sailing.server.gateway.serialization.racegroup.impl.RaceRowsOfSeriesWithRowsSerializer; + +public class SeriesWithRowsDeserializer implements JsonDeserializer { + + private JsonDeserializer raceRowDeserializer; + + public SeriesWithRowsDeserializer(JsonDeserializer raceRowDeserializer) { + this.raceRowDeserializer = raceRowDeserializer; + } + + public SeriesWithRows deserialize(JSONObject object) throws JsonDeserializationException { + String name = object.get("name").toString(); + boolean isMedal = (Boolean) object.get("isMedal"); + boolean isFleetsCanRunInParallel = (Boolean) object.get("isFleetsCanRunInParallel"); + + Collection rows = new ArrayList(); + for (Object fleetObject : Helpers.getNestedArraySafe(object, RaceRowsOfSeriesWithRowsSerializer.FIELD_FLEETS)) { + JSONObject fleetJson = Helpers.toJSONObjectSafe(fleetObject); + rows.add(raceRowDeserializer.deserialize(fleetJson)); + } + + return new SeriesWithRowsImpl(name, isMedal, isFleetsCanRunInParallel, rows); + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java index c2f63c2009c..d35ba2bc03f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java @@ -1,92 +1,92 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import javax.ws.rs.core.Response; - -import org.json.simple.JSONArray; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -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.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest { - private Regatta regatta; - private LeaderboardGroup leaderboardGroup; - private String boatClassName = "49er"; - private String regattaName = "TestRegatta"; - private String leaderboardGroupName = "TestLeaderboardGroup"; - - @Before - public void setUp() { - super.setUp(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, - raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - List competitors = createCompetitors(4); - List fleet1Competitors = competitors.subList(0, 2); - List fleet2Competitors = competitors.subList(2, 4); - - TimePoint now = MillisecondsTimePoint.now(); - - RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); - TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors); - TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); - - racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); - - List leaderboardNames = new ArrayList(); - leaderboardNames.add(regatta.getName()); - leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), leaderboardGroupName, "description", - "The LG displayName", /* displayGroupsInReverseOrder */ false, - leaderboardNames, /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); - } - - @Test - public void testExportLeaderboardGroupsAsJson() throws Exception { - LeaderboardGroupsResource resource = new LeaderboardGroupsResource(); - LeaderboardGroupsResource spyResource = spyResource(resource); - Response leaderboardGroupsResponse = spyResource.getLeaderboardGroups(); - - String jsonString = (String) leaderboardGroupsResponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONArray array= (JSONArray) obj; - - assertTrue(array.size() == 1); - String jsonFirstLeaderboardGroup = (String) array.get(0); - assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup)); - } -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.ws.rs.core.Response; + +import org.json.simple.JSONArray; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Test; + +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.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest { + private Regatta regatta; + private LeaderboardGroup leaderboardGroup; + private String boatClassName = "49er"; + private String regattaName = "TestRegatta"; + private String leaderboardGroupName = "TestLeaderboardGroup"; + + @Before + public void setUp() { + super.setUp(); + List series = new ArrayList(); + List fleets = new ArrayList(); + List raceColumnNames = new ArrayList(); + fleets.add(new FleetImpl("Fleet1")); + fleets.add(new FleetImpl("Fleet2")); + Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, + raceColumnNames, /* trackedRegattaRegistry */null); + series.add(testSeries); + regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); + testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + List competitors = createCompetitors(4); + List fleet1Competitors = competitors.subList(0, 2); + List fleet2Competitors = competitors.subList(2, 4); + + TimePoint now = MillisecondsTimePoint.now(); + + RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); + TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors); + TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); + + racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); + + List leaderboardNames = new ArrayList(); + leaderboardNames.add(regatta.getName()); + leaderboardGroup = racingEventService.addLeaderboardGroup(UUID.randomUUID(), leaderboardGroupName, "description", + "The LG displayName", /* displayGroupsInReverseOrder */ false, + leaderboardNames, /* overallLeaderboardDiscardThresholds */ null, /* overallLeaderboardScoringSchemeType */ null); + } + + @Test + public void testExportLeaderboardGroupsAsJson() throws Exception { + LeaderboardGroupsResource resource = new LeaderboardGroupsResource(); + LeaderboardGroupsResource spyResource = spyResource(resource); + Response leaderboardGroupsResponse = spyResource.getLeaderboardGroups(); + + String jsonString = (String) leaderboardGroupsResponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONArray array= (JSONArray) obj; + + assertTrue(array.size() == 1); + String jsonFirstLeaderboardGroup = (String) array.get(0); + assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup)); + } +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java index 23f3daff110..28178d52f9d 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java @@ -49,7 +49,7 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest c.getColor(), c.getEmail(), c.getFlagImage(), (DynamicTeam) c.getTeam(), (DynamicBoat) c.getBoat(), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), - MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections + MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, /* isFleetsCanRunInParallel */ true, Collections .singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false, new HighPoint(), 0, null, OneDesignRankingMetric::new); racingEventService.addRegattaWithoutReplication(regatta); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java index 6268ea97ab3..dc33adf1a2d 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java @@ -1,105 +1,105 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.doReturn; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.ws.rs.core.Response; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.HighPoint; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceMapping; -import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class LeaderboardsResourcePingMarkTest extends AbstractJaxRsApiTest { - private Mark mark; - private RegattaLog log; - private RegattaLeaderboard leaderboard; - - public static final String PING_MARK_JSON = - " {\n" + - " \"timestamp\" : 1427142552000,\n" + - " \"latitude\" : 54.325246,\n" + - " \"longitude\" : 10.148556,\n" + - " \"speed\" : 3.61,\n" + - " \"course\" : 258.11,\n" + - " }"; - - public static final String PING2_MARK_JSON = - " {\n" + - " \"timestamp\" : 1427142562000,\n" + - " \"latitude\" : 54.425246,\n" + - " \"longitude\" : 10.248556,\n" + - " \"speed\" : 3.61,\n" + - " \"course\" : 258.11,\n" + - " }"; - - @Before - public void setUp() { - super.setUp(); - - mark = racingEventService.getBaseDomainFactory().getOrCreateMark("id", "name"); - Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), - MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections - .singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false, - new HighPoint(), 0, null, OneDesignRankingMetric::new); - racingEventService.addRegattaWithoutReplication(regatta); - leaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "regatta", new int[] {}); - log = leaderboard.getRegattaLike().getRegattaLog(); - } - - @Test - public void testCheckinAndCheckout() throws Exception { - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - doReturn(RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(racingEventService.getBaseDomainFactory())).when(resource).getRaceLogTrackingAdapter(); - { - Response response = resource.pingMark(PING_MARK_JSON, leaderboard.getName(), mark.getId().toString()); - assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); - Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); - List> mappingsForMark = mappings.get(mark); - assertThat("one mapping was created for the one ping", mappingsForMark.size(), equalTo(1)); - assertOneFixPerMapping(mappingsForMark); - } - { - // now produce a second ping; this should produce also only one fix; no additional artificial fixes are - // created (anymore; they used to be before bug2851) - Response response = resource.pingMark(PING2_MARK_JSON, leaderboard.getName(), mark.getId().toString()); - assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); - Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); - List> mappingsForMark = mappings.get(mark); - assertThat("Assert no additional mapping was created for the second ping", mappingsForMark.size(), equalTo(2)); - assertOneFixPerMapping(mappingsForMark); - } - } - - private void assertOneFixPerMapping(List> mappingsForMark) throws TransformationException, - NoCorrespondingServiceRegisteredException { - for (DeviceMapping i : mappingsForMark) { - DeviceIdentifier device = i.getDevice(); - // first ping produces exactly one fix: - assertThat("all fixes stored", racingEventService.getGPSFixStore().getNumberOfFixes(device), equalTo(1L)); - } - } -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.doReturn; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.racelogtracking.DeviceMapping; +import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class LeaderboardsResourcePingMarkTest extends AbstractJaxRsApiTest { + private Mark mark; + private RegattaLog log; + private RegattaLeaderboard leaderboard; + + public static final String PING_MARK_JSON = + " {\n" + + " \"timestamp\" : 1427142552000,\n" + + " \"latitude\" : 54.325246,\n" + + " \"longitude\" : 10.148556,\n" + + " \"speed\" : 3.61,\n" + + " \"course\" : 258.11,\n" + + " }"; + + public static final String PING2_MARK_JSON = + " {\n" + + " \"timestamp\" : 1427142562000,\n" + + " \"latitude\" : 54.425246,\n" + + " \"longitude\" : 10.248556,\n" + + " \"speed\" : 3.61,\n" + + " \"course\" : 258.11,\n" + + " }"; + + @Before + public void setUp() { + super.setUp(); + + mark = racingEventService.getBaseDomainFactory().getOrCreateMark("id", "name"); + Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), + MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, /* isFleetsCanRunInParallel */ true, Collections + .singleton(new FleetImpl("fleet")), Arrays.asList("column"), racingEventService)), false, + new HighPoint(), 0, null, OneDesignRankingMetric::new); + racingEventService.addRegattaWithoutReplication(regatta); + leaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "regatta", new int[] {}); + log = leaderboard.getRegattaLike().getRegattaLog(); + } + + @Test + public void testCheckinAndCheckout() throws Exception { + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + doReturn(RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(racingEventService.getBaseDomainFactory())).when(resource).getRaceLogTrackingAdapter(); + { + Response response = resource.pingMark(PING_MARK_JSON, leaderboard.getName(), mark.getId().toString()); + assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); + Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); + List> mappingsForMark = mappings.get(mark); + assertThat("one mapping was created for the one ping", mappingsForMark.size(), equalTo(1)); + assertOneFixPerMapping(mappingsForMark); + } + { + // now produce a second ping; this should produce also only one fix; no additional artificial fixes are + // created (anymore; they used to be before bug2851) + Response response = resource.pingMark(PING2_MARK_JSON, leaderboard.getName(), mark.getId().toString()); + assertThat("response is ok", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); + Map>> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze(); + List> mappingsForMark = mappings.get(mark); + assertThat("Assert no additional mapping was created for the second ping", mappingsForMark.size(), equalTo(2)); + assertOneFixPerMapping(mappingsForMark); + } + } + + private void assertOneFixPerMapping(List> mappingsForMark) throws TransformationException, + NoCorrespondingServiceRegisteredException { + for (DeviceMapping i : mappingsForMark) { + DeviceIdentifier device = i.getDevice(); + // first ping produces exactly one fix: + assertThat("all fixes stored", racingEventService.getGPSFixStore().getNumberOfFixes(device), equalTo(1L)); + } + } +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java index 509dbc22b1f..574c5a5bbfc 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java @@ -1,266 +1,266 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import javax.ws.rs.core.Response; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -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.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class LeaderboardsResourceTest extends AbstractJaxRsApiTest { - private Regatta regatta; - private RegattaLeaderboard regattaLeaderboard; - private String boatClassName = "49er"; - private String regattaBaseName = "TestRegatta"; - - @Before - public void setUp() { - super.setUp(); - } - - private void dropAndCreateRegatta() { - service.getDB().dropDatabase(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, - raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClassName), boatClassName, - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - List competitors = createCompetitors(4); - List fleet1Competitors = competitors.subList(0, 2); - List fleet2Competitors = competitors.subList(2, 4); - - TimePoint now = MillisecondsTimePoint.now(); - - RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); - TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors, regatta); - TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors, regatta); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); - r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); - - regattaLeaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); - - } - - @Test - public void testExportEmptyLeaderboardAsJson() throws Exception { - dropAndCreateRegatta(); - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - // resultTimepoint should be null if there are no 'final' results yet - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNull(resultTimePoint); - - JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertTrue(jsonCompetitors.size() == 4); - } - - @Test - public void testExportLeaderboardWithFinalResultStateAsJson() throws Exception { - dropAndCreateRegatta(); - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNull(resultTimePoint); - - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - - LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString2 = (String) leaderboardReponse2.getEntity(); - obj= JSONValue.parse(jsonString2); - jsonObject = (JSONObject) obj; - - resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint); - } - - @Test - public void testExportLeaderboardWithLiveResultStateAsJson() throws Exception { - dropAndCreateRegatta(); - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Live, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Live.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint); - - JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertTrue(jsonCompetitors.size() == 4); - } - - @Test - public void testSmartFutureCacheInExportLeaderboardAsJson() throws Exception { - dropAndCreateRegatta(); - - TimePoint now = MillisecondsTimePoint.now(); - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); - - Thread.sleep(100); - - // call the servlet for the first time - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint); - - // second call - it's expected to get the same result from the cache - Thread.sleep(100); - - LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString2 = (String) leaderboardReponse2.getEntity(); - Object obj2= JSONValue.parse(jsonString2); - JSONObject jsonObject2 = (JSONObject) obj2; - - String jsonLeaderboardName2 = (String) jsonObject2.get("name"); - String jsonResultState2 = (String) jsonObject2.get("resultState"); - - assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2)); - assertTrue(jsonResultState.equals(jsonResultState2)); - - TimePoint resultTimePoint2 = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNotNull(resultTimePoint2); - assertTrue(resultTimePoint.equals(resultTimePoint2)); - } - - @Test - public void testExportLeaderboardWithMaxCompetitorsCountAsJson() throws Exception { - dropAndCreateRegatta(); - Integer maxCompetitorsCount = 2; - - LeaderboardsResource resource = spyResource(new LeaderboardsResource()); - Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - String jsonString = (String) leaderboardReponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); - assertNull(resultTimePoint); - - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - - // first call for 'all' competitors - LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - jsonString = (String) leaderboardReponse2.getEntity(); - obj= JSONValue.parse(jsonString); - jsonObject = (JSONObject) obj; - JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertEquals(jsonCompetitors.size(), 4); - - Thread.sleep(100); - - // second call with maxCompetitorsCount set - LeaderboardsResource resource3 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse3 = resource3.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, maxCompetitorsCount); - - jsonString = (String) leaderboardReponse3.getEntity(); - obj= JSONValue.parse(jsonString); - jsonObject = (JSONObject) obj; - jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertEquals((Integer) jsonCompetitors.size(), maxCompetitorsCount); - - Thread.sleep(100); - - // third call for 'all' competitors - LeaderboardsResource resource4 = spyResource(new LeaderboardsResource()); - Response leaderboardReponse4 = resource4.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); - - jsonString = (String) leaderboardReponse4.getEntity(); - obj= JSONValue.parse(jsonString); - jsonObject = (JSONObject) obj; - jsonCompetitors = (JSONArray) jsonObject.get("competitors"); - assertEquals(jsonCompetitors.size(), 4); - } -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javax.ws.rs.core.Response; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Test; + +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.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class LeaderboardsResourceTest extends AbstractJaxRsApiTest { + private Regatta regatta; + private RegattaLeaderboard regattaLeaderboard; + private String boatClassName = "49er"; + private String regattaBaseName = "TestRegatta"; + + @Before + public void setUp() { + super.setUp(); + } + + private void dropAndCreateRegatta() { + service.getDB().dropDatabase(); + List series = new ArrayList(); + List fleets = new ArrayList(); + List raceColumnNames = new ArrayList(); + fleets.add(new FleetImpl("Fleet1")); + fleets.add(new FleetImpl("Fleet2")); + Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, + raceColumnNames, /* trackedRegattaRegistry */null); + series.add(testSeries); + regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClassName), boatClassName, + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); + testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + List competitors = createCompetitors(4); + List fleet1Competitors = competitors.subList(0, 2); + List fleet2Competitors = competitors.subList(2, 4); + + TimePoint now = MillisecondsTimePoint.now(); + + RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); + TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors, regatta); + TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors, regatta); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); + r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); + + regattaLeaderboard = racingEventService.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "Testregatta displayName", new int[] { 3, 5 }); + + } + + @Test + public void testExportEmptyLeaderboardAsJson() throws Exception { + dropAndCreateRegatta(); + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + // resultTimepoint should be null if there are no 'final' results yet + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNull(resultTimePoint); + + JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertTrue(jsonCompetitors.size() == 4); + } + + @Test + public void testExportLeaderboardWithFinalResultStateAsJson() throws Exception { + dropAndCreateRegatta(); + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNull(resultTimePoint); + + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); + + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString2 = (String) leaderboardReponse2.getEntity(); + obj= JSONValue.parse(jsonString2); + jsonObject = (JSONObject) obj; + + resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint); + } + + @Test + public void testExportLeaderboardWithLiveResultStateAsJson() throws Exception { + dropAndCreateRegatta(); + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Live, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Live.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint); + + JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertTrue(jsonCompetitors.size() == 4); + } + + @Test + public void testSmartFutureCacheInExportLeaderboardAsJson() throws Exception { + dropAndCreateRegatta(); + + TimePoint now = MillisecondsTimePoint.now(); + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); + + Thread.sleep(100); + + // call the servlet for the first time + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint); + + // second call - it's expected to get the same result from the cache + Thread.sleep(100); + + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString2 = (String) leaderboardReponse2.getEntity(); + Object obj2= JSONValue.parse(jsonString2); + JSONObject jsonObject2 = (JSONObject) obj2; + + String jsonLeaderboardName2 = (String) jsonObject2.get("name"); + String jsonResultState2 = (String) jsonObject2.get("resultState"); + + assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2)); + assertTrue(jsonResultState.equals(jsonResultState2)); + + TimePoint resultTimePoint2 = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNotNull(resultTimePoint2); + assertTrue(resultTimePoint.equals(resultTimePoint2)); + } + + @Test + public void testExportLeaderboardWithMaxCompetitorsCountAsJson() throws Exception { + dropAndCreateRegatta(); + Integer maxCompetitorsCount = 2; + + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONObject jsonObject = (JSONObject) obj; + + String jsonLeaderboardName = (String) jsonObject.get("name"); + String jsonResultState = (String) jsonObject.get("resultState"); + + assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); + + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); + assertNull(resultTimePoint); + + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); + + // first call for 'all' competitors + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + jsonString = (String) leaderboardReponse2.getEntity(); + obj= JSONValue.parse(jsonString); + jsonObject = (JSONObject) obj; + JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertEquals(jsonCompetitors.size(), 4); + + Thread.sleep(100); + + // second call with maxCompetitorsCount set + LeaderboardsResource resource3 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse3 = resource3.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, maxCompetitorsCount); + + jsonString = (String) leaderboardReponse3.getEntity(); + obj= JSONValue.parse(jsonString); + jsonObject = (JSONObject) obj; + jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertEquals((Integer) jsonCompetitors.size(), maxCompetitorsCount); + + Thread.sleep(100); + + // third call for 'all' competitors + LeaderboardsResource resource4 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse4 = resource4.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + jsonString = (String) leaderboardReponse4.getEntity(); + obj= JSONValue.parse(jsonString); + jsonObject = (JSONObject) obj; + jsonCompetitors = (JSONArray) jsonObject.get("competitors"); + assertEquals(jsonCompetitors.size(), 4); + } +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java index aaecab6e0ac..00b3b158769 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java @@ -1,78 +1,78 @@ -package com.sap.sailing.server.gateway.test.jaxrs; - -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.UUID; - -import javax.ws.rs.core.Response; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.server.gateway.jaxrs.api.RegattasResource; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class RegattasResourceTest extends AbstractJaxRsApiTest { - private String boatClassName = "49er"; - private String regattaName = "TestRegatta"; - - @Before - public void setUp() { - super.setUp(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - final Calendar cal = new GregorianCalendar(); - cal.set(2014, 5, 6, 10, 00); - final TimePoint startDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 16, 00); - final TimePoint endDate = new MillisecondsTimePoint(cal.getTime()); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, - startDate, endDate, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - } - - @Test - public void testGetRegattas() throws Exception { - RegattasResource resource = new RegattasResource(); - RegattasResource spyResource = spyResource(resource); - - Response regattasResponse = spyResource.getRegattas(); - - String jsonString = (String) regattasResponse.getEntity(); - Object obj= JSONValue.parse(jsonString); - JSONArray array= (JSONArray) obj; - - assertTrue(array.size() == 1); - - JSONObject firstElement = (JSONObject) array.get(0); - String jsonName = (String) firstElement.get("name"); - String jsonBoatClass = (String) firstElement.get("boatclass"); - - assertTrue(RegattaImpl.getDefaultName(regattaName, boatClassName).equals(jsonName)); - assertTrue(boatClassName.equals(jsonBoatClass)); - } - -} +package com.sap.sailing.server.gateway.test.jaxrs; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.UUID; + +import javax.ws.rs.core.Response; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.server.gateway.jaxrs.api.RegattasResource; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class RegattasResourceTest extends AbstractJaxRsApiTest { + private String boatClassName = "49er"; + private String regattaName = "TestRegatta"; + + @Before + public void setUp() { + super.setUp(); + List series = new ArrayList(); + List fleets = new ArrayList(); + List raceColumnNames = new ArrayList(); + fleets.add(new FleetImpl("Fleet1")); + fleets.add(new FleetImpl("Fleet2")); + final Calendar cal = new GregorianCalendar(); + cal.set(2014, 5, 6, 10, 00); + final TimePoint startDate = new MillisecondsTimePoint(cal.getTime()); + cal.set(2014, 5, 8, 16, 00); + final TimePoint endDate = new MillisecondsTimePoint(cal.getTime()); + Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, /* isFleetsCanRunInParallel */ true, fleets, raceColumnNames, /* trackedRegattaRegistry */null); + series.add(testSeries); + racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, + startDate, endDate, UUID.randomUUID(), series, /*persistent*/ true, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); + testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); + testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); + } + + @Test + public void testGetRegattas() throws Exception { + RegattasResource resource = new RegattasResource(); + RegattasResource spyResource = spyResource(resource); + + Response regattasResponse = spyResource.getRegattas(); + + String jsonString = (String) regattasResponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONArray array= (JSONArray) obj; + + assertTrue(array.size() == 1); + + JSONObject firstElement = (JSONObject) array.get(0); + String jsonName = (String) firstElement.get("name"); + String jsonBoatClass = (String) firstElement.get("boatclass"); + + assertTrue(RegattaImpl.getDefaultName(regattaName, boatClassName).equals(jsonName)); + assertTrue(boatClassName.equals(jsonBoatClass)); + } + +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java index 9141af9f724..564cedf4cd7 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java @@ -66,7 +66,7 @@ public class RaceGroupFactory { Map> seriesToRaceColumns = getSeriesToRaceColumns(leaderboard); Collection seriesWithRows = new ArrayList<>(); for (Series series : getSeriesIterable(leaderboard, seriesToRaceColumns)) { - seriesWithRows.add(new SeriesWithRowsImpl(series.getName(), series.isMedal(), getRows(series, + seriesWithRows.add(new SeriesWithRowsImpl(series.getName(), series.isMedal(), series.isFleetsCanRunInParallel(), getRows(series, seriesToRaceColumns.get(series)))); } return seriesWithRows; @@ -135,7 +135,7 @@ public class RaceGroupFactory { private Series createDefaultSeries(Iterable fleets) { Series defaultSeries; - defaultSeries = new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, false, + defaultSeries = new SeriesImpl(LeaderboardNameConstants.DEFAULT_SERIES_NAME, false, true, fleets, Collections. emptyList(), null); return defaultSeries; } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java index f886fb2ad9d..dc5a8edcc94 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java @@ -1,117 +1,117 @@ -package com.sap.sailing.server.replication.test; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; - -import com.sap.sailing.domain.abstractlog.AbstractLog; -import com.sap.sailing.domain.abstractlog.AbstractLogEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; -import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; -import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboard; -import com.sap.sse.common.Color; -import com.sap.sse.common.Util; - -public abstract class AbstractLogReplicationTest, EventT extends AbstractLogEvent, VisitorT> - extends AbstractServerReplicationTest { - protected static final String BOAT_CLASS_NAME_49er = "49er"; - - /** - * Uses a new master that loads the existing regatta and race log to append an event to the race log. This will store it to the DB so that if the original - * master re-loads the race log it should see the new race log event. - * @throws Exception - */ - protected void addEventToDB(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent createRaceStatusEvent, String regattaName, String raceColumnName, String fleetName) throws Exception { - final RacingEventServiceImpl temporaryMaster = createNewMaster(); - Regatta regatta = temporaryMaster.getRegatta(new RegattaName(regattaName+" ("+BOAT_CLASS_NAME_49er+")")); - Series series = regatta.getSeries().iterator().next(); - RaceLog masterLog = series.getRaceColumnByName(raceColumnName).getRaceLog(series.getFleetByName(fleetName)); - masterLog.add(createRaceStatusEvent); - } - - /** - * Validation is done based only on the identifier and type of the {@link RaceLogEvent}s. - */ - protected void addAndValidateEventIds(LogT masterLog, LogT replicaLog, @SuppressWarnings("unchecked") EventT... addedEvents) throws InterruptedException { - // 1. Check state of replica after initial load... - assertEqualsOnId(masterLog, replicaLog); - // 2. ... add all incoming events... - for (EventT event : addedEvents) { - masterLog.add(event); - } - // 3. ... and give replication some time to deliver messages. - Thread.sleep(3000); - assertEqualsOnId(masterLog, replicaLog); - } - - /** - * Equality of the events is done based only on the identifier and type. - */ - protected void assertEqualsOnId(LogT masterLog, LogT replicaLog) { - replicaLog.lockForRead(); - try { - masterLog.lockForRead(); - try { - assertEqualsOnId(masterLog.getRawFixes(), replicaLog.getRawFixes()); - } finally { - masterLog.unlockAfterRead(); - } - } finally { - replicaLog.unlockAfterRead(); - } - } - - /** - * Equality of the events is done based only on the {@link RaceLogEvent}'s identifier and type. - */ - private void assertEqualsOnId(Iterable expectedEvents, Iterable actualEvents) { - List expectedCollection = new ArrayList<>(); - Util.addAll(expectedEvents, expectedCollection); - List actualCollection = new ArrayList<>(); - Util.addAll(actualEvents, actualCollection); - assertEquals(Util.size(expectedEvents), Util.size(actualEvents)); - for (int i = 0; i < expectedCollection.size(); i++) { - assertEquals(expectedCollection.get(i).getClass(), actualCollection.get(i).getClass()); - assertEquals(expectedCollection.get(i).getId(), actualCollection.get(i).getId()); - } - } - - protected Regatta setupRegatta(final String regattaName, String seriesName, String fleetName, String boatClassName) { - LinkedHashMap seriesCreationParameters = new LinkedHashMap<>(); - SeriesCreationParametersDTO creationParametersForDefaultSeries = new SeriesCreationParametersDTO( - Arrays.asList(new FleetDTO[] { new FleetDTO(fleetName, 0, Color.BLACK), }), /* medal */false, /* startsWithZero */ - false, /* firstColumnIsNonDiscardableCarryForward */false, /* discardingThresholds */new int[0], /* hasSplitFleetContiguousScoring */ - false); - seriesCreationParameters.put(seriesName, creationParametersForDefaultSeries); - // 1. Install some race column on master... - RegattaCreationParametersDTO regattaCreationParams = new RegattaCreationParametersDTO(seriesCreationParameters); - AddSpecificRegatta addRegattaOperation = new AddSpecificRegatta(regattaName, boatClassName, - /*startDate*/ null, /*endDate*/ null, /* regatta ID */ UUID.randomUUID(), regattaCreationParams, /* persistent */ true, - new LowPoint(), /* default course area ID */ UUID.randomUUID(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN); - return master.apply(addRegattaOperation); - } - - protected FlexibleLeaderboard setupFlexibleLeaderboard(final String leaderboardName) { - CreateFlexibleLeaderboard createTestLeaderboard = new CreateFlexibleLeaderboard(leaderboardName, leaderboardName, new int[] { 19, 44 }, new LowPoint(), null); - FlexibleLeaderboard masterLeaderboard = master.apply(createTestLeaderboard); - return masterLeaderboard; - } - +package com.sap.sailing.server.replication.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; + +import com.sap.sailing.domain.abstractlog.AbstractLog; +import com.sap.sailing.domain.abstractlog.AbstractLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; +import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; +import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboard; +import com.sap.sse.common.Color; +import com.sap.sse.common.Util; + +public abstract class AbstractLogReplicationTest, EventT extends AbstractLogEvent, VisitorT> + extends AbstractServerReplicationTest { + protected static final String BOAT_CLASS_NAME_49er = "49er"; + + /** + * Uses a new master that loads the existing regatta and race log to append an event to the race log. This will store it to the DB so that if the original + * master re-loads the race log it should see the new race log event. + * @throws Exception + */ + protected void addEventToDB(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent createRaceStatusEvent, String regattaName, String raceColumnName, String fleetName) throws Exception { + final RacingEventServiceImpl temporaryMaster = createNewMaster(); + Regatta regatta = temporaryMaster.getRegatta(new RegattaName(regattaName+" ("+BOAT_CLASS_NAME_49er+")")); + Series series = regatta.getSeries().iterator().next(); + RaceLog masterLog = series.getRaceColumnByName(raceColumnName).getRaceLog(series.getFleetByName(fleetName)); + masterLog.add(createRaceStatusEvent); + } + + /** + * Validation is done based only on the identifier and type of the {@link RaceLogEvent}s. + */ + protected void addAndValidateEventIds(LogT masterLog, LogT replicaLog, @SuppressWarnings("unchecked") EventT... addedEvents) throws InterruptedException { + // 1. Check state of replica after initial load... + assertEqualsOnId(masterLog, replicaLog); + // 2. ... add all incoming events... + for (EventT event : addedEvents) { + masterLog.add(event); + } + // 3. ... and give replication some time to deliver messages. + Thread.sleep(3000); + assertEqualsOnId(masterLog, replicaLog); + } + + /** + * Equality of the events is done based only on the identifier and type. + */ + protected void assertEqualsOnId(LogT masterLog, LogT replicaLog) { + replicaLog.lockForRead(); + try { + masterLog.lockForRead(); + try { + assertEqualsOnId(masterLog.getRawFixes(), replicaLog.getRawFixes()); + } finally { + masterLog.unlockAfterRead(); + } + } finally { + replicaLog.unlockAfterRead(); + } + } + + /** + * Equality of the events is done based only on the {@link RaceLogEvent}'s identifier and type. + */ + private void assertEqualsOnId(Iterable expectedEvents, Iterable actualEvents) { + List expectedCollection = new ArrayList<>(); + Util.addAll(expectedEvents, expectedCollection); + List actualCollection = new ArrayList<>(); + Util.addAll(actualEvents, actualCollection); + assertEquals(Util.size(expectedEvents), Util.size(actualEvents)); + for (int i = 0; i < expectedCollection.size(); i++) { + assertEquals(expectedCollection.get(i).getClass(), actualCollection.get(i).getClass()); + assertEquals(expectedCollection.get(i).getId(), actualCollection.get(i).getId()); + } + } + + protected Regatta setupRegatta(final String regattaName, String seriesName, String fleetName, String boatClassName) { + LinkedHashMap seriesCreationParameters = new LinkedHashMap<>(); + SeriesCreationParametersDTO creationParametersForDefaultSeries = new SeriesCreationParametersDTO( + Arrays.asList(new FleetDTO[] { new FleetDTO(fleetName, 0, Color.BLACK), }), /* medal */false, /* fleetsCanRunInParallel */ true, /* startsWithZero */ + false, /* firstColumnIsNonDiscardableCarryForward */false, /* discardingThresholds */new int[0], /* hasSplitFleetContiguousScoring */ + false); + seriesCreationParameters.put(seriesName, creationParametersForDefaultSeries); + // 1. Install some race column on master... + RegattaCreationParametersDTO regattaCreationParams = new RegattaCreationParametersDTO(seriesCreationParameters); + AddSpecificRegatta addRegattaOperation = new AddSpecificRegatta(regattaName, boatClassName, + /*startDate*/ null, /*endDate*/ null, /* regatta ID */ UUID.randomUUID(), regattaCreationParams, /* persistent */ true, + new LowPoint(), /* default course area ID */ UUID.randomUUID(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN); + return master.apply(addRegattaOperation); + } + + protected FlexibleLeaderboard setupFlexibleLeaderboard(final String leaderboardName) { + CreateFlexibleLeaderboard createTestLeaderboard = new CreateFlexibleLeaderboard(leaderboardName, leaderboardName, new int[] { 19, 44 }, new LowPoint(), null); + FlexibleLeaderboard masterLeaderboard = master.apply(createTestLeaderboard); + return masterLeaderboard; + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java index c9a660ffc08..62ed8b2751e 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java @@ -185,11 +185,11 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String baseEventName = "Kiel Week 2012"; final String boatClassName = "49er"; final List emptyRaceColumnNamesList = Collections.emptyList(); - Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, + Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); - Series finals = new SeriesImpl("Finals", /* isMedal */ false, + Series finals = new SeriesImpl("Finals", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Gold", 1), new FleetImpl("Silver", 2) }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); - Series medal = new SeriesImpl("Medal", /* isMedal */ true, + Series medal = new SeriesImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Medal") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification, finals, medal }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); @@ -224,7 +224,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String baseEventName = "Extreme Sailing Series 2020"; final String boatClassName = "Extreme40"; final List emptyRaceColumnNamesList = Collections.emptyList(); - Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, + Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); @@ -238,10 +238,10 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { assertEquals("Qualification", replicatedQualification.getName()); assertEquals(2, Util.size(replicatedQualification.getFleets())); assertFalse(seriesIter.hasNext()); - Series finals = new SeriesImpl("Finals", /* isMedal */ false, + Series finals = new SeriesImpl("Finals", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Gold", 1) }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); FleetDTO finalsGoldFleet = new FleetDTO("Gold", 1, Color.GRAY); - master.apply(new UpdateSeries(masterRegatta.getRegattaIdentifier(), finals.getName(), finals.getName(), finals.isMedal(), + master.apply(new UpdateSeries(masterRegatta.getRegattaIdentifier(), finals.getName(), finals.getName(), finals.isMedal(), finals.isFleetsCanRunInParallel(), new int[] {}, finals.isStartsWithZeroScore(), finals.isFirstColumnIsNonDiscardableCarryForward(), finals.hasSplitFleetContiguousScoring(), Arrays.asList(new FleetDTO[] { finalsGoldFleet }))); @@ -267,7 +267,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final String baseEventName = "Extreme Sailing Series 2021"; final String boatClassName = "Extreme40"; final List emptyRaceColumnNamesList = Collections.emptyList(); - Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, + Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); @@ -279,7 +279,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { Series replicatedQualification = seriesIter.next(); assertEquals("Qualification", replicatedQualification.getName()); master.apply(new UpdateSeries(masterRegatta.getRegattaIdentifier(), qualification.getName(), "Simons Quali", - qualification.isMedal(), + qualification.isMedal(), qualification.isFleetsCanRunInParallel(), new int[] {}, qualification.isStartsWithZeroScore(), qualification.isFirstColumnIsNonDiscardableCarryForward(), qualification.hasSplitFleetContiguousScoring(), Arrays.asList(new FleetDTO[] { }))); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java index 455cd856500..bf6f4c88ca5 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/impl/MediaMasterDataExportTest.java @@ -1,190 +1,190 @@ -package com.sap.sailing.server.impl; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -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.UUID; - -import org.junit.Test; - -import com.sap.sailing.domain.base.BoatClass; -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.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.CourseAreaImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.BoatClassMasterdata; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.media.MediaTrack; -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.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -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.LowPoint; -import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.masterdataimport.TopLevelMasterData; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsDurationImpl; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.media.MimeType; - -public class MediaMasterDataExportTest { - - static final String matchingRegattaName = "Matching Regatta"; - static final String missingRegattaName = "Missing Regatta"; - static final String matchingRace1 = "matching race 1"; - static final String matchingRace2 = "matching race 2"; - static final String matchingRace3 = "matching race 3"; - static final String missingRace1 = "missing race 1"; - static final String missingRace2 = "missing race 2"; - static final String missingRace3 = "missing race 3"; - - static final boolean displayGroupsInReverseOrder = true; - static final ThresholdBasedResultDiscardingRule resultDiscardingRule = new ThresholdBasedResultDiscardingRuleImpl(new int[0]); - static final BoatClass boatClass = new BoatClassImpl("boat class name", BoatClassMasterdata._12M); - static final TimePoint startDate = MillisecondsTimePoint.now(); - static final TimePoint endDate = startDate.plus(MillisecondsDurationImpl.ONE_DAY); - static final boolean isMedal = false; - static final Fleet regattaFleet = new FleetImpl("fleet name"); - static final boolean persistent = false; - static final ScoringScheme scoringScheme = new LowPoint(); - static final CourseArea courseArea = new CourseAreaImpl("Course Area", UUID.randomUUID()); - static final Serializable regatteId = "regatta id"; - - @Test - public void testTrackWithAssignedRaceButEmptyLeaderboardGroup() { - TimePoint startTime = null; - Duration duration = null; - MimeType mimeType = MimeType.mp4; - Collection allMediaTracks = Arrays.asList( - new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), - new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), - new MediaTrack("dbId 3", "title 1 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), - new MediaTrack("dbId 4", "title 2 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))) - ); - Collection regattaRaces = Collections.emptyList(); - Collection flexibleRaces = Collections.emptyList(); - TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); - Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); - assertEquals(0, filteredMediaTracks.size()); - } - - @Test - public void testTracksWithAssignedRaceAndMatchingRegattaInLeaderboardGroup() { - TimePoint startTime = null; - Duration duration = null; - MimeType mimeType = MimeType.mp4; - Collection allMediaTracks = Arrays.asList( - new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), - new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), - new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), - new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), - new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) - ); - Collection regattaRaces = Arrays.asList( - new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) - ); - Collection flexibleRaces = Collections.emptyList(); - TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); - Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); - assertEquals(3, filteredMediaTracks.size()); - } - - @Test - public void testTrackWithAssignedRaceAndMatchingFlexibleLeaderboardInLeaderboardGroup() { - TimePoint startTime = null; - Duration duration = null; - MimeType mimeType = MimeType.mp4; - Collection allMediaTracks = Arrays.asList( - new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), - new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), - new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), - new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), - new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) - ); - Collection regattaRaces = Collections.emptyList(); - Collection flexibleRaces = Arrays.asList( - new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), - new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) - ); - TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); - Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); - assertEquals(3, filteredMediaTracks.size()); - } - - private TopLevelMasterData createTopLevelMasterData(Collection regattaRaces, Collection flexibleRaces, Collection allMediaTracks) { - Set raceColumnNames = new HashSet<>(); - for (RaceIdentifier regattaAndRaceIdentifier : regattaRaces) { - raceColumnNames.add(regattaAndRaceIdentifier.getRaceName()); - } - Regatta regatta = createTestRegatta("regatta name", raceColumnNames); - RegattaLeaderboard regattaLeaderboard = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); - assignRacesToRegattaLeaderboardColumns(regattaLeaderboard, regattaRaces); - - FlexibleLeaderboard flexibleLeaderboard = new FlexibleLeaderboardImpl("flexible leaderboard", resultDiscardingRule, scoringScheme , courseArea); - for (RaceIdentifier regattaAndRaceIdentifier : flexibleRaces) { - RaceColumn raceColumn = flexibleLeaderboard.addRaceColumn(regattaAndRaceIdentifier.getRaceName(), isMedal); - Fleet defaultFleet = raceColumn.getFleets().iterator().next(); - raceColumn.setRaceIdentifier(defaultFleet, regattaAndRaceIdentifier); - } - - - List leaderboards = Arrays.asList(regattaLeaderboard, flexibleLeaderboard); - LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("name", "description", "displayName", displayGroupsInReverseOrder , leaderboards); - Set groupsToExport = Collections.singleton(leaderboardGroup); - Iterable allEvents = Collections.emptyList(); - Map regattaForRaceIdString = Collections.emptyMap(); - GPSFixStore gpsFixStore = mock(GPSFixStore.class); - boolean exportWind = false; - TopLevelMasterData topLevelMasterData = new TopLevelMasterData(groupsToExport , allEvents , regattaForRaceIdString, allMediaTracks , gpsFixStore , exportWind, new HashMap<>()); - return topLevelMasterData; - } - - public static void assignRacesToRegattaLeaderboardColumns(RegattaLeaderboard leaderboard, Collection raceIdentifiers) { - Iterator regattaRacesIterator = raceIdentifiers.iterator(); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - raceColumn.setRaceIdentifier(regattaFleet, regattaRacesIterator.next()); - } - } - - public static RegattaImpl createTestRegatta(String regattaName, Iterable raceColumnNames) { - Iterable regattaFleets = Collections.singleton(regattaFleet); - TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class); - Series series = new SeriesImpl("series name", isMedal, regattaFleets, raceColumnNames, trackedRegattaRegistry); - Iterable regattaSeries = Collections.singleton(series); - return new RegattaImpl(regattaName, boatClass, startDate, endDate, regattaSeries, persistent, scoringScheme, regatteId , courseArea, OneDesignRankingMetric::new); - } - - - -} +package com.sap.sailing.server.impl; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +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.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.base.BoatClass; +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.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.BoatClassMasterdata; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.media.MediaTrack; +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.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +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.LowPoint; +import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.masterdataimport.TopLevelMasterData; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsDurationImpl; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.MimeType; + +public class MediaMasterDataExportTest { + + static final String matchingRegattaName = "Matching Regatta"; + static final String missingRegattaName = "Missing Regatta"; + static final String matchingRace1 = "matching race 1"; + static final String matchingRace2 = "matching race 2"; + static final String matchingRace3 = "matching race 3"; + static final String missingRace1 = "missing race 1"; + static final String missingRace2 = "missing race 2"; + static final String missingRace3 = "missing race 3"; + + static final boolean displayGroupsInReverseOrder = true; + static final ThresholdBasedResultDiscardingRule resultDiscardingRule = new ThresholdBasedResultDiscardingRuleImpl(new int[0]); + static final BoatClass boatClass = new BoatClassImpl("boat class name", BoatClassMasterdata._12M); + static final TimePoint startDate = MillisecondsTimePoint.now(); + static final TimePoint endDate = startDate.plus(MillisecondsDurationImpl.ONE_DAY); + static final boolean isMedal = false; + static final Fleet regattaFleet = new FleetImpl("fleet name"); + static final boolean persistent = false; + static final ScoringScheme scoringScheme = new LowPoint(); + static final CourseArea courseArea = new CourseAreaImpl("Course Area", UUID.randomUUID()); + static final Serializable regatteId = "regatta id"; + + @Test + public void testTrackWithAssignedRaceButEmptyLeaderboardGroup() { + TimePoint startTime = null; + Duration duration = null; + MimeType mimeType = MimeType.mp4; + Collection allMediaTracks = Arrays.asList( + new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), + new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), + new MediaTrack("dbId 3", "title 1 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), + new MediaTrack("dbId 4", "title 2 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))) + ); + Collection regattaRaces = Collections.emptyList(); + Collection flexibleRaces = Collections.emptyList(); + TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); + Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); + assertEquals(0, filteredMediaTracks.size()); + } + + @Test + public void testTracksWithAssignedRaceAndMatchingRegattaInLeaderboardGroup() { + TimePoint startTime = null; + Duration duration = null; + MimeType mimeType = MimeType.mp4; + Collection allMediaTracks = Arrays.asList( + new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), + new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), + new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), + new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), + new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) + ); + Collection regattaRaces = Arrays.asList( + new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) + ); + Collection flexibleRaces = Collections.emptyList(); + TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); + Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); + assertEquals(3, filteredMediaTracks.size()); + } + + @Test + public void testTrackWithAssignedRaceAndMatchingFlexibleLeaderboardInLeaderboardGroup() { + TimePoint startTime = null; + Duration duration = null; + MimeType mimeType = MimeType.mp4; + Collection allMediaTracks = Arrays.asList( + new MediaTrack("dbId 1", "title 1 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))), + new MediaTrack("dbId 2", "title 2 for existing one regatta and race and one missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace2), new RegattaNameAndRaceName(matchingRegattaName, matchingRace2)))), + new MediaTrack("dbId 3", "title 3 for missing regatta", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(missingRegattaName, matchingRace1))), + new MediaTrack("dbId 4", "title 4 for missing regatta", "url", startTime, duration, mimeType, new HashSet(Arrays.asList(new RegattaNameAndRaceName(missingRegattaName, matchingRace3), new RegattaNameAndRaceName(matchingRegattaName, missingRace1)))), + new MediaTrack("dbId 5", "title 5 for existing regatta and race", "url", startTime, duration, mimeType, Collections.singleton(new RegattaNameAndRaceName(matchingRegattaName, matchingRace1))) + ); + Collection regattaRaces = Collections.emptyList(); + Collection flexibleRaces = Arrays.asList( + new RegattaNameAndRaceName(matchingRegattaName, matchingRace1), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace2), + new RegattaNameAndRaceName(matchingRegattaName, matchingRace3) + ); + TopLevelMasterData topLevelMasterData = createTopLevelMasterData(regattaRaces , flexibleRaces, allMediaTracks); + Collection filteredMediaTracks = topLevelMasterData.getFilteredMediaTracks(); + assertEquals(3, filteredMediaTracks.size()); + } + + private TopLevelMasterData createTopLevelMasterData(Collection regattaRaces, Collection flexibleRaces, Collection allMediaTracks) { + Set raceColumnNames = new HashSet<>(); + for (RaceIdentifier regattaAndRaceIdentifier : regattaRaces) { + raceColumnNames.add(regattaAndRaceIdentifier.getRaceName()); + } + Regatta regatta = createTestRegatta("regatta name", raceColumnNames); + RegattaLeaderboard regattaLeaderboard = new RegattaLeaderboardImpl(regatta, resultDiscardingRule); + assignRacesToRegattaLeaderboardColumns(regattaLeaderboard, regattaRaces); + + FlexibleLeaderboard flexibleLeaderboard = new FlexibleLeaderboardImpl("flexible leaderboard", resultDiscardingRule, scoringScheme , courseArea); + for (RaceIdentifier regattaAndRaceIdentifier : flexibleRaces) { + RaceColumn raceColumn = flexibleLeaderboard.addRaceColumn(regattaAndRaceIdentifier.getRaceName(), isMedal); + Fleet defaultFleet = raceColumn.getFleets().iterator().next(); + raceColumn.setRaceIdentifier(defaultFleet, regattaAndRaceIdentifier); + } + + + List leaderboards = Arrays.asList(regattaLeaderboard, flexibleLeaderboard); + LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("name", "description", "displayName", displayGroupsInReverseOrder , leaderboards); + Set groupsToExport = Collections.singleton(leaderboardGroup); + Iterable allEvents = Collections.emptyList(); + Map regattaForRaceIdString = Collections.emptyMap(); + GPSFixStore gpsFixStore = mock(GPSFixStore.class); + boolean exportWind = false; + TopLevelMasterData topLevelMasterData = new TopLevelMasterData(groupsToExport , allEvents , regattaForRaceIdString, allMediaTracks , gpsFixStore , exportWind, new HashMap<>()); + return topLevelMasterData; + } + + public static void assignRacesToRegattaLeaderboardColumns(RegattaLeaderboard leaderboard, Collection raceIdentifiers) { + Iterator regattaRacesIterator = raceIdentifiers.iterator(); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + raceColumn.setRaceIdentifier(regattaFleet, regattaRacesIterator.next()); + } + } + + public static RegattaImpl createTestRegatta(String regattaName, Iterable raceColumnNames) { + Iterable regattaFleets = Collections.singleton(regattaFleet); + TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class); + Series series = new SeriesImpl("series name", isMedal, /* isFleetsCanRunInParallel */ true, regattaFleets, raceColumnNames, trackedRegattaRegistry); + Iterable regattaSeries = Collections.singleton(series); + return new RegattaImpl(regattaName, boatClass, startDate, endDate, regattaSeries, persistent, scoringScheme, regatteId , courseArea, OneDesignRankingMetric::new); + } + + + +} diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index c6da147d75f..ab6c392a302 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -204,7 +204,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl(testFleet1Name); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -467,7 +467,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -626,7 +626,7 @@ public class MasterDataImportTest { List fleets = new ArrayList(); FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta(RegattaImpl.getDefaultName("testRegatta", "29er"), "29er", /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ @@ -789,7 +789,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -949,7 +949,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -1063,7 +1063,7 @@ public class MasterDataImportTest { List fleetsNotToOverride = new ArrayList(); FleetImpl testFleet1NotToOverride = new FleetImpl("testFleet1"); fleetsNotToOverride.add(testFleet1NotToOverride); - seriesNotToOverride.add(new SeriesImpl("testSeries", false, fleetsNotToOverride, emptyRaceColumnNamesList, + seriesNotToOverride.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleetsNotToOverride, emptyRaceColumnNamesList, destService)); Regatta regattaNotToOverride = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ @@ -1151,7 +1151,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -1261,7 +1261,7 @@ public class MasterDataImportTest { List fleetsToOverride = new ArrayList(); FleetImpl testFleet1ToOverride = new FleetImpl("testFleet1"); fleetsToOverride.add(testFleet1ToOverride); - seriesToOverride.add(new SeriesImpl("testSeries", false, fleetsToOverride, emptyRaceColumnNamesList, + seriesToOverride.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleetsToOverride, emptyRaceColumnNamesList, destService)); Regatta regattaToOverride = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ @@ -1452,7 +1452,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( @@ -1587,7 +1587,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -1833,7 +1833,7 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl("testFleet1"); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - series.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, @@ -2063,8 +2063,8 @@ public class MasterDataImportTest { FleetImpl testFleet1 = new FleetImpl(testFleet1Name); fleets.add(testFleet1); fleets.add(new FleetImpl("testFleet2")); - seriesOnSource.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); - seriesOnTarget.add(new SeriesImpl("testSeries", false, fleets, emptyRaceColumnNamesList, sourceService)); + seriesOnSource.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); + seriesOnTarget.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java index 94b5884d201..fa6a3ed3f87 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java @@ -1,322 +1,322 @@ -package com.sap.sailing.server.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.LeaderboardGroupBase; -import com.sap.sailing.domain.base.LeaderboardSearchResult; -import com.sap.sailing.domain.base.LeaderboardSearchResultBase; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Venue; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.CourseAreaImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.common.RankingMetrics; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; -import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.impl.LowPoint; -import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; -import com.sap.sailing.domain.test.AbstractTracTracLiveTest; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.impl.EmptyWindStore; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; -import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; -import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.LeaderboardSearchResultJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; -import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; -import com.sap.sailing.server.operationaltransformation.AddRaceDefinition; -import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; -import com.sap.sailing.server.operationaltransformation.CreateEvent; -import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; -import com.sap.sailing.server.operationaltransformation.CreateTrackedRace; -import com.sap.sailing.server.operationaltransformation.RemoveEvent; -import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; -import com.sap.sailing.server.operationaltransformation.RemoveRegatta; -import com.sap.sse.common.Color; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.search.KeywordQuery; -import com.sap.sse.common.search.Result; -import com.sap.sse.shared.media.ImageDescriptor; -import com.sap.sse.shared.media.VideoDescriptor; - -public class SearchServiceTest { - private RacingEventService server; - private Venue kiel; - private Venue flensburg; - private Event pfingstbusch; - private Regatta pfingstbusch29er; - private Competitor hassoPlattner; - private Competitor alexanderRies; - private Competitor antonKoch; - private Competitor tobiasSchadewaldt; - private Competitor philippBuhl; - private Competitor dennisGehrlein; - private Regatta pfingstbusch470; - private Regatta aalRegatta; - private DynamicTrackedRace pfingstbusch29erTrackedR1; - private DynamicTrackedRace pfingstbusch29erTrackedR3; - private Event aalEvent; - private DynamicTrackedRace pfingstbusch470TrackedR1; - private DynamicTrackedRace pfingstbusch470TrackedR2; - private DynamicTrackedRace aalOrcTrackedR1; - private DynamicTrackedRace aalOrcTrackedR2; - - @Before - public void setUp() { - server = new RacingEventServiceImpl(); - List allEvents = new ArrayList<>(); - Util.addAll(server.getAllEvents(), allEvents); - for (Event e : allEvents) { - server.apply(new RemoveEvent(e.getId())); - } - Map allLeaderboards = new HashMap<>(server.getLeaderboards()); - for (String leaderboardName : allLeaderboards.keySet()) { - server.apply(new RemoveLeaderboard(leaderboardName)); - } - server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (29er)"))); - server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (470)"))); - server.apply(new RemoveRegatta(new RegattaName("Aalregatta (ORC)"))); - final Calendar cal = new GregorianCalendar(); - cal.set(2014, 5, 6, 10, 00); - final TimePoint pfingstbuschStartDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 16, 00); - final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime()); - pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */ - "Kiel", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /* sailorsInfoWebsiteURLAsString */ null, - /* images */Collections. emptyList(), /* videos */Collections. emptyList())); - kiel = pfingstbusch.getVenue(); - final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID()); - kiel.addCourseArea(kielAlpha); - final CourseAreaImpl kielBravo = new CourseAreaImpl("Bravo", UUID.randomUUID()); - kiel.addCourseArea(kielBravo); - final LinkedHashMap seriesCreationParams = new LinkedHashMap(); - seriesCreationParams.put("Default", - new SeriesCreationParametersDTO(Collections.singletonList(new FleetDTO("Default", /* order */-1, Color.RED)), - /* medal */false, /* startsWithZero */false, /* firstColumnIsNonDiscardableCarryForward */false, - /* discardingThresholds */null, /* hasSplitFleetContiguousScoring */false)); - pfingstbusch29er = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "29er"), "29er", - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), - new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), kielAlpha.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); - server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R1")); - server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R2")); - server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R3")); - RegattaLeaderboard pfingstbusch29erLeaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch29er.getRegattaIdentifier(), - /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); - pfingstbusch470 = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "470"), "470", - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), kielBravo.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); - server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R1")); - server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R2")); - server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R3")); - RegattaLeaderboard pfingstbusch470Leaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch470.getRegattaIdentifier(), - /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); - cal.set(2014, 5, 7, 10, 00); - final TimePoint aalStartDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 18, 00); - final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime()); - aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */ - "Flensburg", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /*sailorsInfoWebsiteURLAsString */ null, - /* images */Collections. emptyList(), /* videos */Collections. emptyList())); - flensburg = aalEvent.getVenue(); - final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID()); - flensburg.addCourseArea(flensburgStandard); - aalRegatta = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Aalregatta", "ORC"), "ORC", - /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), - new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), flensburgStandard.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); - server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R1")); - server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R2")); - RegattaLeaderboard aalRegattaLeaderboard = server.apply(new CreateRegattaLeaderboard(aalRegatta.getRegattaIdentifier(), - /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); - hassoPlattner = AbstractTracTracLiveTest.createCompetitor("Hasso Plattner"); - alexanderRies = AbstractTracTracLiveTest.createCompetitor("Alexander Ries"); - antonKoch = AbstractTracTracLiveTest.createCompetitor("Anton Koch"); - tobiasSchadewaldt = AbstractTracTracLiveTest.createCompetitor("Tobias Schadewaldt"); - philippBuhl = AbstractTracTracLiveTest.createCompetitor("Philipp Buhl"); - dennisGehrlein = AbstractTracTracLiveTest.createCompetitor("Dennis Gehrlein"); - final RaceDefinitionImpl pfingstbusch29erR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), - Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); - final RaceDefinitionImpl pfingstbusch29erR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), - Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); - final RaceDefinitionImpl pfingstbusch29erR3 = new RaceDefinitionImpl("R3", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), - Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); - server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR1)); - server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR2)); - server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR3)); - // track only R1 and R3 - pfingstbusch29erTrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch29erTrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(1)); - pfingstbusch29erLeaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch29erTrackedR1); - pfingstbusch29erTrackedR3 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR3), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch29erLeaderboard.getRaceColumnByName("R3").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R3").getFleetByName("Default"), pfingstbusch29erTrackedR3); - - final RaceDefinitionImpl pfingstbush470R1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), - Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); - final RaceDefinitionImpl pfingstbush470R2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), - Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); - server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R1)); - server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R2)); - // track only R1 and R2 - pfingstbusch470TrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch470TrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(2)); // starts later than 29er - pfingstbusch470Leaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch470TrackedR1); - pfingstbusch470TrackedR2 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - pfingstbusch470Leaderboard.getRaceColumnByName("R2").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R2").getFleetByName("Default"), pfingstbusch470TrackedR2); - - final RaceDefinitionImpl aalOrcR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), - Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); - final RaceDefinitionImpl aalOrcR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), - Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); - server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR1)); - server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR2)); - // track only R1 and R2 - aalOrcTrackedR1 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - aalOrcTrackedR1.setStartOfTrackingReceived(aalStartDate); - aalRegattaLeaderboard.getRaceColumnByName("R1").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), aalOrcTrackedR1); - aalOrcTrackedR2 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); - aalRegattaLeaderboard.getRaceColumnByName("R2").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R2").getFleetByName("Default"), aalOrcTrackedR2); - } - - @Test - public void testSetup() { - assertNotNull(pfingstbusch29er); - assertNotNull(pfingstbusch29erTrackedR1); - assertNotNull(pfingstbusch29erTrackedR3); - assertNotNull(pfingstbusch470TrackedR1); - assertNotNull(pfingstbusch470TrackedR2); - assertNotNull(aalEvent); - assertNotNull(aalRegatta); - assertNotNull(aalOrcTrackedR1); - assertNotNull(aalOrcTrackedR2); - } - - @Test - public void testSimpleSearchByCompetitorName() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Tobi" }))); - assertEquals(1, Util.size(searchResults.getHits())); - Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); - assertSame(pfingstbusch29er, foundRegatta); - } - - @Test - public void testSimpleSearchByCompetitorName2() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Hasso" }))); - assertEquals(1, Util.size(searchResults.getHits())); - Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); - assertSame(aalRegatta, foundRegatta); - } - - @Test - public void testSimpleSearchByVenueName() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Flensburg" }))); - assertEquals(1, Util.size(searchResults.getHits())); - Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); - assertSame(aalRegatta, foundRegatta); - } - - @Test - public void testSimpleSearchByVenueName2() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Kiel" }))); - assertEquals(2, Util.size(searchResults.getHits())); - final Iterator iterator = searchResults.getHits().iterator(); - final LeaderboardSearchResult firstMatch = iterator.next(); - Regatta firstFoundRegatta = firstMatch.getRegatta(); - assertSame(pfingstbusch29er, firstFoundRegatta); - assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); - final LeaderboardSearchResult secondMatch = iterator.next(); - Regatta secondFoundRegatta = secondMatch.getRegatta(); - assertSame(pfingstbusch470, secondFoundRegatta); - assertSame(pfingstbusch, secondMatch.getEvents().iterator().next()); - } - - @Test - public void testMultipleMatchesSortedCorrectly() { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); - assertEquals(2, Util.size(searchResults.getHits())); - final Iterator iter = searchResults.getHits().iterator(); - final LeaderboardSearchResult firstMatch = iter.next(); - Regatta earlierStartRegatta = firstMatch.getRegatta(); - assertSame(pfingstbusch470, earlierStartRegatta); - assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); - final LeaderboardSearchResult secondMatch = iter.next(); - Regatta laterStartRegatta = secondMatch.getRegatta(); - assertSame(aalRegatta, laterStartRegatta); - assertSame(aalEvent, secondMatch.getEvents().iterator().next()); - } - - @Test - public void testSerializeAndDeserializeSearchResult() throws JsonDeserializationException { - Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); - LeaderboardGroupBaseJsonSerializer leaderboardGroupBaseJsonSerializer = new LeaderboardGroupBaseJsonSerializer(); - LeaderboardSearchResultJsonSerializer serializer = new LeaderboardSearchResultJsonSerializer(new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), - leaderboardGroupBaseJsonSerializer), - leaderboardGroupBaseJsonSerializer); - LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer(); - LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer( - new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), - leaderboardGroupBaseJsonDeserializer), leaderboardGroupBaseJsonDeserializer); - final LeaderboardSearchResult expected = searchResults.getHits().iterator().next(); - LeaderboardSearchResultBase deserialized = deserializer.deserialize(serializer.serialize(expected)); - assertEquals("Pfingstbusch (470)", deserialized.getRegattaName()); - assertEquals(expected.getEvents().iterator().next().getName(), deserialized.getEvents().iterator().next().getName()); - assertEquals(expected.getEvents().iterator().next().getId(), deserialized.getEvents().iterator().next().getId()); - assertEquals(expected.getEvents().iterator().next().getStartDate(), deserialized.getEvents().iterator().next().getStartDate()); - assertEquals(expected.getEvents().iterator().next().getEndDate(), deserialized.getEvents().iterator().next().getEndDate()); - assertEquals(expected.getEvents().iterator().next().getVenue().getName(), deserialized.getEvents().iterator().next().getVenue().getName()); - Iterator expectedLGs = expected.getLeaderboardGroups().iterator(); - Iterator deserializedLGs = deserialized.getLeaderboardGroups().iterator(); - while (expectedLGs.hasNext()) { - assertTrue(deserializedLGs.hasNext()); - LeaderboardGroup expectedLG = expectedLGs.next(); - LeaderboardGroupBase deserializedLG = deserializedLGs.next(); - assertEquals(expectedLG.getId(), deserializedLG.getId()); - assertEquals(expectedLG.getName(), deserializedLG.getName()); - assertEquals(expectedLG.getDescription(), deserializedLG.getDescription()); - } - } -} +package com.sap.sailing.server.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.LeaderboardGroupBase; +import com.sap.sailing.domain.base.LeaderboardSearchResult; +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; +import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.test.AbstractTracTracLiveTest; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; +import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.LeaderboardSearchResultJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; +import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; +import com.sap.sailing.server.operationaltransformation.AddRaceDefinition; +import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; +import com.sap.sailing.server.operationaltransformation.CreateEvent; +import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; +import com.sap.sailing.server.operationaltransformation.CreateTrackedRace; +import com.sap.sailing.server.operationaltransformation.RemoveEvent; +import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; +import com.sap.sailing.server.operationaltransformation.RemoveRegatta; +import com.sap.sse.common.Color; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.common.search.Result; +import com.sap.sse.shared.media.ImageDescriptor; +import com.sap.sse.shared.media.VideoDescriptor; + +public class SearchServiceTest { + private RacingEventService server; + private Venue kiel; + private Venue flensburg; + private Event pfingstbusch; + private Regatta pfingstbusch29er; + private Competitor hassoPlattner; + private Competitor alexanderRies; + private Competitor antonKoch; + private Competitor tobiasSchadewaldt; + private Competitor philippBuhl; + private Competitor dennisGehrlein; + private Regatta pfingstbusch470; + private Regatta aalRegatta; + private DynamicTrackedRace pfingstbusch29erTrackedR1; + private DynamicTrackedRace pfingstbusch29erTrackedR3; + private Event aalEvent; + private DynamicTrackedRace pfingstbusch470TrackedR1; + private DynamicTrackedRace pfingstbusch470TrackedR2; + private DynamicTrackedRace aalOrcTrackedR1; + private DynamicTrackedRace aalOrcTrackedR2; + + @Before + public void setUp() { + server = new RacingEventServiceImpl(); + List allEvents = new ArrayList<>(); + Util.addAll(server.getAllEvents(), allEvents); + for (Event e : allEvents) { + server.apply(new RemoveEvent(e.getId())); + } + Map allLeaderboards = new HashMap<>(server.getLeaderboards()); + for (String leaderboardName : allLeaderboards.keySet()) { + server.apply(new RemoveLeaderboard(leaderboardName)); + } + server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (29er)"))); + server.apply(new RemoveRegatta(new RegattaName("Pfingstbusch (470)"))); + server.apply(new RemoveRegatta(new RegattaName("Aalregatta (ORC)"))); + final Calendar cal = new GregorianCalendar(); + cal.set(2014, 5, 6, 10, 00); + final TimePoint pfingstbuschStartDate = new MillisecondsTimePoint(cal.getTime()); + cal.set(2014, 5, 8, 16, 00); + final TimePoint pfingstbuschEndDate = new MillisecondsTimePoint(cal.getTime()); + pfingstbusch = server.apply(new CreateEvent("Pfingsbusch", /* eventDescription */ null, pfingstbuschStartDate, pfingstbuschEndDate, /* isPublic */ + "Kiel", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /* sailorsInfoWebsiteURLAsString */ null, + /* images */Collections. emptyList(), /* videos */Collections. emptyList())); + kiel = pfingstbusch.getVenue(); + final CourseAreaImpl kielAlpha = new CourseAreaImpl("Alpha", UUID.randomUUID()); + kiel.addCourseArea(kielAlpha); + final CourseAreaImpl kielBravo = new CourseAreaImpl("Bravo", UUID.randomUUID()); + kiel.addCourseArea(kielBravo); + final LinkedHashMap seriesCreationParams = new LinkedHashMap(); + seriesCreationParams.put("Default", + new SeriesCreationParametersDTO(Collections.singletonList(new FleetDTO("Default", /* order */-1, Color.RED)), + /* medal */false, /* fleetsCanRunInParallel */ true, /* startsWithZero */false, /* firstColumnIsNonDiscardableCarryForward */false, + /* discardingThresholds */null, /* hasSplitFleetContiguousScoring */false)); + pfingstbusch29er = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "29er"), "29er", + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), + new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ + true, new LowPoint(), kielAlpha.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); + server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R1")); + server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R2")); + server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R3")); + RegattaLeaderboard pfingstbusch29erLeaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch29er.getRegattaIdentifier(), + /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); + pfingstbusch470 = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "470"), "470", + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ + true, new LowPoint(), kielBravo.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); + server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R1")); + server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R2")); + server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R3")); + RegattaLeaderboard pfingstbusch470Leaderboard = server.apply(new CreateRegattaLeaderboard(pfingstbusch470.getRegattaIdentifier(), + /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); + cal.set(2014, 5, 7, 10, 00); + final TimePoint aalStartDate = new MillisecondsTimePoint(cal.getTime()); + cal.set(2014, 5, 8, 18, 00); + final TimePoint aalEndDate = new MillisecondsTimePoint(cal.getTime()); + aalEvent = server.apply(new CreateEvent("Aalregatta", /* eventDescription */ null, aalStartDate, aalEndDate, /* isPublic */ + "Flensburg", true, UUID.randomUUID(), /* officialWebsiteURLAsString */ null, /*sailorsInfoWebsiteURLAsString */ null, + /* images */Collections. emptyList(), /* videos */Collections. emptyList())); + flensburg = aalEvent.getVenue(); + final CourseAreaImpl flensburgStandard = new CourseAreaImpl("Standard", UUID.randomUUID()); + flensburg.addCourseArea(flensburgStandard); + aalRegatta = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Aalregatta", "ORC"), "ORC", + /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), + new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ + true, new LowPoint(), flensburgStandard.getId(), /* useStartTimeInference */ true, RankingMetrics.ONE_DESIGN)); + server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R1")); + server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R2")); + RegattaLeaderboard aalRegattaLeaderboard = server.apply(new CreateRegattaLeaderboard(aalRegatta.getRegattaIdentifier(), + /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); + hassoPlattner = AbstractTracTracLiveTest.createCompetitor("Hasso Plattner"); + alexanderRies = AbstractTracTracLiveTest.createCompetitor("Alexander Ries"); + antonKoch = AbstractTracTracLiveTest.createCompetitor("Anton Koch"); + tobiasSchadewaldt = AbstractTracTracLiveTest.createCompetitor("Tobias Schadewaldt"); + philippBuhl = AbstractTracTracLiveTest.createCompetitor("Philipp Buhl"); + dennisGehrlein = AbstractTracTracLiveTest.createCompetitor("Dennis Gehrlein"); + final RaceDefinitionImpl pfingstbusch29erR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), + Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); + final RaceDefinitionImpl pfingstbusch29erR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), + Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); + final RaceDefinitionImpl pfingstbusch29erR3 = new RaceDefinitionImpl("R3", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch29er.getBoatClass(), + Arrays.asList(new Competitor[] { alexanderRies, tobiasSchadewaldt })); + server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR1)); + server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR2)); + server.apply(new AddRaceDefinition(pfingstbusch29er.getRegattaIdentifier(), pfingstbusch29erR3)); + // track only R1 and R3 + pfingstbusch29erTrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch29erTrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(1)); + pfingstbusch29erLeaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch29erTrackedR1); + pfingstbusch29erTrackedR3 = server.apply(new CreateTrackedRace(pfingstbusch29er.getRaceIdentifier(pfingstbusch29erR3), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch29erLeaderboard.getRaceColumnByName("R3").setTrackedRace(pfingstbusch29erLeaderboard.getRaceColumnByName("R3").getFleetByName("Default"), pfingstbusch29erTrackedR3); + + final RaceDefinitionImpl pfingstbush470R1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), + Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); + final RaceDefinitionImpl pfingstbush470R2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), pfingstbusch470.getBoatClass(), + Arrays.asList(new Competitor[] { philippBuhl, antonKoch })); + server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R1)); + server.apply(new AddRaceDefinition(pfingstbusch470.getRegattaIdentifier(), pfingstbush470R2)); + // track only R1 and R2 + pfingstbusch470TrackedR1 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch470TrackedR1.setStartOfTrackingReceived(pfingstbuschStartDate.plus(2)); // starts later than 29er + pfingstbusch470Leaderboard.getRaceColumnByName("R1").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R1").getFleetByName("Default"), pfingstbusch470TrackedR1); + pfingstbusch470TrackedR2 = server.apply(new CreateTrackedRace(pfingstbusch470.getRaceIdentifier(pfingstbush470R2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + pfingstbusch470Leaderboard.getRaceColumnByName("R2").setTrackedRace(pfingstbusch470Leaderboard.getRaceColumnByName("R2").getFleetByName("Default"), pfingstbusch470TrackedR2); + + final RaceDefinitionImpl aalOrcR1 = new RaceDefinitionImpl("R1", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), + Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); + final RaceDefinitionImpl aalOrcR2 = new RaceDefinitionImpl("R2", new CourseImpl("up/down", Collections.emptyList()), aalRegatta.getBoatClass(), + Arrays.asList(new Competitor[] { hassoPlattner, dennisGehrlein, philippBuhl })); + server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR1)); + server.apply(new AddRaceDefinition(aalRegatta.getRegattaIdentifier(), aalOrcR2)); + // track only R1 and R2 + aalOrcTrackedR1 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR1), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + aalOrcTrackedR1.setStartOfTrackingReceived(aalStartDate); + aalRegattaLeaderboard.getRaceColumnByName("R1").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R1").getFleetByName("Default"), aalOrcTrackedR1); + aalOrcTrackedR2 = server.apply(new CreateTrackedRace(aalRegatta.getRaceIdentifier(aalOrcR2), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 15000)); + aalRegattaLeaderboard.getRaceColumnByName("R2").setTrackedRace(aalRegattaLeaderboard.getRaceColumnByName("R2").getFleetByName("Default"), aalOrcTrackedR2); + } + + @Test + public void testSetup() { + assertNotNull(pfingstbusch29er); + assertNotNull(pfingstbusch29erTrackedR1); + assertNotNull(pfingstbusch29erTrackedR3); + assertNotNull(pfingstbusch470TrackedR1); + assertNotNull(pfingstbusch470TrackedR2); + assertNotNull(aalEvent); + assertNotNull(aalRegatta); + assertNotNull(aalOrcTrackedR1); + assertNotNull(aalOrcTrackedR2); + } + + @Test + public void testSimpleSearchByCompetitorName() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Tobi" }))); + assertEquals(1, Util.size(searchResults.getHits())); + Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); + assertSame(pfingstbusch29er, foundRegatta); + } + + @Test + public void testSimpleSearchByCompetitorName2() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Hasso" }))); + assertEquals(1, Util.size(searchResults.getHits())); + Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); + assertSame(aalRegatta, foundRegatta); + } + + @Test + public void testSimpleSearchByVenueName() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Flensburg" }))); + assertEquals(1, Util.size(searchResults.getHits())); + Regatta foundRegatta = searchResults.getHits().iterator().next().getRegatta(); + assertSame(aalRegatta, foundRegatta); + } + + @Test + public void testSimpleSearchByVenueName2() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Kiel" }))); + assertEquals(2, Util.size(searchResults.getHits())); + final Iterator iterator = searchResults.getHits().iterator(); + final LeaderboardSearchResult firstMatch = iterator.next(); + Regatta firstFoundRegatta = firstMatch.getRegatta(); + assertSame(pfingstbusch29er, firstFoundRegatta); + assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); + final LeaderboardSearchResult secondMatch = iterator.next(); + Regatta secondFoundRegatta = secondMatch.getRegatta(); + assertSame(pfingstbusch470, secondFoundRegatta); + assertSame(pfingstbusch, secondMatch.getEvents().iterator().next()); + } + + @Test + public void testMultipleMatchesSortedCorrectly() { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); + assertEquals(2, Util.size(searchResults.getHits())); + final Iterator iter = searchResults.getHits().iterator(); + final LeaderboardSearchResult firstMatch = iter.next(); + Regatta earlierStartRegatta = firstMatch.getRegatta(); + assertSame(pfingstbusch470, earlierStartRegatta); + assertSame(pfingstbusch, firstMatch.getEvents().iterator().next()); + final LeaderboardSearchResult secondMatch = iter.next(); + Regatta laterStartRegatta = secondMatch.getRegatta(); + assertSame(aalRegatta, laterStartRegatta); + assertSame(aalEvent, secondMatch.getEvents().iterator().next()); + } + + @Test + public void testSerializeAndDeserializeSearchResult() throws JsonDeserializationException { + Result searchResults = server.search(new KeywordQuery(Arrays.asList(new String[] { "Buhl" }))); + LeaderboardGroupBaseJsonSerializer leaderboardGroupBaseJsonSerializer = new LeaderboardGroupBaseJsonSerializer(); + LeaderboardSearchResultJsonSerializer serializer = new LeaderboardSearchResultJsonSerializer(new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), + leaderboardGroupBaseJsonSerializer), + leaderboardGroupBaseJsonSerializer); + LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer(); + LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer( + new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), + leaderboardGroupBaseJsonDeserializer), leaderboardGroupBaseJsonDeserializer); + final LeaderboardSearchResult expected = searchResults.getHits().iterator().next(); + LeaderboardSearchResultBase deserialized = deserializer.deserialize(serializer.serialize(expected)); + assertEquals("Pfingstbusch (470)", deserialized.getRegattaName()); + assertEquals(expected.getEvents().iterator().next().getName(), deserialized.getEvents().iterator().next().getName()); + assertEquals(expected.getEvents().iterator().next().getId(), deserialized.getEvents().iterator().next().getId()); + assertEquals(expected.getEvents().iterator().next().getStartDate(), deserialized.getEvents().iterator().next().getStartDate()); + assertEquals(expected.getEvents().iterator().next().getEndDate(), deserialized.getEvents().iterator().next().getEndDate()); + assertEquals(expected.getEvents().iterator().next().getVenue().getName(), deserialized.getEvents().iterator().next().getVenue().getName()); + Iterator expectedLGs = expected.getLeaderboardGroups().iterator(); + Iterator deserializedLGs = deserialized.getLeaderboardGroups().iterator(); + while (expectedLGs.hasNext()) { + assertTrue(deserializedLGs.hasNext()); + LeaderboardGroup expectedLG = expectedLGs.next(); + LeaderboardGroupBase deserializedLG = deserializedLGs.next(); + assertEquals(expectedLG.getId(), deserializedLG.getId()); + assertEquals(expectedLG.getName(), deserializedLG.getName()); + assertEquals(expectedLG.getDescription(), deserializedLG.getDescription()); + } + } +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java index 9aa44d096da..df2e1f6b302 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogReplicator.java @@ -78,6 +78,10 @@ public class RaceLogReplicator implements RaceColumnListener { public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java index a2b3ad0ec64..1efadc59320 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java @@ -51,6 +51,10 @@ public class RaceLogScoringReplicator implements RaceColumnListener { public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { } + @Override + public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { + } + @Override public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 6541b9a4d8c..1de83e5335d 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1444,7 +1444,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes } result.put( s.getName(), - new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isStartsWithZeroScore(), s + new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isFleetsCanRunInParallel(), s.isStartsWithZeroScore(), s .isFirstColumnIsNonDiscardableCarryForward(), s.getResultDiscardingRule() == null ? null : s.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), s .hasSplitFleetContiguousScoring())); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java index a996da27431..b58bf4d24c5 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java @@ -56,7 +56,7 @@ public class AddSpecificRegatta extends AbstractAddRegattaOperation { List result = new ArrayList(); for (Map.Entry e : seriesNamesWithFleetNamesAndFleetOrderingAndMedalAndStartsWithZeroScoreAndDiscardingThresholds.getSeriesCreationParameters().entrySet()) { final List emptyRaceColumnNamesList = Collections.emptyList(); - Series s = new SeriesImpl(e.getKey(), e.getValue().isMedal(), createFleets(e.getValue().getFleets()), + Series s = new SeriesImpl(e.getKey(), e.getValue().isMedal(), e.getValue().isFleetsCanRunInParallel(), createFleets(e.getValue().getFleets()), emptyRaceColumnNamesList, trackedRegattaRegistry); if (e.getValue().getDiscardingThresholds() != null) { s.setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(e.getValue().getDiscardingThresholds())); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java index b3e5f88ef34..88d9208e9dd 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSeries.java @@ -1,90 +1,93 @@ -package com.sap.sailing.server.operationaltransformation; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.RegattaIdentifier; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.RacingEventServiceOperation; - -public class UpdateSeries extends AbstractSeriesOperation { - private static final long serialVersionUID = 6356089749049112710L; - - private final List fleets; - private final boolean isMedal; - private final int[] resultDiscardingThresholds; - private final boolean startsWithZeroScore; - private final boolean firstColumnIsNonDiscardableCarryForward; - private final boolean hasSplitFleetContiguousScoring; - private final boolean seriesNameChanged; - private final String newSeriesName; - - public UpdateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, - int[] resultDiscardingThresholds, boolean startsWithZeroScore, - boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, - List fleets) { - super(regattaIdentifier, seriesName); - this.seriesNameChanged = !seriesName.equals(newSeriesName); - this.newSeriesName = newSeriesName; - this.isMedal = isMedal; - this.resultDiscardingThresholds = resultDiscardingThresholds; - this.startsWithZeroScore = startsWithZeroScore; - this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; - this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; - this.fleets = fleets; - } - - @Override - public Void internalApplyTo(RacingEventService toState) throws Exception { - Series series = getSeries(toState); - if (series == null) { - series = createSeries(toState); - } - if (seriesNameChanged) { - series.setName(newSeriesName); - } - series.setIsMedal(isMedal); - series.setResultDiscardingRule(resultDiscardingThresholds == null ? - null : new ThresholdBasedResultDiscardingRuleImpl(resultDiscardingThresholds)); - series.setStartsWithZeroScore(startsWithZeroScore); - series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward); - series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoring); - if (series.getRegatta().isPersistent()) { - toState.updateStoredRegatta(series.getRegatta()); - } - return null; - } - - private Series createSeries(RacingEventService toState) { - Regatta regatta = toState.getRegatta(getRegattaIdentifier()); - final List emptyRaceColumnNames = Collections.emptyList(); - List result = new ArrayList(); - for (FleetDTO fleetNameAndOrderingAndColor : fleets) { - Fleet fleet = new FleetImpl(fleetNameAndOrderingAndColor.getName(), fleetNameAndOrderingAndColor.getOrderNo(), fleetNameAndOrderingAndColor.getColor()); - result.add(fleet); - } - regatta.addSeries(new SeriesImpl(getSeriesName(), isMedal, result, emptyRaceColumnNames, (TrackedRegattaRegistry)toState)); - return regatta.getSeriesByName(getSeriesName()); - } - - @Override - public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) { - // TODO Auto-generated method stub - return null; - } - - @Override - public RacingEventServiceOperation transformServerOp(RacingEventServiceOperation clientOp) { - // TODO Auto-generated method stub - return null; - } -} +package com.sap.sailing.server.operationaltransformation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.server.RacingEventServiceOperation; + +public class UpdateSeries extends AbstractSeriesOperation { + private static final long serialVersionUID = 6356089749049112710L; + + private final List fleets; + private final boolean isMedal; + private final boolean isFleetsCanRunInParallel; + private final int[] resultDiscardingThresholds; + private final boolean startsWithZeroScore; + private final boolean firstColumnIsNonDiscardableCarryForward; + private final boolean hasSplitFleetContiguousScoring; + private final boolean seriesNameChanged; + private final String newSeriesName; + + public UpdateSeries(RegattaIdentifier regattaIdentifier, String seriesName, String newSeriesName, boolean isMedal, boolean isFleetsCanRunInParallel, + int[] resultDiscardingThresholds, boolean startsWithZeroScore, + boolean firstColumnIsNonDiscardableCarryForward, boolean hasSplitFleetContiguousScoring, + List fleets) { + super(regattaIdentifier, seriesName); + this.seriesNameChanged = !seriesName.equals(newSeriesName); + this.newSeriesName = newSeriesName; + this.isMedal = isMedal; + this.isFleetsCanRunInParallel = isFleetsCanRunInParallel; + this.resultDiscardingThresholds = resultDiscardingThresholds; + this.startsWithZeroScore = startsWithZeroScore; + this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward; + this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring; + this.fleets = fleets; + } + + @Override + public Void internalApplyTo(RacingEventService toState) throws Exception { + Series series = getSeries(toState); + if (series == null) { + series = createSeries(toState); + } + if (seriesNameChanged) { + series.setName(newSeriesName); + } + series.setIsMedal(isMedal); + series.setIsFleetsCanRunInParallel(isFleetsCanRunInParallel); + series.setResultDiscardingRule(resultDiscardingThresholds == null ? + null : new ThresholdBasedResultDiscardingRuleImpl(resultDiscardingThresholds)); + series.setStartsWithZeroScore(startsWithZeroScore); + series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForward); + series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoring); + if (series.getRegatta().isPersistent()) { + toState.updateStoredRegatta(series.getRegatta()); + } + return null; + } + + private Series createSeries(RacingEventService toState) { + Regatta regatta = toState.getRegatta(getRegattaIdentifier()); + final List emptyRaceColumnNames = Collections.emptyList(); + List result = new ArrayList(); + for (FleetDTO fleetNameAndOrderingAndColor : fleets) { + Fleet fleet = new FleetImpl(fleetNameAndOrderingAndColor.getName(), fleetNameAndOrderingAndColor.getOrderNo(), fleetNameAndOrderingAndColor.getColor()); + result.add(fleet); + } + regatta.addSeries(new SeriesImpl(getSeriesName(), isMedal, isFleetsCanRunInParallel, result, emptyRaceColumnNames, (TrackedRegattaRegistry)toState)); + return regatta.getSeriesByName(getSeriesName()); + } + + @Override + public RacingEventServiceOperation transformClientOp(RacingEventServiceOperation serverOp) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RacingEventServiceOperation transformServerOp(RacingEventServiceOperation clientOp) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java index 1e46db1ecf2..f132381a169 100755 --- a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java +++ b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java @@ -143,7 +143,7 @@ public class StructureImporter { for (Series raceType : regattaStructure.getSeries()) { List fleets = getFleets(raceType.getFleets()); setRaceNames(index, raceType, raceType.getFleets()); - series.add(new SeriesImpl(raceType.getSeries(), raceType.isMedal(), fleets, raceType.getRaceNames(), null)); + series.add(new SeriesImpl(raceType.getSeries(), raceType.isMedal(), true, fleets, raceType.getRaceNames(), null)); } } return series; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java index 71683a1d6cc..41be9b03291 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java @@ -87,8 +87,8 @@ public class OfflineDataManager extends DataManager { newEvent.getVenue().addCourseArea(new CourseAreaImpl("Stadium", UUID.randomUUID())); dataStore.addEvent(newEvent); - SeriesWithRows qualifying = new SeriesWithRowsImpl("Qualifying", false, null); - SeriesWithRows medal = new SeriesWithRowsImpl("Medal", true, null); + SeriesWithRows qualifying = new SeriesWithRowsImpl("Qualifying", false, /* isFleetsCanRunInParallel */ true, null); + SeriesWithRows medal = new SeriesWithRowsImpl("Medal", true, /* isFleetsCanRunInParallel */ true, null); RaceGroup raceGroup = new RaceGroupImpl("ESS", /* displayName */ null, new BoatClassImpl("X40", false), null, Arrays.asList(qualifying, medal), new EmptyRegattaConfiguration()); From 139f5b4ad40a03188a22e71853649d8ab6eba877 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 21 Apr 2016 18:23:05 +0200 Subject: [PATCH 27/57] a first version of a nav drawer that uses a majorly refactored filter and sorting design; filter implementation still empty, test not compiling Change-Id: I2711757df7d4e72f6c627d2daacabb7b22e6e16f --- .../base/racegroup/CurrentRaceComparator.java | 12 +- .../base/racegroup/CurrentRaceFilter.java | 5 +- .../base/racegroup/RaceGroupSeries.java | 106 +++++++++ .../racegroup/RaceGroupSeriesComparator.java | 26 +++ .../racegroup/impl/CurrentRaceFilterImpl.java | 38 +-- .../app/domain/impl/RaceGroupSeriesFleet.java | 79 +------ .../racelist/ManagedRaceListAdapter.java | 173 ++++++++++---- .../app/ui/adapters/racelist/RaceFilter.java | 217 ++---------------- .../adapters/racelist/RaceListDataType.java | 5 +- .../racelist/RaceListDataTypeHeader.java | 35 +-- .../racelist/RaceListDataTypeRace.java | 20 +- .../app/ui/fragments/RaceListFragment.java | 157 +++---------- 12 files changed, 380 insertions(+), 493 deletions(-) create mode 100755 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java index f7130bf33c3..b6ce06b88ff 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java @@ -19,7 +19,7 @@ import com.sap.sse.common.Util; public class CurrentRaceComparator implements Comparator { @Override public int compare(FilterableRace o1, FilterableRace o2) { - int result = o1.getRaceGroup().getDisplayName().compareTo(o2.getRaceGroup().getDisplayName()); + int result = getRaceGroupDisplayName(o1).compareTo(getRaceGroupDisplayName(o2)); if (result == 0) { result = o1.getZeroBasedSeriesIndex() - o2.getZeroBasedSeriesIndex(); if (result == 0) { @@ -31,4 +31,14 @@ public class CurrentRaceComparator implements Comparator { } return result; } + + private String getRaceGroupDisplayName(FilterableRace r) { + final String result; + if (r.getRaceGroup().getDisplayName() != null) { + result = r.getRaceGroup().getDisplayName(); + } else { + result = r.getRaceGroup().getName(); + } + return result; + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java index 9afa53545cb..8d4ca29da0b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java @@ -1,7 +1,6 @@ package com.sap.sailing.domain.base.racegroup; -import java.util.Collection; -import java.util.List; +import java.util.Set; import com.sap.sailing.domain.abstractlog.race.state.RaceState; @@ -14,5 +13,5 @@ import com.sap.sailing.domain.abstractlog.race.state.RaceState; * @author Axel Uhl (d043530) */ public interface CurrentRaceFilter { - List filterCurrentRaces(Collection allItems); + Set filterCurrentRaces(Set allRaces); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java new file mode 100755 index 00000000000..5151c9f6cc0 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java @@ -0,0 +1,106 @@ +package com.sap.sailing.domain.base.racegroup; + +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.common.LeaderboardNameConstants; +import com.sap.sse.common.Util; + +/** + * Represents a pair of {@link RaceGroup} (representing a regatta or a flexible leaderboard) and {@link SeriesBase + * series}. An instance can be constructed for a {@link ManagedRace} which then extracts these two properties from the + * race. Note that equal objects of this type can result for different races as long as they are in the equal + * {@link RaceGroup} and {@link SeriesBase series}. + * + * @author Axel Uhl (d043530) + * + */ +public class RaceGroupSeries { + + private RaceGroup raceGroup; + private SeriesBase series; + private int seriesOrder; + + public RaceGroupSeries(FilterableRace race) { + raceGroup = race.getRaceGroup(); + series = race.getSeries(); + seriesOrder = getSeriesIndex(race, series); + } + + private static int getSeriesIndex(FilterableRace race, SeriesBase series) { + return Util.indexOf(race.getRaceGroup().getSeries(), series); + } + + public RaceGroup getRaceGroup() { + return raceGroup; + } + + public SeriesBase getSeries() { + return series; + } + + public String getRaceGroupName() { + return raceGroup.getName(); + } + + public String getSeriesName() { + return series.getName(); + } + + public String getDisplayName() { + return getDisplayName(false); + } + + public String getDisplayName(boolean useDisplayName) { + String name = raceGroup.getDisplayName(); + if (!useDisplayName || name == null || name.length() == 0) { + name = raceGroup.getName(); + } + if (series != null && !series.getName().equals(LeaderboardNameConstants.DEFAULT_SERIES_NAME)) { + name += " - " + series.getName(); + } + return name; + } + + public int getSeriesOrder() { + return seriesOrder; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((raceGroup == null) ? 0 : raceGroup.hashCode()); + result = prime * result + ((series == null) ? 0 : series.hashCode()); + result = prime * result + seriesOrder; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RaceGroupSeries other = (RaceGroupSeries) obj; + if (raceGroup == null) { + if (other.raceGroup != null) { + return false; + } + } else if (!raceGroup.equals(other.raceGroup)) { + return false; + } + if (series == null) { + if (other.series != null) { + return false; + } + } else if (!series.equals(other.series)) { + return false; + } + if (seriesOrder != other.seriesOrder) { + return false; + } + return true; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java new file mode 100644 index 00000000000..c8e77c3f9c7 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java @@ -0,0 +1,26 @@ +package com.sap.sailing.domain.base.racegroup; + +import java.util.Comparator; + +/** + * When current races of a regatta are to be displayed to a user, a reasonable ordering is key to usability. This + * comparator sorts by their series index (day 1 before day 2; qualification before final, etc.), then by their index + * within the fleet (R1 before R2, etc.) and lastly by their fleet index (keeping the ordering as set on the server; not + * to be confused with the ranking ordering set for the fleet; so if on the server silver is placed before gold then + * that order is kept, regardless of the 1 and 2 values of these fleets). For a typical regatta with qualification, + * final and medal series, with race columns Q1..Q5, F6..F10, M and fleets Yellow/Blue and Silver/Gold, this would result + * in an ordering Q1-Yellow, Q1-Blue, ..., Q5-Yellow, Q5-Blue, F6-Silver, F6-Gold, ..., F10-Silver, F10-Gold, Medal. + * + * @author Axel Uhl (d043530) + * + */ +public class RaceGroupSeriesComparator implements Comparator { + @Override + public int compare(RaceGroupSeries o1, RaceGroupSeries o2) { + int result = o1.getDisplayName().compareTo(o2.getDisplayName()); + if (result == 0) { + result = o1.getSeriesOrder() - o2.getSeriesOrder(); + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 5ebaf485788..154a5e4125e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -1,13 +1,16 @@ package com.sap.sailing.domain.base.racegroup.impl; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; +import com.sap.sailing.domain.base.racegroup.FilterableRace; import com.sap.sailing.domain.base.racegroup.IsFleetFragment; import com.sap.sailing.domain.base.racegroup.IsRaceFragment; import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; @@ -16,36 +19,11 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; public class CurrentRaceFilterImpl implements CurrentRaceFilter { @Override - public List filterCurrentRaces(Collection allItems) { - Map> filteredRace = filterRaces(allItems); - filteredRace = removeAllListsBeforeLastStarted(filteredRace); - return convertBackToList(filteredRace); - } - - private List convertBackToList(Map> filteredRaces) { - Map> filteredRacesWithoutEmtyList = removeEmtyLists(filteredRaces); - List result = new LinkedList<>(); - for (IsFleetFragment currentKey : filteredRacesWithoutEmtyList.keySet()) { - @SuppressWarnings("unchecked") - T t = (T) currentKey; // assumes that the IsFleetFragment specialization used here also specializes T which is the special RaceGroupFragment used here - result.add(t); - for (IsRaceFragment r : filteredRacesWithoutEmtyList.get(currentKey)) { - @SuppressWarnings("unchecked") - T tRace = (T) r;// assumes that the IsRaceFragment specialization used here also specializes T which is the special RaceGroupFragment used here - result.add(tRace); - } - } - return result; - } - - private Map> removeEmtyLists(Map> filteredRaces) { - Map> result = new LinkedHashMap<>(); - for (IsFleetFragment currentKey : filteredRaces.keySet()) { - if (!filteredRaces.get(currentKey).isEmpty()) { - result.put(currentKey, filteredRaces.get(currentKey)); - } - } - return result; + public Set filterCurrentRaces(Set allRaces) { + final Set filteredRaces = new HashSet<>(); + // TODO implement the CurrentRaceFilter algorithm here... + filteredRaces.addAll(allRaces); + return filteredRaces; } private Map> removeAllListsBeforeLastStarted(Map> filteredRaces) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java index 9755b1686a1..860677dc3e8 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java @@ -1,10 +1,9 @@ package com.sap.sailing.racecommittee.app.domain.impl; -import android.text.TextUtils; - import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sse.common.Util; @@ -18,50 +17,25 @@ import com.sap.sse.common.Util; * @author Axel Uhl (d043530) * */ -public class RaceGroupSeriesFleet { +public class RaceGroupSeriesFleet extends RaceGroupSeries { - private RaceGroup raceGroup; private Fleet fleet; - private SeriesBase series; - private int seriesOrder; private int fleetOrder; public RaceGroupSeriesFleet(ManagedRace race) { - raceGroup = race.getRaceGroup(); - series = race.getSeries(); + super(race); fleet = race.getFleet(); - seriesOrder = getSeriesIndex(race, series); - fleetOrder = getFleetIndex(series.getFleets(), race.getFleet()); - } - - private static int getSeriesIndex(ManagedRace race, SeriesBase series) { - return Util.indexOf(race.getRaceGroup().getSeries(), series); + fleetOrder = getFleetIndex(race.getSeries().getFleets(), race.getFleet()); } private int getFleetIndex(Iterable fleets, Fleet fleet) { return Util.indexOf(fleets, fleet); } - public RaceGroup getRaceGroup() { - return raceGroup; - } - - public SeriesBase getSeries() { - return series; - } - public Fleet getFleet() { return fleet; } - public String getRaceGroupName() { - return raceGroup.getName(); - } - - public String getSeriesName() { - return series.getName(); - } - public String getFleetName() { return fleet.getName(); } @@ -71,23 +45,13 @@ public class RaceGroupSeriesFleet { } public String getDisplayName(boolean useDisplayName) { - String name = raceGroup.getDisplayName(); - if (!useDisplayName || TextUtils.isEmpty(name)) { - name = raceGroup.getName(); - } - if (series != null && !series.getName().equals(LeaderboardNameConstants.DEFAULT_SERIES_NAME)) { - name += " - " + series.getName(); - } + String name = super.getDisplayName(useDisplayName); if (fleet != null && !fleet.getName().equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { name += " - " + fleet.getName(); } return name; } - public int getSeriesOrder() { - return seriesOrder; - } - public int getFleetOrder() { return fleetOrder; } @@ -95,11 +59,8 @@ public class RaceGroupSeriesFleet { @Override public int hashCode() { final int prime = 31; - int result = 1; - result = prime * result + ((raceGroup == null) ? 0 : raceGroup.hashCode()); - result = prime * result + ((series == null) ? 0 : series.hashCode()); + int result = super.hashCode(); result = prime * result + ((fleet == null) ? 0 : fleet.hashCode()); - result = prime * result + seriesOrder; result = prime * result + fleetOrder; return result; } @@ -108,38 +69,18 @@ public class RaceGroupSeriesFleet { public boolean equals(Object obj) { if (this == obj) return true; - if (obj == null) + if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; RaceGroupSeriesFleet other = (RaceGroupSeriesFleet) obj; - if (raceGroup == null) { - if (other.raceGroup != null) { - return false; - } - } else if (!raceGroup.equals(other.raceGroup)) { - return false; - } if (fleet == null) { - if (other.fleet != null) { + if (other.fleet != null) return false; - } - } else if (!fleet.equals(other.fleet)) { + } else if (!fleet.equals(other.fleet)) return false; - } - if (series == null) { - if (other.series != null) { - return false; - } - } else if (!series.equals(other.series)) { + if (fleetOrder != other.fleetOrder) return false; - } - if (seriesOrder != other.seriesOrder) { - return false; - } - if (fleetOrder != other.fleetOrder) { - return false; - } return true; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index 55fbd28f6d9..aced3420a05 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -2,7 +2,41 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; import java.text.DecimalFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.shared.util.BitmapHelper; +import com.sap.sailing.android.shared.util.BroadcastManager; +import com.sap.sailing.android.shared.util.ViewHelper; +import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.racegroup.CurrentRaceComparator; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesComparator; +import com.sap.sailing.domain.common.racelog.FlagPole; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.racecommittee.app.AppConstants; +import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.data.DataManager; +import com.sap.sailing.racecommittee.app.domain.ManagedRace; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceFilter.FilterSubscriber; +import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources; +import com.sap.sailing.racecommittee.app.utils.RaceHelper; +import com.sap.sailing.racecommittee.app.utils.ThemeHelper; +import com.sap.sailing.racecommittee.app.utils.TimeUtils; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; import android.content.Context; import android.content.Intent; @@ -19,39 +53,16 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import com.sap.sailing.android.shared.logging.ExLog; -import com.sap.sailing.android.shared.util.BitmapHelper; -import com.sap.sailing.android.shared.util.BroadcastManager; -import com.sap.sailing.android.shared.util.ViewHelper; -import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; -import com.sap.sailing.domain.abstractlog.race.state.RaceState; -import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; -import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; -import com.sap.sailing.domain.common.racelog.FlagPole; -import com.sap.sailing.domain.common.racelog.Flags; -import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.data.DataManager; -import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceFilter.FilterSubscriber; -import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources; -import com.sap.sailing.racecommittee.app.utils.RaceHelper; -import com.sap.sailing.racecommittee.app.utils.ThemeHelper; -import com.sap.sailing.racecommittee.app.utils.TimeUtils; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; - public class ManagedRaceListAdapter extends ArrayAdapter implements FilterSubscriber { private final static String TAG = ManagedRaceListAdapter.class.getName(); private final Object mLockObject = new Object(); - private List mAllViewItems; - private RaceFilter mFilter; + private final RaceFilter mFilter; private LayoutInflater mInflater; private Resources mResources; - private List mShownViewItems; + private final List mShownViewItems; + private final Map viewItemsRaces; + private final Map viewItemsSeriesHeaders; private ImageView marker; private ImageView update_badge; private LinearLayout race_flag; @@ -75,12 +86,16 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple private TextView explicit_factor; private int flag_size; private DecimalFormat factor_format; + private final Set mAllRaces; - public ManagedRaceListAdapter(Context context, List viewItems) { + public ManagedRaceListAdapter(Context context, Set allRaces) { super(context, 0); - - mAllViewItems = viewItems; - mShownViewItems = viewItems; + mAllRaces = allRaces; + mShownViewItems = new ArrayList<>(); + this.viewItemsRaces = new HashMap<>(); + this.viewItemsSeriesHeaders = new HashMap<>(); + createViewItemsForRacesAndSeries(); + mFilter = new RaceFilter(allRaces, this); mInflater = LayoutInflater.from(getContext()); mResources = getContext().getResources(); dateFormat = new SimpleDateFormat("kk:mm", getContext().getResources().getConfiguration().locale); @@ -88,6 +103,25 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple factor_format = new DecimalFormat(context.getString(R.string.race_factor_format)); } + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + createViewItemsForRacesAndSeries(); + getFilter().filter(""); + } + + private void createViewItemsForRacesAndSeries() { + viewItemsRaces.clear(); + viewItemsSeriesHeaders.clear(); + for (final ManagedRace race : mAllRaces) { + viewItemsRaces.put(race, new RaceListDataTypeRace(race, mInflater)); + final SeriesBase series = race.getSeries(); + if (!viewItemsSeriesHeaders.containsKey(series)) { + viewItemsSeriesHeaders.put(series, new RaceListDataTypeHeader(new RaceGroupSeries(race), mInflater)); + } + } + } + @Override public int getCount() { synchronized (mLockObject) { @@ -97,9 +131,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple @Override public RaceFilter getFilter() { - if (mFilter == null) { - mFilter = new RaceFilter(mAllViewItems, this); - } return mFilter; } @@ -122,18 +153,13 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple @Override public View getView(int position, View convertView, ViewGroup parent) { - RaceListDataType raceListElement; - raceListElement = getItem(position); + final RaceListDataType raceListElement = getItem(position); int type = getItemViewType(position); TimePoint now = MillisecondsTimePoint.now(); if (convertView == null) { - if (type == ViewType.HEADER.index) { - convertView = mInflater.inflate(R.layout.race_list_area_header, parent, false); - } else if (type == ViewType.RACE.index) { - convertView = mInflater.inflate(R.layout.race_list_area_item, parent, false); - } + convertView = raceListElement.getView(parent); } findViews(convertView); resetValues(convertView); @@ -145,11 +171,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple regatta = header.getRaceGroup().getName(); } boat_class.setText(regatta); - if (header.isFleetVisible()) { - fleet_series.setText(RaceHelper.getFleetSeries(header.getFleet(), header.getSeries())); - } else { - fleet_series.setText(RaceHelper.getSeriesName(header.getSeries(), "")); - } + fleet_series.setText(RaceHelper.getSeriesName(header.getSeries(), "")); if (fleet_series.getText().length() == 0) { fleet_series.setVisibility(View.GONE); } else { @@ -259,13 +281,68 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple } @Override - public void onResult(List filtered) { + public void onResult(List filteredRaces) { synchronized (mLockObject) { - mShownViewItems = filtered; + mShownViewItems.clear(); + mShownViewItems.addAll(getShownViewItems(filteredRaces)); notifyDataSetChanged(); } } + /** + * For the {@code filteredRaces} ensure that exactly the required view items are in {@link #mShownViewItems}. This + * encompasses the header items for all non-empty series and the race items for all races in their series. + */ + private List getShownViewItems(List races) { + final Map> raceItemsByHeader = getRaceItemsGroupedBySeriesHeader(races); + final List result = serializeItems(raceItemsByHeader); + return result; + } + + private List serializeItems(final Map> raceItemsByHeader) { + final List result = new ArrayList<>(); + final List headers = new ArrayList<>(raceItemsByHeader.keySet()); + Collections.sort(headers, new Comparator() { + private final RaceGroupSeriesComparator c = new RaceGroupSeriesComparator(); + + @Override + public int compare(RaceListDataTypeHeader lhs, RaceListDataTypeHeader rhs) { + return c.compare(lhs.getRegattaSeries(), rhs.getRegattaSeries()); + } + }); + for (final RaceListDataTypeHeader header : headers) { + result.add(header); + final List raceItems = new ArrayList<>(raceItemsByHeader.get(header)); + Collections.sort(raceItems, new Comparator() { + final CurrentRaceComparator c = new CurrentRaceComparator(); + + @Override + public int compare(RaceListDataTypeRace lhs, RaceListDataTypeRace rhs) { + return c.compare(lhs.getRace(), rhs.getRace()); + } + }); + for (final RaceListDataTypeRace raceItem : raceItems) { + result.add(raceItem); + } + } + return result; + } + + private Map> getRaceItemsGroupedBySeriesHeader(final List filteredRaces) { + final Map> raceItemsByHeader = new HashMap<>(); + for (final ManagedRace race : filteredRaces) { + final SeriesBase series = race.getSeries(); + final RaceListDataTypeHeader seriesHeader = viewItemsSeriesHeaders.get(series); + List raceItemsInSeries = raceItemsByHeader.get(seriesHeader); + if (raceItemsInSeries == null) { + raceItemsInSeries = new ArrayList<>(); + raceItemsByHeader.put(seriesHeader, raceItemsInSeries); + } + raceItemsInSeries.add(viewItemsRaces.get(race)); + } + return raceItemsByHeader; + } + public void setSelectedRace(RaceListDataType id) { mSelectedRace = id; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 6bb3365bd27..ee7cd72cb42 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -1,16 +1,12 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Set; -import com.sap.sailing.domain.base.racegroup.CurrentRaceComparator; import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.base.racegroup.impl.CurrentRaceFilterImpl; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment.FilterMode; import android.widget.Filter; @@ -19,23 +15,21 @@ import android.widget.Filter; * Filters {@link ManagedRace} objects by status and place in the regatta structure. See {@link CurrentRaceFilter}. If * the {@link #filterMode} is {@link FilterMode#ALL}, no filtering takes place at all, and all races will be displayed. * If the {@link #filterMode} is {@link FilterMode#ACTIVE}, the set of races to be displayed is computed using a - * {@link CurrentRaceFilter}. For each race to be displayed, a {@link RaceListDataTypeRace} view item is produced. The - * races are grouped into sections, one for each series, and the items within each such series section are ordered using - * a {@link CurrentRaceComparator} while the series sections are ordered by the order in which the series appear in the - * Race Group / regatta. + * {@link CurrentRaceFilter}. */ public class RaceFilter extends Filter { + private final CurrentRaceFilter currentRaceFilter = new CurrentRaceFilterImpl(); public interface FilterSubscriber { - void onResult(List filtered); + void onResult(List filtered); } - protected final FilterSubscriber subscriber; - protected Collection items; - protected FilterMode filterMode; + private final FilterSubscriber subscriber; + private final Set allRaces; + private FilterMode filterMode; - public RaceFilter(Collection items, FilterSubscriber resultSubscriber) { - this.items = items; + public RaceFilter(Set allRaces, FilterSubscriber resultSubscriber) { + this.allRaces = allRaces; this.subscriber = resultSubscriber; } @@ -44,7 +38,7 @@ public class RaceFilter extends Filter { filter(""); } - protected static FilterResults createResults(Collection result) { + protected static FilterResults createResults(Set result) { FilterResults results = new FilterResults(); results.values = new ArrayList<>(result); results.count = result.size(); @@ -53,192 +47,21 @@ public class RaceFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { + final FilterResults result; if (filterMode == null || filterMode.equals(FilterMode.ALL)) { - return createResults(items); + result = createResults(allRaces); + } else { + // filter using the CurrentRaceFilter + result = createResults(currentRaceFilter.filterCurrentRaces(allRaces)); } - - List filteredItems = new ArrayList<>(); - RaceListDataTypeRace currentUnscheduledItem = null; - String currentUnscheduledRaceName = ""; - String currentRegattaName = ""; - RaceGroupSeriesFleet currentlyRunningFleet = null; - boolean showCurrentFleet; - Map finalsStarted = new HashMap<>(); - int finishedItems = 0; - int subItems = -1; - - List headersToRemove = new ArrayList<>(); - RaceListDataTypeHeader previousHeader = null; - Map fleetItemCount = new HashMap<>(); - Map lastRunningRaceInFleet = new HashMap<>(); - - List allItems = new ArrayList<>(items); - detectStartedFinalsOrMedals(finalsStarted, allItems); - - filterByFinalOrMedal(finalsStarted, headersToRemove, allItems); - - for (RaceListDataType item : allItems) { - if (item instanceof RaceListDataTypeHeader) { - showCurrentFleet = subItems < 0 || (subItems > 0 && subItems != finishedItems); - if (previousHeader != null && subItems > 0 && subItems == finishedItems && !showCurrentFleet) { - headersToRemove.add(previousHeader); - } - showCurrentFleet = false; - // Mark race header (including races) of subsequent series to be removed if previous series was not in last race - if (fleetItemCount.isEmpty() && lastRunningRaceInFleet.isEmpty()) { - showCurrentFleet = true; - } else { - for (String key : fleetItemCount.keySet()) { - if (fleetItemCount.get(key).equals(lastRunningRaceInFleet.get(key))) { - showCurrentFleet = true; - break; - } - } - } - RaceListDataTypeHeader headerItem = (RaceListDataTypeHeader) item; - filteredItems.add(headerItem); - // new run for all types! - previousHeader = headerItem; - if (!showCurrentFleet) { - if (currentRegattaName.equals("") || currentRegattaName.equals(headerItem.getRaceGroup().getName())) { - headersToRemove.add(headerItem); - } - } - fleetItemCount.clear(); - lastRunningRaceInFleet.clear(); - currentUnscheduledRaceName = ""; - currentRegattaName = previousHeader.getRaceGroup().getName(); - currentUnscheduledItem = null; - currentlyRunningFleet = null; - finishedItems = 0; - subItems = 0; - } else if (item instanceof RaceListDataTypeRace) { - subItems++; - RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; - String fleetName = getFleetName(raceItem); - countFleetItems(fleetItemCount, fleetName); - boolean qualifying = raceItem.getRace().getFleet().getOrdering() == 0 || !raceItem.getRace().getSeries().isMedal(); - RaceLogRaceStatus status = raceItem.getCurrentStatus(); - // Preserve first unscheduled races that belong to the first race column of the leaderboard - if (qualifying && RaceLogRaceStatus.UNSCHEDULED.equals(status)) { - if (currentUnscheduledRaceName.equals("")) { - currentUnscheduledRaceName = raceItem.getRaceName(); - } - if (currentUnscheduledRaceName.equals(raceItem.getRaceName())) { - filteredItems.add(raceItem); - if (currentlyRunningFleet == null) { - currentUnscheduledItem = raceItem; - } - } - } - if (currentUnscheduledItem == null && RaceLogRaceStatus.UNSCHEDULED.equals(status)) { - if (currentlyRunningFleet == null || currentlyRunningFleet.equals(raceItem.getFleet())) { - currentUnscheduledItem = raceItem; - if (!filteredItems.contains(raceItem)) { - filteredItems.add(raceItem); - } - } - } else if (RaceLogRaceStatus.FINISHED.equals(status)) { - finishedItems++; - if (filteredItems.contains(raceItem)) { - filteredItems.remove(raceItem); - } - int fleetCount = fleetItemCount.get(getFleetName(raceItem)); - lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); - currentlyRunningFleet = raceItem.getFleet(); - currentUnscheduledItem = null; - } else if(RaceLogRaceStatus.isPreRunning(status)) { - filteredItems.add(raceItem); - currentUnscheduledItem = null; - currentlyRunningFleet = raceItem.getFleet(); - } - else if (RaceLogRaceStatus.isActive(status)) { - int fleetCount = fleetItemCount.get(getFleetName(raceItem)); - lastRunningRaceInFleet.put(getFleetName(raceItem), fleetCount); - filteredItems.add(raceItem); - currentUnscheduledItem = null; - currentlyRunningFleet = raceItem.getFleet(); - } - } - } - if (previousHeader != null && subItems > 0 && subItems == finishedItems) { - headersToRemove.add(previousHeader); - } - removeFinishedSeries(filteredItems, headersToRemove); - return createResults(filteredItems); + return result; } - private String getFleetName(RaceListDataTypeRace raceItem) { - return raceItem.getRace().getIdentifier().getFleet().getName(); - } - - private void countFleetItems(Map fleetItemCount, String fleetName) { - if (!fleetItemCount.containsKey(fleetName)) { - fleetItemCount.put(fleetName, 0); - } - int count = fleetItemCount.get(fleetName) + 1; - fleetItemCount.put(fleetName, count); - } - - private void filterByFinalOrMedal(Map finalsStarted, List headersToRemove, - List allItems) { - RaceListDataTypeHeader currentHeader = null; - List racesToRemove = new ArrayList<>(); - for (RaceListDataType item : allItems) { - if (item instanceof RaceListDataTypeHeader) { - currentHeader = (RaceListDataTypeHeader) item; - } else if (item instanceof RaceListDataTypeRace) { - RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; - if (raceItem.getRace().getFleet().getOrdering() == 0 && !raceItem.getRace().getSeries().isMedal()) { - String regattaName = raceItem.getRace().getRaceGroup().getName(); - if (finalsStarted.containsKey(regattaName) && currentHeader != null) { - headersToRemove.add(currentHeader); - racesToRemove.add(raceItem); - } - } - } - } - allItems.removeAll(racesToRemove); - } - - private void detectStartedFinalsOrMedals(Map finalsStarted, List allItems) { - for (RaceListDataType item : allItems) { - if (item instanceof RaceListDataTypeRace) { - RaceListDataTypeRace raceItem = (RaceListDataTypeRace) item; - String regattaName = raceItem.getRace().getRaceGroup().getName(); - if (raceItem.getRace().getFleet().getOrdering() != 0 || raceItem.getRace().getSeries().isMedal()) { - if (RaceLogRaceStatus.isRunningOrFinished(raceItem.getCurrentStatus())) { - finalsStarted.put(regattaName, true); - } - } - } - } - } - - private void removeFinishedSeries(List filteredItems, List headersToRemove) { - List itemsToKeep = new ArrayList<>(); - boolean keepItems = false; - for (RaceListDataType item : filteredItems) { - if (item instanceof RaceListDataTypeHeader) { - keepItems = !headersToRemove.contains(item); - if (keepItems) { - itemsToKeep.add(item); - } - - } else if (item instanceof RaceListDataTypeRace) { - if (keepItems) { - itemsToKeep.add(item); - } - } - } - filteredItems.clear(); - filteredItems.addAll(itemsToKeep); - } - - @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { - subscriber.onResult((List) results.values); + @SuppressWarnings("unchecked") + final List filterResults = (List) results.values; + subscriber.onResult(filterResults); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataType.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataType.java index 363f49d4e60..1e74c471acf 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataType.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataType.java @@ -1,5 +1,7 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; +import android.view.View; +import android.view.ViewGroup; /* * author: I065873 @@ -7,6 +9,5 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; * Data Type for displaying the Race list in the left part of the second activity */ public interface RaceListDataType { - - + View getView(ViewGroup parent); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java index a884c3c61f1..570da733ef7 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeHeader.java @@ -1,26 +1,29 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; -import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.IsFleetFragment; import com.sap.sailing.domain.base.racegroup.RaceGroup; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; +import com.sap.sailing.racecommittee.app.R; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; public class RaceListDataTypeHeader implements RaceListDataType, IsFleetFragment { + private RaceGroupSeries data; + private final LayoutInflater mInflater; - private RaceGroupSeriesFleet data; - private boolean isFleetVisible; - - public RaceListDataTypeHeader(RaceGroupSeriesFleet data) { - this(data, true); + public RaceListDataTypeHeader(RaceGroupSeries data, LayoutInflater layoutInflater) { + this(data, true, layoutInflater); } - public RaceListDataTypeHeader(RaceGroupSeriesFleet data, boolean isFleetVisible) { + public RaceListDataTypeHeader(RaceGroupSeries data, boolean isFleetVisible, LayoutInflater layoutInflater) { this.data = data; - this.isFleetVisible = isFleetVisible; + this.mInflater = layoutInflater; } - public RaceGroupSeriesFleet getRegattaSeriesFleet() { + public RaceGroupSeries getRegattaSeries() { return data; } @@ -32,14 +35,14 @@ public class RaceListDataTypeHeader implements RaceListDataType, IsFleetFragment return data.getSeries(); } - public Fleet getFleet() { - return data.getFleet(); - } - - public boolean isFleetVisible() { return isFleetVisible; } - @Override public String toString() { return data.getDisplayName(); } + + @Override + public View getView(ViewGroup parent) { + return mInflater.inflate(R.layout.race_list_area_header, parent, false); + } + } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java index 9dd5f655638..aed944c15bd 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java @@ -5,6 +5,9 @@ import java.text.SimpleDateFormat; import java.util.Locale; import android.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.R; @@ -23,10 +26,11 @@ public class RaceListDataTypeRace implements RaceListDataType { private static String finishedTemplate; private static String unknownTemplate; + private final ManagedRace race; + private final RaceGroupSeriesFleet fleet; + private final LayoutInflater mInflater; private boolean updateIndicatorVisible = false; private RaceLogRaceStatus currentStatus = RaceLogRaceStatus.UNKNOWN; - private ManagedRace race; - private RaceGroupSeriesFleet fleet; private Format scheduleFormatter = new SimpleDateFormat("HH:mm", Locale.US); @@ -41,14 +45,15 @@ public class RaceListDataTypeRace implements RaceListDataType { unknownTemplate = fragment.getString(R.string.racelist_unknown); } - public RaceListDataTypeRace(ManagedRace race) { - this(race, null); + public RaceListDataTypeRace(ManagedRace race, LayoutInflater layoutInflater) { + this(race, null, layoutInflater); } - public RaceListDataTypeRace(ManagedRace race, RaceGroupSeriesFleet fleet) { + public RaceListDataTypeRace(ManagedRace race, RaceGroupSeriesFleet fleet, LayoutInflater layoutInflater) { this.race = race; this.fleet = fleet; this.currentStatus = race.getStatus(); + this.mInflater = layoutInflater; } public void onStatusChanged(RaceLogRaceStatus status, boolean allowUpdateIndicator) { @@ -114,4 +119,9 @@ public class RaceListDataTypeRace implements RaceListDataType { return scheduleFormatter.format(startTime.asDate()); } + @Override + public View getView(ViewGroup parent) { + return mInflater.inflate(R.layout.race_list_area_item, parent, false); + } + } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 61ba9453759..6852bdde769 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -1,14 +1,38 @@ package com.sap.sailing.racecommittee.app.ui.fragments; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; +import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.shared.util.AppUtils; +import com.sap.sailing.android.shared.util.BitmapHelper; +import com.sap.sailing.android.shared.util.BroadcastManager; +import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; +import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; +import com.sap.sailing.racecommittee.app.AppConstants; +import com.sap.sailing.racecommittee.app.AppPreferences; +import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.RaceApplication; +import com.sap.sailing.racecommittee.app.data.DataManager; +import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; +import com.sap.sailing.racecommittee.app.domain.ManagedRace; +import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; +import com.sap.sailing.racecommittee.app.ui.activities.SessionActivity; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.ManagedRaceListAdapter; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace; +import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.ProtestTimeDialogFragment; +import com.sap.sailing.racecommittee.app.utils.RaceHelper; +import com.sap.sailing.racecommittee.app.utils.StringHelper; +import com.sap.sailing.racecommittee.app.utils.ThemeHelper; +import com.sap.sailing.racecommittee.app.utils.TickListener; +import com.sap.sailing.racecommittee.app.utils.TickSingleton; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; + import android.app.Activity; import android.app.FragmentTransaction; import android.content.BroadcastReceiver; @@ -41,34 +65,6 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import com.sap.sailing.android.shared.logging.ExLog; -import com.sap.sailing.android.shared.util.AppUtils; -import com.sap.sailing.android.shared.util.BitmapHelper; -import com.sap.sailing.android.shared.util.BroadcastManager; -import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; -import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; -import com.sap.sailing.domain.base.SeriesBase; -import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.AppPreferences; -import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.RaceApplication; -import com.sap.sailing.racecommittee.app.data.DataManager; -import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; -import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; -import com.sap.sailing.racecommittee.app.ui.activities.SessionActivity; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.ManagedRaceListAdapter; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeHeader; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace; -import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.ProtestTimeDialogFragment; -import com.sap.sailing.racecommittee.app.utils.RaceHelper; -import com.sap.sailing.racecommittee.app.utils.StringHelper; -import com.sap.sailing.racecommittee.app.utils.ThemeHelper; -import com.sap.sailing.racecommittee.app.utils.TickListener; -import com.sap.sailing.racecommittee.app.utils.TickSingleton; -import com.sap.sse.common.TimePoint; - public class RaceListFragment extends LoggableFragment implements OnItemClickListener, OnItemSelectedListener, TickListener, OnScrollListener { private final static String TAG = RaceListFragment.class.getName(); @@ -88,8 +84,8 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis private ManagedRace mSelectedRace; private IntentReceiver mReceiver; private boolean mUpdateList = true; - private ArrayList mViewItems; private View mProgress; + private final Set mAllRaces; private BaseRaceStateChangedListener stateListener = new BaseRaceStateChangedListener() { @Override @@ -107,13 +103,12 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis filterChanged(); } }; - public RaceListFragment() { mFilterMode = FilterMode.ACTIVE; mSelectedRace = null; mManagedRacesById = new LinkedHashMap<>(); mRacesByGroup = new LinkedHashMap<>(); - mViewItems = new ArrayList<>(); + mAllRaces = new HashSet<>(); } public static RaceListFragment newInstance(int layout) { @@ -202,7 +197,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis super.onActivityCreated(savedInstanceState); RaceListDataTypeRace.initializeTemplates(this); if (mListView != null) { - mAdapter = new ManagedRaceListAdapter(getActivity(), mViewItems); + mAdapter = new ManagedRaceListAdapter(getActivity(), mAllRaces); mListView.setAdapter(mAdapter); mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); mListView.setOnItemClickListener(this); @@ -404,98 +399,16 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis } } - private void initializeRacesByGroup() { - mRacesByGroup.clear(); - for (ManagedRace race : mManagedRacesById.values()) { - RaceGroupSeriesFleet container = new RaceGroupSeriesFleet(race); - if (!mRacesByGroup.containsKey(container)) { - mRacesByGroup.put(container, new LinkedList()); - } - mRacesByGroup.get(container).add(race); - } - } - - private void initializeViewElements() { - // 1. Group races by - initializeRacesByGroup(); - - // 2. Remove previous view items - mViewItems.clear(); - - - // 3. Group active fleets from tree by series - Map> seriesWithFleets = getFleetsGroupedBySeries(mRacesByGroup.keySet()); - - // 4. Create view elements from series with fleets - for (SeriesBase series : seriesWithFleets.keySet()) { - List fleets = seriesWithFleets.get(series); - if (fleets.size() == 1) { // Default fleet - // ... add the header view for default fleet... - RaceGroupSeriesFleet fleet = fleets.get(0); - mViewItems.add(new RaceListDataTypeHeader(fleet)); - List races = mRacesByGroup.get(fleet); - for (ManagedRace race : races) { - // ... and add the race view! - mViewItems.add(new RaceListDataTypeRace(race)); - } - } else if (fleets.size() > 1) { - // ... add the header view for all following fleets... - mViewItems.add(new RaceListDataTypeHeader(fleets.get(0), false)); - // ... and add all the sorted race views! - List raceListDataTypeRaces = getRaceListDataTypeRaces(mRacesByGroup, fleets); - raceListDataTypeRaces = sortRaceList(raceListDataTypeRaces, fleets.size()); - for (RaceListDataTypeRace raceListDataTypeRace : raceListDataTypeRaces) { - mViewItems.add(raceListDataTypeRace); - } - } - } - } - - private List sortRaceList(List raceList, int fleetSize) { - int racePerFleet = raceList.size() / fleetSize; - List sortedList = new ArrayList<>(); - for (int race = 0; race < racePerFleet; race++) { - for (int fleet = 0; fleet < fleetSize; fleet++) { - sortedList.add(raceList.get(race + (fleet * racePerFleet))); - } - } - return sortedList; - } - - private static LinkedHashMap> getFleetsGroupedBySeries(Set raceGroupSeriesFleets) { - LinkedHashMap> seriesWithFleets = new LinkedHashMap<>(); - for (RaceGroupSeriesFleet key : raceGroupSeriesFleets) { - SeriesBase series = key.getSeries(); - List fleets = seriesWithFleets.get(series); - if (fleets == null) { - fleets = new ArrayList<>(); - } - fleets.add(key); - seriesWithFleets.put(series, fleets); - } - return seriesWithFleets; - } - - private static List getRaceListDataTypeRaces(LinkedHashMap> racesByGroup, - List fleets) { - List races = new ArrayList<>(); - for (RaceGroupSeriesFleet fleet : fleets) { - for (ManagedRace race : racesByGroup.get(fleet)) { - races.add(new RaceListDataTypeRace(race, fleet)); - } - } - return races; - } - - public void setupOn(Collection races) { - ExLog.i(getActivity(), TAG, String.format("Setting up %s with %d races.", this.getClass().getSimpleName(), races.size())); + public void setupOn(Iterable races) { + ExLog.i(getActivity(), TAG, String.format("Setting up %s with %d races.", this.getClass().getSimpleName(), Util.size(races))); unregisterOnAllRaces(); mManagedRacesById.clear(); for (ManagedRace managedRace : races) { mManagedRacesById.put(managedRace.getId(), managedRace); } + mAllRaces.clear(); + Util.addAll(races, mAllRaces); registerOnAllRaces(); - initializeViewElements(); // prepare views and do initial filtering filterChanged(); mAdapter.notifyDataSetChanged(); From 4781e0353190d3ab6396d71d0e4ce2c88c005185 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 21 Apr 2016 20:57:19 +0200 Subject: [PATCH 28/57] formatting only Change-Id: I2a85ba37e507c9ab94e413b29e9c189275a72c33 --- .../analyzing/impl/RaceStatusAnalyzer.java | 20 +++++++++---------- .../racegroup/impl/CurrentRaceFilterImpl.java | 1 + .../sailing/gwt/home/server/RaceContext.java | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java index 823694ac179..476e5238d7a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java @@ -79,15 +79,15 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { private void setRaceLogStatusBasedOnStartTime(TimePoint startTime) { if (startTime == null) { nextStatus = RaceLogRaceStatus.PRESCHEDULED; - return; - } - TimePoint now = clock.now(); - if (racingProcedure.isStartphaseActive(startTime, now)) { - nextStatus = RaceLogRaceStatus.STARTPHASE; - } else if (now.before(startTime)) { - nextStatus = RaceLogRaceStatus.SCHEDULED; } else { - nextStatus = RaceLogRaceStatus.RUNNING; + TimePoint now = clock.now(); + if (racingProcedure.isStartphaseActive(startTime, now)) { + nextStatus = RaceLogRaceStatus.STARTPHASE; + } else if (now.before(startTime)) { + nextStatus = RaceLogRaceStatus.SCHEDULED; + } else { + nextStatus = RaceLogRaceStatus.RUNNING; + } } } @@ -101,6 +101,6 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { DependentStartTimeResolver startTimeResolver = new DependentStartTimeResolver(resolver); TimePoint startTime = startTimeResolver.resolve(event).getStartTime(); setRaceLogStatusBasedOnStartTime(startTime); - }; - }; + } + } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 154a5e4125e..323c2767092 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -21,6 +21,7 @@ public class CurrentRaceFilterImpl implements CurrentRaceFilter { @Override public Set filterCurrentRaces(Set allRaces) { final Set filteredRaces = new HashSet<>(); + // TODO implement the CurrentRaceFilter algorithm here... filteredRaces.addAll(allRaces); return filteredRaces; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java index 08236de9e4b..edc8421f2b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java @@ -293,7 +293,7 @@ public class RaceContext { case FINISHING: case RUNNING: case SCHEDULED: - case PRESCHEDULED: + case PRESCHEDULED: case STARTPHASE: case UNKNOWN: break; From f3e9219804f07829088544f9edb54ea252fd52e6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 21 Apr 2016 23:01:07 +0200 Subject: [PATCH 29/57] a first implementation of the new current races filter algorithm according to the specification in bug 3011 Change-Id: I5f29d35bbbde9429bff5f3219fa6c22455c6e9d2 --- .../impl/CurrentRaceFilterImplTest.java | 8 +- .../analyzing/impl/RaceStatusAnalyzer.java | 8 +- .../base/racegroup/CurrentRaceFilter.java | 4 +- .../domain/base/racegroup/FilterableRace.java | 2 + .../base/racegroup/RaceGroupSeries.java | 14 +- .../base/racegroup}/RaceGroupSeriesFleet.java | 13 +- .../RaceGroupSeriesFleetRaceColumn.java | 56 ++++ .../domain/base/racegroup/SeriesWithRows.java | 5 +- .../racegroup/impl/CurrentRaceFilterImpl.java | 272 ++++++++++-------- .../base/racegroup/impl/RaceRowImpl.java | 4 + .../racegroup/impl/SeriesWithRowsImpl.java | 14 + .../racegroup/impl/RaceRowDeserializer.java | 1 + .../racegroup/impl/RaceRowJsonSerializer.java | 7 +- .../gateway/impl/rc/RaceGroupFactory.java | 17 +- .../src/com/sap/sse/common/Util.java | 23 +- .../app/ui/activities/RacingActivity.java | 2 +- .../racelist/ManagedRaceListAdapter.java | 2 +- .../app/ui/adapters/racelist/RaceFilter.java | 19 +- .../racelist/RaceListDataTypeRace.java | 2 +- .../app/ui/fragments/RaceListFragment.java | 2 +- .../dialogs/ProtestTimeDialogFragment.java | 2 +- .../fragments/raceinfo/StartTimeFragment.java | 2 +- .../ui/fragments/raceinfo/WindFragment.java | 2 +- .../racecommittee/app/utils/RaceHelper.java | 2 +- 24 files changed, 328 insertions(+), 155 deletions(-) rename {mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl => java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup}/RaceGroupSeriesFleet.java (88%) create mode 100644 java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index 8f70f450868..c1a488d5090 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -35,7 +35,7 @@ public class CurrentRaceFilterImplTest { RaceFragment firstRace = new RaceFragment(RaceLogRaceStatus.UNSCHEDULED); values.add(firstRace); - List result = fixture.filterCurrentRaces(values); + List result = fixture.getCurrentRaces(); Assert.assertThat(result.size(), Matchers.equalTo(2)); Assert.assertThat(result.get(0), Matchers.equalTo(fristSeries)); Assert.assertThat(result.get(1), Matchers.equalTo(firstRace)); @@ -49,14 +49,14 @@ public class CurrentRaceFilterImplTest { values.addAll(createRacesForSeries(3, seriesBase1)); values.addAll(createRacesForSeries(3, seriesBase2)); - List result = fixture.filterCurrentRaces(values); + List result = fixture.getCurrentRaces(); Assert.assertThat(result.size(), Matchers.equalTo(2)); Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); ((RaceFragment) values.get(5)).setCurrentStatus(RaceLogRaceStatus.PRESCHEDULED); - result = fixture.filterCurrentRaces(values); + result = fixture.getCurrentRaces(); Assert.assertThat(result.size(), Matchers.equalTo(5)); Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); @@ -66,7 +66,7 @@ public class CurrentRaceFilterImplTest { ((RaceFragment) values.get(5)).setCurrentStatus(RaceLogRaceStatus.RUNNING); - result = fixture.filterCurrentRaces(values); + result = fixture.getCurrentRaces(); Assert.assertThat(result.size(), Matchers.equalTo(3)); Assert.assertThat(result.get(0), Matchers.equalTo(values.get(4))); Assert.assertThat(result.get(1), Matchers.equalTo(values.get(5))); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java index 476e5238d7a..889c5b9d27e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceStatusAnalyzer.java @@ -44,9 +44,10 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { @Override protected RaceLogRaceStatus performAnalysis() { - ArrayListNavigableSet statusEvents = new ArrayListNavigableSet<>(RaceLogRaceStatusEventComparator.INSTANCE); - for(RaceLogEvent event: getPassEvents()) { - if(event instanceof RaceLogRaceStatusEvent) { + ArrayListNavigableSet statusEvents = new ArrayListNavigableSet<>( + RaceLogRaceStatusEventComparator.INSTANCE); + for (RaceLogEvent event : getPassEvents()) { + if (event instanceof RaceLogRaceStatusEvent) { statusEvents.add((RaceLogRaceStatusEvent) event); } } @@ -54,7 +55,6 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { event.accept(eventDispatcher); return eventDispatcher.nextStatus; } - return RaceLogRaceStatus.UNSCHEDULED; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java index 8d4ca29da0b..1dcd61afa97 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceFilter.java @@ -12,6 +12,6 @@ import com.sap.sailing.domain.abstractlog.race.state.RaceState; * @author Charlotte Proeller (D062347) * @author Axel Uhl (d043530) */ -public interface CurrentRaceFilter { - Set filterCurrentRaces(Set allRaces); +public interface CurrentRaceFilter { + Set getCurrentRaces(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java index b047e385999..e510c87c421 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/FilterableRace.java @@ -40,4 +40,6 @@ public interface FilterableRace { int getZeroBasedIndexInFleet(); int getZeroBasedSeriesIndex(); + + String getRaceColumnName(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java index 5151c9f6cc0..41048c76332 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java @@ -20,13 +20,17 @@ public class RaceGroupSeries { private int seriesOrder; public RaceGroupSeries(FilterableRace race) { - raceGroup = race.getRaceGroup(); - series = race.getSeries(); - seriesOrder = getSeriesIndex(race, series); + this(race.getRaceGroup(), race.getSeries()); } - private static int getSeriesIndex(FilterableRace race, SeriesBase series) { - return Util.indexOf(race.getRaceGroup().getSeries(), series); + public RaceGroupSeries(RaceGroup raceGroup, SeriesBase series) { + this.raceGroup = raceGroup; + this.series = series; + seriesOrder = getSeriesIndex(raceGroup, series); + } + + private static int getSeriesIndex(RaceGroup raceGroup, SeriesBase series) { + return Util.indexOf(raceGroup.getSeries(), series); } public RaceGroup getRaceGroup() { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java similarity index 88% rename from mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java rename to java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java index 860677dc3e8..5bb3fcf0894 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/RaceGroupSeriesFleet.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java @@ -1,11 +1,8 @@ -package com.sap.sailing.racecommittee.app.domain.impl; +package com.sap.sailing.domain.base.racegroup; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; -import com.sap.sailing.domain.base.racegroup.RaceGroup; -import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; import com.sap.sailing.domain.common.LeaderboardNameConstants; -import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sse.common.Util; /** @@ -22,11 +19,17 @@ public class RaceGroupSeriesFleet extends RaceGroupSeries { private Fleet fleet; private int fleetOrder; - public RaceGroupSeriesFleet(ManagedRace race) { + public RaceGroupSeriesFleet(FilterableRace race) { super(race); fleet = race.getFleet(); fleetOrder = getFleetIndex(race.getSeries().getFleets(), race.getFleet()); } + + public RaceGroupSeriesFleet(RaceGroup raceGroup, SeriesBase series, Fleet fleet) { + super(raceGroup, series); + this.fleet = fleet; + getFleetIndex(series.getFleets(), fleet); + } private int getFleetIndex(Iterable fleets, Fleet fleet) { return Util.indexOf(fleets, fleet); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java new file mode 100644 index 00000000000..9b00b83183c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java @@ -0,0 +1,56 @@ +package com.sap.sailing.domain.base.racegroup; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.SeriesBase; + +/** + * Represents a quadruple of {@link RaceGroup} (representing a regatta or a flexible leaderboard), a {@link SeriesBase + * series}, a {@link Fleet} and a race column, identified by its unique name within the leaderboard. An instance can be + * constructed for a {@link FilterableRace} which then extracts these four properties from the race. Equal + * objects of this type can only result for equal races because they identify a "race slot" in a regatta. + * + * @author Axel Uhl (d043530) + * + */ +public class RaceGroupSeriesFleetRaceColumn extends RaceGroupSeriesFleet { + private final String raceColumnName; + + public RaceGroupSeriesFleetRaceColumn(FilterableRace race) { + super(race); + raceColumnName = race.getRaceColumnName(); + } + + public RaceGroupSeriesFleetRaceColumn(RaceGroup raceGroup, SeriesBase series, Fleet fleet, String raceColumnName) { + super(raceGroup, series, fleet); + this.raceColumnName = raceColumnName; + } + + public String getRaceColumnName() { + return raceColumnName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((raceColumnName == null) ? 0 : raceColumnName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + RaceGroupSeriesFleetRaceColumn other = (RaceGroupSeriesFleetRaceColumn) obj; + if (raceColumnName == null) { + if (other.raceColumnName != null) + return false; + } else if (!raceColumnName.equals(other.raceColumnName)) + return false; + return true; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/SeriesWithRows.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/SeriesWithRows.java index 2e1604bf79a..90dbbc216fd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/SeriesWithRows.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/SeriesWithRows.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.base.racegroup; +import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; /** @@ -11,5 +12,7 @@ public interface SeriesWithRows extends SeriesBase { /** * All {@link RaceRow}s of this series. */ - public Iterable getRaceRows(); + Iterable getRaceRows(); + + RaceRow getRaceRow(Fleet fleet); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 323c2767092..3741597a08f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -1,142 +1,180 @@ package com.sap.sailing.domain.base.racegroup.impl; -import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.Iterator; import java.util.Map; import java.util.Set; +import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; import com.sap.sailing.domain.base.racegroup.FilterableRace; -import com.sap.sailing.domain.base.racegroup.IsFleetFragment; -import com.sap.sailing.domain.base.racegroup.IsRaceFragment; -import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; +import com.sap.sailing.domain.base.racegroup.RaceCell; +import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleetRaceColumn; +import com.sap.sailing.domain.base.racegroup.RaceRow; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sse.common.Util; -public class CurrentRaceFilterImpl implements CurrentRaceFilter { +/** + * Determines a subset from a set of {@link FilterableRace} objects based on a definition of "being current." Current + * races are interesting for a race officer to deal with. For example, the next set of races that are eligible for being + * scheduled are part of this set; furthermore all running races. See {@link #getCurrentRaces()} for a more formal + * definition what this filter considers to be a "current" race. + *

+ * + * An instance of this type is initialized with the full set of races out of which the "current" subset shall be + * extracted by the {@link #getCurrentRaces()} method. The full set of races is expected to skip no race columns and no + * fleets and no series, or in other words, hold all races that form the regatta structure. This is important for the + * "predecessor" relation which uses the fleet and series indices, such as + * {@link FilterableRace#getZeroBasedIndexInFleet()} and {@link FilterableRace#getZeroBasedSeriesIndex()}. + * + * @author Axel Uhl (d043530) + * + * @param + */ +public class CurrentRaceFilterImpl implements CurrentRaceFilter { + private final Set allRaces; + private final Map racesByRaceGroupSeriesFleet; + public CurrentRaceFilterImpl(Set allRaces) { + this.allRaces = allRaces; + this.racesByRaceGroupSeriesFleet = new HashMap<>(); + for (final T race : allRaces) { + racesByRaceGroupSeriesFleet.put(new RaceGroupSeriesFleet(race), race); + } + } + + /** + * A race is considered "current" and shall be shown if its status is between (inclusive) + * {@link RaceLogRaceStatus#SCHEDULED} and {@link RaceLogRaceStatus#FINISHING}, or if the race + * has no predecessor in the regatta / leaderboard and therefore could start the regatta, + * or if the race has an immediate predecessor (a race A that must precede the race in question, say X, + * such that there is no other race B that also must precede X with A having to precede B) which + * is at least in state {@link RaceLogRaceStatus#SCHEDULED}. + */ @Override - public Set filterCurrentRaces(Set allRaces) { + public Set getCurrentRaces() { final Set filteredRaces = new HashSet<>(); - - // TODO implement the CurrentRaceFilter algorithm here... - filteredRaces.addAll(allRaces); + for (final T race : allRaces) { + if ((race.getStatus().getOrderNumber() >= RaceLogRaceStatus.SCHEDULED.getOrderNumber() && + race.getStatus().getOrderNumber() <= RaceLogRaceStatus.FINISHING.getOrderNumber()) || + hasNoPredecessor(race) || + hasImmediatePredecessorThatIsAtLeastScheduled(race)) { + // show the race + filteredRaces.add(race); + } + } return filteredRaces; } - private Map> removeAllListsBeforeLastStarted(Map> filteredRaces) { - Map> result; - IsFleetFragment keyOfLastStartedRace = null; - for (IsFleetFragment currentKey : filteredRaces.keySet()) { - if (containsActiceRace(filteredRaces.get(currentKey))) { - keyOfLastStartedRace = currentKey; - } - } - if (keyOfLastStartedRace != null) { - result = new LinkedHashMap<>(); - boolean found = false; - for (IsFleetFragment currentKey : filteredRaces.keySet()) { - if (found) { - result.put(currentKey, filteredRaces.get(currentKey)); - } else if (currentKey.equals(keyOfLastStartedRace)) { - found = true; - result.put(currentKey, filteredRaces.get(currentKey)); - } + /** + * For a race to have no predecessor in the regatta structure it has to be located in the first race column in the first series. + * If the races of the different fleets in the series may {@link SeriesBase#isFleetsRunInParallel() run in parallel} then this is + * already sufficient. If the races of the different fleets in the series are run in sequence, the race must additionally be + * in the first fleet of the series. + */ + private boolean hasNoPredecessor(FilterableRace race) { + return race.getZeroBasedSeriesIndex() == 0 && race.getZeroBasedIndexInFleet() == 0 && + (race.getSeries().isFleetsRunInParallel() || Util.indexOf(race.getSeries().getFleets(), race.getFleet()) == 0); + } + + /** + * Two cases need to be distinguished: the races in different fleets of the same series may be run + * in parallel or not.

+ * + * Parallel fleets in series: the immediate predecessor is the race in the same fleet in the immediately preceding + * race column; or if {@code race} is in the first column of its series, all races in the last column of the immediately + * preceding series.

+ * + * Sequential fleets in series: the immediate predecessor is the race that immediately precedes {@code race} in + * the list of races constructed by looping over all columns of the series and for each column looping over all + * fleets. If {@code race} is the first in this sequence of its series, then the immediate predecessors is/are the + * last race(s) (multiple if the previous series runs its fleets in parallel) of the immediately preceding series.

+ * + * Precondition: {@code !hasNoPredecessor(race)} + * + * @return whether any of the predecessors (of which there is least one because of the precondition) have a + * {@link RaceLogRaceStatus race status} whose {@link RaceLogRaceStatus#getOrderNumber() order number} is at least + * {@link RaceLogRaceStatus#SCHEDULED SCHEDULED.getOrderNumber()}. + */ + private boolean hasImmediatePredecessorThatIsAtLeastScheduled(FilterableRace race) { + final Set immediatePredecessors = new HashSet<>(); + final RaceGroup raceGroup = race.getRaceGroup(); + if (race.getSeries().isFleetsRunInParallel()) { + if (race.getZeroBasedIndexInFleet() == 0) { + // it's in the first column; for fleets that may run in parallel, the immediate predecessors are + // in the last column of the immediately preceding series: + Util.addAll(getLastRacesFromImmediatelyPrecedingSeries(race, raceGroup), immediatePredecessors); + } else { + // not the first race column in its fleet; for parallel fleets, only the immediate predecessor + // in the same fleet is an immediate predecessor for race + RaceRow raceRow = Util.get(raceGroup.getSeries(), race.getZeroBasedSeriesIndex()).getRaceRow(race.getFleet()); + RaceCell previousCellInFleet = Util.get(raceRow.getCells(), race.getZeroBasedIndexInFleet()-1); + immediatePredecessors.add(racesByRaceGroupSeriesFleet.get( + new RaceGroupSeriesFleetRaceColumn(raceGroup, race.getSeries(), race.getFleet(), previousCellInFleet.getName()))); } } else { - result = filteredRaces; - } - return result; - } - - private boolean containsActiceRace(List races) { - for (IsRaceFragment race : races) { - if (RaceLogRaceStatus.isRunningOrFinished(race.getCurrentStatus())) { - return true; - } - } - return false; - } - - /** - * method maps the races to fleets/series and removes all that should be not shown with "A follows B" relationship - * - * @param allItems - * All SORTED Items - * @return all current Races mapped to there fleet/series - */ - private Map> filterRaces(Collection allItems) { - Map> mappedItems = mapItems(allItems); - List newValueForKey = null; - SeriesBase firstSeries = mappedItems.keySet().iterator().next().getSeries(); - for (IsFleetFragment currentKey : mappedItems.keySet()) { - newValueForKey = filterWithFollowing(mappedItems.get(currentKey), - currentKey.getSeries().equals(firstSeries)); - mappedItems.put(currentKey, newValueForKey); - } - - return mappedItems; - } - - /** - * Filters a List (with races of the same series and fleet). - * - * @param list - * @return - */ - private List filterWithFollowing(List races, boolean isFirstSeries) { - List result = new LinkedList<>(); - boolean addNext = isFirstSeries; - for (IsRaceFragment currentRace : races) { - if (hasShowingStatus(currentRace)) { - result.add(currentRace); - addNext = true; - } else if (addNext) { - result.add(currentRace); - addNext = false; - } - } - return result; - } - - /** - * Checks if the status is A Status to Show - * - * @param currentRace - * @return - */ - private boolean hasShowingStatus(IsRaceFragment currentRace) { - return RaceLogRaceStatus.isActive(currentRace.getCurrentStatus()) - || RaceLogRaceStatus.isActive(currentRace.getCurrentStatus()); - } - - /** - * maps the Races to there Fleet/series - * - * @param allItems - * @return - */ - private Map> mapItems(Collection allItems) { - Map> resultMap = new LinkedHashMap<>(); - IsFleetFragment currentKey = null; - List currentValue = null; - for (T currentItem : allItems) { - if (currentItem instanceof IsFleetFragment) { - if (currentKey != null && currentValue != null) { - resultMap.put(currentKey, currentValue); - } - currentKey = (IsFleetFragment) currentItem; - currentValue = new LinkedList<>(); + // race's series is run sequentially, within one race column one fleet after the other, in the order of the + // fleets as specified by the series; the immediate predecessor is within this series the previous element in this + // ordering, or the last race(s) from the immediately preceding series if race is in the first column's first fleet. + final int zeroBasedIndexOfFleetInSeries = Util.indexOf(race.getSeries().getFleets(), race.getFleet()); + if (race.getZeroBasedIndexInFleet() == 0 && zeroBasedIndexOfFleetInSeries == 0) { + Util.addAll(getLastRacesFromImmediatelyPrecedingSeries(race, raceGroup), immediatePredecessors); } else { - currentValue.add((IsRaceFragment) currentItem); + final Fleet fleetOfImmediatelyPrecedingRace; + final int zeroBasedIndexOfImmediatelyPrecedingRaceInFleet; + if (zeroBasedIndexOfFleetInSeries == 0) { + // race is in first fleet; wrap around to last fleet in immediately preceding column + fleetOfImmediatelyPrecedingRace = Util.last(race.getSeries().getFleets()); + zeroBasedIndexOfImmediatelyPrecedingRaceInFleet = race.getZeroBasedIndexInFleet() - 1; + } else { + fleetOfImmediatelyPrecedingRace = Util.get(race.getSeries().getFleets(), zeroBasedIndexOfFleetInSeries-1); + zeroBasedIndexOfImmediatelyPrecedingRaceInFleet = race.getZeroBasedIndexInFleet(); + } + final RaceGroupSeriesFleetRaceColumn identifierOfImmediatelyPrecedingRace = + new RaceGroupSeriesFleetRaceColumn(raceGroup, race.getSeries(), fleetOfImmediatelyPrecedingRace, + Util.get(Util.get(raceGroup.getSeries(), race.getZeroBasedSeriesIndex()).getRaceRow( + fleetOfImmediatelyPrecedingRace).getCells(), zeroBasedIndexOfImmediatelyPrecedingRaceInFleet).getName()); + immediatePredecessors.add(racesByRaceGroupSeriesFleet.get(identifierOfImmediatelyPrecedingRace)); } } - if (currentKey != null && currentValue != null) { - resultMap.put(currentKey, currentValue); + final Iterator i = immediatePredecessors.iterator(); + boolean result = false; + while (!result && i.hasNext()) { + final RaceLogRaceStatus nextStatus = i.next().getStatus(); + result = nextStatus.getOrderNumber() >= RaceLogRaceStatus.SCHEDULED.getOrderNumber(); } - return resultMap; + return result; + } + + private Iterable getLastRacesFromImmediatelyPrecedingSeries(FilterableRace race, final RaceGroup raceGroup) { + final Set lastRacesInImmediatelyPrecedingSeries = new HashSet<>(); + final SeriesWithRows immediatelyPrecedingSeries = Util.get(raceGroup.getSeries(), race.getZeroBasedSeriesIndex()-1); + if (immediatelyPrecedingSeries.isFleetsRunInParallel()) { + // add all last races of all fleets in immediately preceding series because they may be run in parallel + for (final RaceRow rowInImmediatelyPrecedingSeries : immediatelyPrecedingSeries.getRaceRows()) { + final T lastRaceInRow = getLastRaceInRow(raceGroup, immediatelyPrecedingSeries, rowInImmediatelyPrecedingSeries); + lastRacesInImmediatelyPrecedingSeries.add(lastRaceInRow); + } + } else { + // add only the last race of the last fleet in the immediately preceding series + final RaceRow lastRowInImmediatelyPrecedingSeries = Util.last(immediatelyPrecedingSeries.getRaceRows()); + lastRacesInImmediatelyPrecedingSeries.add(getLastRaceInRow(raceGroup, immediatelyPrecedingSeries, lastRowInImmediatelyPrecedingSeries)); + } + return lastRacesInImmediatelyPrecedingSeries; + } + + private T getLastRaceInRow(final RaceGroup raceGroup, final SeriesWithRows immediatelyPrecedingSeries, + final RaceRow rowInImmediatelyPrecedingSeries) { + final RaceCell lastCell = Util.last(rowInImmediatelyPrecedingSeries.getCells()); + RaceGroupSeriesFleetRaceColumn identifierOfALastRaceInImmediatelyPrecedingSeries = + new RaceGroupSeriesFleetRaceColumn(raceGroup, immediatelyPrecedingSeries, + rowInImmediatelyPrecedingSeries.getFleet(), lastCell.getName()); + final T lastRaceInRow = racesByRaceGroupSeriesFleet.get(identifierOfALastRaceInImmediatelyPrecedingSeries); + return lastRaceInRow; } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java index 16a4d2a7d38..9be6959994a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java @@ -9,6 +9,10 @@ public class RaceRowImpl implements RaceRow { private Fleet fleet; private Iterable races; + /** + * @param races + * expected to be provided in the order in which the race columns appear in the series / leaderboard + */ public RaceRowImpl(Fleet fleet, Iterable races) { this.fleet = fleet; this.races = races; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java index fa5aa6c7126..9c925c3935f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java @@ -20,18 +20,32 @@ public class SeriesWithRowsImpl implements SeriesWithRows { this.isMedal = isMedal; } + @Override public String getName() { return name; } + @Override public boolean isMedal() { return isMedal; } + @Override public Iterable getRaceRows() { return raceRows; } + @Override + public RaceRow getRaceRow(Fleet fleet) { + for (final RaceRow row : getRaceRows()) { + if (row.getFleet().equals(fleet)) { + return row; + } + } + return null; + } + + @Override public Iterable getFleets() { Collection fleets = new ArrayList(); for (RaceRow row : raceRows) { diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceRowDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceRowDeserializer.java index 53d75393e6d..e4f22b84f44 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceRowDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/RaceRowDeserializer.java @@ -28,6 +28,7 @@ public class RaceRowDeserializer implements JsonDeserializer { public RaceRow deserialize(JSONObject object) throws JsonDeserializationException { JSONObject fleetObject = Helpers.toJSONObjectSafe(object.get(RaceRowJsonSerializer.FIELD_FLEET)); + // the race cells are expected to be written in the order of the race columns in the series / leaderboard JSONArray cellsObject = Helpers.getNestedArraySafe(object, RaceRowJsonSerializer.FIELD_RACE_CELLS); Collection races = new ArrayList(); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceRowJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceRowJsonSerializer.java index 3324a5a1c7b..3dc047e08a7 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceRowJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/RaceRowJsonSerializer.java @@ -10,6 +10,11 @@ import com.sap.sailing.server.gateway.serialization.JsonSerializer; public class RaceRowJsonSerializer implements JsonSerializer { public static final String FIELD_FLEET = "fleet"; + + /** + * The race cells in the JSONArray keyed by this name are expected to be written + * in the order in which their race columns appear in the series / leaderboard + */ public static final String FIELD_RACE_CELLS = "races"; private JsonSerializer fleetSerializer; @@ -25,14 +30,12 @@ public class RaceRowJsonSerializer implements JsonSerializer { @Override public JSONObject serialize(RaceRow object) { JSONObject result = new JSONObject(); - result.put(FIELD_FLEET, fleetSerializer.serialize(object.getFleet())); JSONArray cells = new JSONArray(); for (RaceCell cell : object.getCells()) { cells.add(cellSerializer.serialize(cell)); } result.put(FIELD_RACE_CELLS, cells); - return result; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java index 53448156a4a..a64084d23b3 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java @@ -80,6 +80,9 @@ public class RaceGroupFactory { return seriesToRaceColumns.keySet(); } + /** + * @param raceColumns must be provided in the same order in which they appear in the series / leaderboard + */ private Collection getRows(Series series, List raceColumns) { Collection rows = new ArrayList<>(); for (Fleet fleet : series.getFleets()) { @@ -94,9 +97,12 @@ public class RaceGroupFactory { return series.isFirstColumnIsNonDiscardableCarryForward(); } - private Collection getCells(String fleetName, List raceColumns, boolean isFirstRaceColumnVirtual) { + /** + * Delivers the race cell in the order of the {@link RaceColumn}s provided in {@code raceColumns}. + */ + private List getCells(String fleetName, List raceColumns, boolean isFirstRaceColumnVirtual) { boolean skippedFirst = false; - Collection cells = new ArrayList<>(); + List cells = new ArrayList<>(); if (raceColumns != null) { int zeroBasedIndexOfRaceInFleet = 0; for (final RaceColumn raceColumn : raceColumns) { @@ -104,7 +110,8 @@ public class RaceGroupFactory { skippedFirst = true; } else { Fleet fleet = raceColumn.getFleetByName(fleetName); - cells.add(new RaceCellImpl(raceColumn.getName(), raceColumn.getRaceLog(fleet), raceColumn.getFactor(), raceColumn.getExplicitFactor(), zeroBasedIndexOfRaceInFleet++)); + cells.add(new RaceCellImpl(raceColumn.getName(), raceColumn.getRaceLog(fleet), + raceColumn.getFactor(), raceColumn.getExplicitFactor(), zeroBasedIndexOfRaceInFleet++)); } } } @@ -113,7 +120,9 @@ public class RaceGroupFactory { /** * Returns a series to race column mapping. If there are no series all race columns will - * be mapped from a default series. + * be mapped from a default series. The race column lists that appear as values in the result + * maintain the order in which the race column objects appear in the original {@link Series} + * or leaderboard. */ private Map> getSeriesToRaceColumns(Leaderboard leaderboard) { Map> seriesToRaceColumns = new HashMap<>(); diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index 624d09fbc0b..1756ebf710d 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -253,7 +253,7 @@ public class Util { List l = (List) iterable; return l.get(i); } else { - Iterator iter = iterable.iterator(); + final Iterator iter = iterable.iterator(); T result = iter.next(); for (int j=0; j T first(Iterable iterable) { + final Iterator iter = iterable.iterator(); + final T result; + if (iter.hasNext()) { + result = iter.next(); + } else { + result = null; + } + return result; + } + + public static T last(Iterable iterable) { + final T result; + if (isEmpty(iterable)) { + result = null; + } else { + result = get(iterable, size(iterable)-1); + } + return result; + } /** * A null-safe check whether t is contained in ts. For ts==null the method diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java index 0c0e17f9492..75c17746e00 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java @@ -16,6 +16,7 @@ import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.AppConstants; @@ -26,7 +27,6 @@ import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; import com.sap.sailing.racecommittee.app.data.clients.LoadClient; import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.logging.LogEvent; import com.sap.sailing.racecommittee.app.services.RaceStateService; import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index aced3420a05..170043f7bb8 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -107,7 +107,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple public void notifyDataSetChanged() { super.notifyDataSetChanged(); createViewItemsForRacesAndSeries(); - getFilter().filter(""); + getFilter().refreshRegattaStructures(); } private void createViewItemsForRacesAndSeries() { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index ee7cd72cb42..e32288b65fe 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -18,7 +18,10 @@ import android.widget.Filter; * {@link CurrentRaceFilter}. */ public class RaceFilter extends Filter { - private final CurrentRaceFilter currentRaceFilter = new CurrentRaceFilterImpl(); + /** + * A filter that is updated each time an update for {@link #allRaces} is announced by {@link #refreshRegattaStructures()}. + */ + private CurrentRaceFilter currentRaceFilter; public interface FilterSubscriber { void onResult(List filtered); @@ -28,9 +31,16 @@ public class RaceFilter extends Filter { private final Set allRaces; private FilterMode filterMode; + /** + * @param allRaces + * callers must call {@link #refreshRegattaStructures()} whenever the contents of this set have changed + * @param resultSubscriber + * triggered when the filter results have changed + */ public RaceFilter(Set allRaces, FilterSubscriber resultSubscriber) { this.allRaces = allRaces; this.subscriber = resultSubscriber; + refreshRegattaStructures(); } public void filterByMode(FilterMode filterMode) { @@ -52,7 +62,7 @@ public class RaceFilter extends Filter { result = createResults(allRaces); } else { // filter using the CurrentRaceFilter - result = createResults(currentRaceFilter.filterCurrentRaces(allRaces)); + result = createResults(currentRaceFilter.getCurrentRaces()); } return result; } @@ -64,4 +74,9 @@ public class RaceFilter extends Filter { subscriber.onResult(filterResults); } + public void refreshRegattaStructures() { + this.currentRaceFilter = new CurrentRaceFilterImpl(allRaces); + filter(""); + } + } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java index aed944c15bd..fd298433d8e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceListDataTypeRace.java @@ -9,10 +9,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sse.common.TimePoint; public class RaceListDataTypeRace implements RaceListDataType { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 6852bdde769..873205337e9 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -12,6 +12,7 @@ import com.sap.sailing.android.shared.util.BitmapHelper; import com.sap.sailing.android.shared.util.BroadcastManager; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; @@ -19,7 +20,6 @@ import com.sap.sailing.racecommittee.app.RaceApplication; import com.sap.sailing.racecommittee.app.data.DataManager; import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.activities.SessionActivity; import com.sap.sailing.racecommittee.app.ui.adapters.racelist.ManagedRaceListAdapter; import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/dialogs/ProtestTimeDialogFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/dialogs/ProtestTimeDialogFragment.java index 404a3f92d84..55900bd2398 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/dialogs/ProtestTimeDialogFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/dialogs/ProtestTimeDialogFragment.java @@ -27,13 +27,13 @@ import com.sap.sailing.android.shared.util.BroadcastManager; import com.sap.sailing.android.shared.util.ScreenHelper; import com.sap.sailing.android.shared.util.ViewHelper; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.DataManager; import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; import com.sap.sailing.racecommittee.app.utils.TimeUtils; import com.sap.sse.common.TimePoint; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java index fc5a4b69e56..897b5191a65 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java @@ -33,6 +33,7 @@ import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl; import com.sap.sailing.domain.abstractlog.race.state.RaceStateChangedListener; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; @@ -42,7 +43,6 @@ import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.DataStore; import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.adapters.DependentRaceSpinnerAdapter; import com.sap.sailing.racecommittee.app.ui.fragments.RaceFragment; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/WindFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/WindFragment.java index 3f71c638315..a6fd60c41db 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/WindFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/WindFragment.java @@ -39,6 +39,7 @@ import com.google.android.gms.location.LocationServices; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.util.AppUtils; import com.sap.sailing.android.shared.util.ViewHelper; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; @@ -50,7 +51,6 @@ import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.activities.RacingActivity; import com.sap.sailing.racecommittee.app.ui.utils.OnRaceUpdatedListener; import com.sap.sailing.racecommittee.app.ui.views.AccuracyView; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java index 03067c6ec0b..986175e7d79 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java @@ -15,12 +15,12 @@ import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.GateSt import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.domain.impl.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.GateStartTimingFragment; public class RaceHelper { From 47ec06cffdf0caa3fbff7a794af202c550f623f2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 00:04:51 +0200 Subject: [PATCH 30/57] a first working trivial test case which checks the basic test fixture, set-up and race retrieval with status update Change-Id: I8de10018477bca6bd5b9a51003acc7bcfc5df285 --- .../impl/CurrentRaceFilterImplTest.java | 230 +++++++++++------- .../racegroup/impl/SimpleFilterableRace.java | 69 ++++++ .../racegroup/impl/CurrentRaceFilterImpl.java | 4 +- .../racegroup/impl/SeriesWithRowsImpl.java | 2 +- 4 files changed, 214 insertions(+), 91 deletions(-) create mode 100755 java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index c1a488d5090..233e967a512 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -1,114 +1,168 @@ package com.sap.sailing.domain.base.racegroup.impl; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.util.ArrayList; -import java.util.Collection; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.hamcrest.Matchers; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; -import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; -import com.sap.sailing.domain.base.racegroup.IsRaceFragment; -import com.sap.sailing.domain.base.racegroup.IsFleetFragment; -import com.sap.sailing.domain.base.racegroup.RaceGroupFragment; +import com.sap.sailing.domain.base.racegroup.RaceCell; +import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleetRaceColumn; +import com.sap.sailing.domain.base.racegroup.RaceRow; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; +import com.sap.sailing.domain.common.BoatClassMasterdata; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; public class CurrentRaceFilterImplTest { - private CurrentRaceFilter fixture; + private CurrentRaceFilter fixture; + private RaceGroup _505; + private RaceGroup yes; + private RaceGroup isaf; + private RaceGroup league; + private Map races; @Before public void setUp() { - fixture = new CurrentRaceFilterImpl(); + races = new HashMap<>(); + // five regattas ("RaceGroup"s): + // - simple single-series, single fleet set-up (505 style) + // - single-series, two fleets (Yellow / Blue, as used by YES) + // - qualification with Yellow / Blue; final with Gold / Silver; Medal + // - league set-up with single series with three fleets Red/Green/Blue + _505 = create505Regatta(); + races.putAll(getAllRaces(_505)); + yes = createYesRegatta(); + races.putAll(getAllRaces(yes)); + isaf = createIsafRegatta(); + races.putAll(getAllRaces(isaf)); + league = createLeagueRegatta(); + races.putAll(getAllRaces(league)); + fixture = new CurrentRaceFilterImpl<>(races.values()); + } + + private RaceGroup create505Regatta() { + final List _505Series = new ArrayList<>(); + final List _505RaceRows = new ArrayList<>(); + final RaceRow _505RaceRow = createRaceRow(/* firstRaceColumnNumber */ 1, /* numberOfRaces */ 9, /* raceColumnNamePrefix */ "R", /* fleetName */ "Default"); + _505RaceRows.add(_505RaceRow); + _505Series.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, _505RaceRows)); + final RaceGroup _505 = new RaceGroupImpl("505", /* displayName */ null, + new BoatClassImpl(BoatClassMasterdata._5O5.getDisplayName(), BoatClassMasterdata._5O5), + /* courseArea */ null, _505Series, /* regattaConfiguration */ null); + return _505; + } + + private RaceGroup createYesRegatta() { + final List yesSeries = new ArrayList<>(); + final List yesRaceRows = Arrays.asList(createRaceRow(1, 9, "R", "Yellow"), createRaceRow(1, 9, "R", "Blue")); + yesSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, yesRaceRows)); + final RaceGroup yes = new RaceGroupImpl("YES", /* displayName */ null, + new BoatClassImpl(BoatClassMasterdata._470.getDisplayName(), BoatClassMasterdata._470), + /* courseArea */ null, yesSeries, /* regattaConfiguration */ null); + return yes; + } + + private RaceGroup createIsafRegatta() { + final List isafSeries = new ArrayList<>(); + final List isafQualificationRaceRows = Arrays.asList(createRaceRow(1, 5, "Q", "Yellow"), createRaceRow(1, 5, "Q", "Blue")); + isafSeries.add(new SeriesWithRowsImpl("Qualification", /* isMedal */ false, isafQualificationRaceRows)); + final List isafFinalRaceRows = Arrays.asList(createRaceRow(6, 5, "F", "Gold"), createRaceRow(6, 5, "F", "Silver")); + isafSeries.add(new SeriesWithRowsImpl("Final", /* isMedal */ false, isafFinalRaceRows)); + final List isafMedalRaceRows = Arrays.asList(createRaceRow(1, 1, "M", "Medal")); + isafSeries.add(new SeriesWithRowsImpl("Medal", /* isMedal */ true, isafMedalRaceRows)); + final RaceGroup isaf = new RaceGroupImpl("ISAF", /* displayName */ null, + new BoatClassImpl(BoatClassMasterdata._470.getDisplayName(), BoatClassMasterdata._470), + /* courseArea */ null, isafSeries, /* regattaConfiguration */ null); + return isaf; + } + + private RaceGroup createLeagueRegatta() { + final List leagueSeries = new ArrayList<>(); + final List leagueRaceRows = Arrays.asList(createRaceRow(1, 15, "F", "Red"), createRaceRow(1, 15, "F", "Green"), createRaceRow(1, 15, "F", "Blue")); + leagueSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, leagueRaceRows)); + final RaceGroup league = new RaceGroupImpl("League", /* displayName */ null, + new BoatClassImpl(BoatClassMasterdata.J70.getDisplayName(), BoatClassMasterdata.J70), + /* courseArea */ null, leagueSeries, /* regattaConfiguration */ null); + return league; + } + + private Map getAllRaces(RaceGroup raceGroup) { + final Map result = new HashMap<>(); + int zeroBasedSeriesIndex = 0; + for (SeriesWithRows series : raceGroup.getSeries()) { + for (final RaceRow raceRow : series.getRaceRows()) { + int zeroBasedIndexInFleet = 0; + for (final RaceCell raceCell : raceRow.getCells()) { + final SimpleFilterableRace race = new SimpleFilterableRace(raceGroup, series, raceRow.getFleet(), zeroBasedIndexInFleet, zeroBasedSeriesIndex, raceCell.getName()); + result.put(new RaceGroupSeriesFleetRaceColumn(race), race); + zeroBasedIndexInFleet++; + } + } + zeroBasedSeriesIndex++; + } + return result; } @Test - public void easyRaceGroupTest() throws Exception { - SeriesBase seriesBase = Mockito.mock(SeriesBase.class); - List values = new ArrayList<>(); - SeriesFragment fristSeries = new SeriesFragment(seriesBase); - values.add(fristSeries); - RaceFragment firstRace = new RaceFragment(RaceLogRaceStatus.UNSCHEDULED); - values.add(firstRace); - - List result = fixture.getCurrentRaces(); - Assert.assertThat(result.size(), Matchers.equalTo(2)); - Assert.assertThat(result.get(0), Matchers.equalTo(fristSeries)); - Assert.assertThat(result.get(1), Matchers.equalTo(firstRace)); + public void testRaceAccess() { + SimpleFilterableRace isafQ4Yellow = get(isaf, "F7", "Gold"); + assertNotNull(isafQ4Yellow); + assertEquals("F7", isafQ4Yellow.getRaceColumnName()); + assertEquals("Gold", isafQ4Yellow.getFleet().getName()); + assertEquals(1, isafQ4Yellow.getZeroBasedIndexInFleet()); + assertEquals(1, isafQ4Yellow.getZeroBasedSeriesIndex()); + assertEquals(RaceLogRaceStatus.UNSCHEDULED, isafQ4Yellow.getStatus()); + isafQ4Yellow.setStatus(RaceLogRaceStatus.SCHEDULED); + assertEquals(RaceLogRaceStatus.SCHEDULED, isafQ4Yellow.getStatus()); + } + + private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) { + for (final SeriesWithRows series : raceGroup.getSeries()) { + final Fleet fleet = getFleet(series, fleetName); + if (fleet != null) { + final RaceRow raceRow = series.getRaceRow(fleet); + if (raceRow != null) { + for (final RaceCell raceCell : raceRow.getCells()) { + if (raceCell.getName().equals(raceColumnName)) { + return races.get(new RaceGroupSeriesFleetRaceColumn(raceGroup, series, fleet, raceColumnName)); + } + } + } + } + } + return null; } - @Test - public void raceGroupWithTwoSeries() throws Exception { - SeriesBase seriesBase1 = Mockito.mock(SeriesBase.class); - SeriesBase seriesBase2 = Mockito.mock(SeriesBase.class); - List values = new ArrayList<>(); - values.addAll(createRacesForSeries(3, seriesBase1)); - values.addAll(createRacesForSeries(3, seriesBase2)); - - List result = fixture.getCurrentRaces(); - Assert.assertThat(result.size(), Matchers.equalTo(2)); - Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); - Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); - - ((RaceFragment) values.get(5)).setCurrentStatus(RaceLogRaceStatus.PRESCHEDULED); - - result = fixture.getCurrentRaces(); - Assert.assertThat(result.size(), Matchers.equalTo(5)); - Assert.assertThat(result.get(0), Matchers.equalTo(values.get(0))); - Assert.assertThat(result.get(1), Matchers.equalTo(values.get(1))); - Assert.assertThat(result.get(2), Matchers.equalTo(values.get(4))); - Assert.assertThat(result.get(3), Matchers.equalTo(values.get(5))); - Assert.assertThat(result.get(4), Matchers.equalTo(values.get(6))); - - ((RaceFragment) values.get(5)).setCurrentStatus(RaceLogRaceStatus.RUNNING); - - result = fixture.getCurrentRaces(); - Assert.assertThat(result.size(), Matchers.equalTo(3)); - Assert.assertThat(result.get(0), Matchers.equalTo(values.get(4))); - Assert.assertThat(result.get(1), Matchers.equalTo(values.get(5))); - Assert.assertThat(result.get(2), Matchers.equalTo(values.get(6))); + private Fleet getFleet(SeriesWithRows series, String fleetName) { + for (final Fleet fleet : series.getFleets()) { + if (fleet.getName().equals(fleetName)) { + return fleet; + } + } + return null; } - private Collection createRacesForSeries(int max, SeriesBase seriesBase) { - List results = new ArrayList<>(); - results.add(new SeriesFragment(seriesBase)); - for (int i = 0; i < max; i++) { - results.add(new RaceFragment(RaceLogRaceStatus.UNSCHEDULED)); - } - return results; - } - - private class SeriesFragment implements IsFleetFragment { - private SeriesBase series; - - public SeriesFragment(SeriesBase series) { - this.series = series; - } - - @Override - public SeriesBase getSeries() { - return series; - } - } - - private class RaceFragment implements IsRaceFragment { - private RaceLogRaceStatus currentStatus; - - public RaceFragment(RaceLogRaceStatus currentStatus) { - this.currentStatus = currentStatus; - } - - @Override - public RaceLogRaceStatus getCurrentStatus() { - return currentStatus; - } - - public void setCurrentStatus(RaceLogRaceStatus newStatus) { - currentStatus = newStatus; + private RaceRow createRaceRow(final int firstRaceColumnNumber, final int numberOfRaces, + final String raceColumnNamePrefix, final String fleetName) { + final List _505RaceCells = new ArrayList<>(); + for (int i=0; i < numberOfRaces; i++) { + _505RaceCells.add(new RaceCellImpl(raceColumnNamePrefix+(i+firstRaceColumnNumber), + /* race log */ null, /* factor */ firstRaceColumnNumber, /* explicitFactor */ null, + /* zeroBasedIndexInFleet */ i)); } + final RaceRowImpl _505RaceRow = new RaceRowImpl(new FleetImpl(fleetName), _505RaceCells); + return _505RaceRow; } } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java new file mode 100755 index 00000000000..1fecbdc179a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java @@ -0,0 +1,69 @@ +package com.sap.sailing.domain.base.racegroup.impl; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.racegroup.FilterableRace; +import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; + +public class SimpleFilterableRace implements FilterableRace { + private final RaceGroup raceGroup; + private final SeriesBase series; + private final Fleet fleet; + private RaceLogRaceStatus status; + private final int zeroBasedIndexInFleet; + private final int zeroBasedSeriesIndex; + private final String raceColumnName; + + public SimpleFilterableRace(RaceGroup raceGroup, SeriesBase series, Fleet fleet, int zeroBasedIndexInFleet, + int zeroBasedSeriesIndex, String raceColumnName) { + super(); + this.raceGroup = raceGroup; + this.series = series; + this.fleet = fleet; + this.zeroBasedIndexInFleet = zeroBasedIndexInFleet; + this.zeroBasedSeriesIndex = zeroBasedSeriesIndex; + this.raceColumnName = raceColumnName; + this.status = RaceLogRaceStatus.UNSCHEDULED; + } + + @Override + public RaceGroup getRaceGroup() { + return raceGroup; + } + + @Override + public SeriesBase getSeries() { + return series; + } + + @Override + public Fleet getFleet() { + return fleet; + } + + @Override + public RaceLogRaceStatus getStatus() { + return status; + } + + public void setStatus(RaceLogRaceStatus status) { + this.status = status; + } + + @Override + public int getZeroBasedIndexInFleet() { + return zeroBasedIndexInFleet; + } + + @Override + public int getZeroBasedSeriesIndex() { + return zeroBasedSeriesIndex; + } + + @Override + public String getRaceColumnName() { + return raceColumnName; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 3741597a08f..c2d6cd14f89 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -37,10 +37,10 @@ import com.sap.sse.common.Util; * @param */ public class CurrentRaceFilterImpl implements CurrentRaceFilter { - private final Set allRaces; + private final Iterable allRaces; private final Map racesByRaceGroupSeriesFleet; - public CurrentRaceFilterImpl(Set allRaces) { + public CurrentRaceFilterImpl(Iterable allRaces) { this.allRaces = allRaces; this.racesByRaceGroupSeriesFleet = new HashMap<>(); for (final T race : allRaces) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java index 9c925c3935f..626a6264522 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java @@ -38,7 +38,7 @@ public class SeriesWithRowsImpl implements SeriesWithRows { @Override public RaceRow getRaceRow(Fleet fleet) { for (final RaceRow row : getRaceRows()) { - if (row.getFleet().equals(fleet)) { + if (row.getFleet() == fleet) { return row; } } From 99c9642e2e24fc4f6c6709bb0e5e49e53db9cab8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 00:20:12 +0200 Subject: [PATCH 31/57] renamed a test Change-Id: I12a8be8feda02e0a7066f29bb7b26083fee6fd08 --- .../base/racegroup/impl/CurrentRaceFilterImplTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index 233e967a512..7d7dda39ca7 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -116,7 +116,7 @@ public class CurrentRaceFilterImplTest { } @Test - public void testRaceAccess() { + public void testRaceAccessAndStatusUpdate() { SimpleFilterableRace isafQ4Yellow = get(isaf, "F7", "Gold"); assertNotNull(isafQ4Yellow); assertEquals("F7", isafQ4Yellow.getRaceColumnName()); @@ -128,6 +128,11 @@ public class CurrentRaceFilterImplTest { assertEquals(RaceLogRaceStatus.SCHEDULED, isafQ4Yellow.getStatus()); } + @Test + public void testBasicFiltering() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + } + private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) { for (final SeriesWithRows series : raceGroup.getSeries()) { final Fleet fleet = getFleet(series, fleetName); From 00e0f9a43870f75206ffabdd420605693f10d855 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 00:24:54 +0200 Subject: [PATCH 32/57] fixed serialization of new isFleetsCanRunInParallel property on SeriesWithRows; introduced constants for JSON properties Change-Id: I83a4742548621845c5cdf5338e5d51ce1e4d9d50 --- .../racegroup/impl/SeriesWithRowsDeserializer.java | 7 ++++--- .../racegroup/impl/SeriesWithRowsJsonSerializer.java | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java index 2663a47fc32..5913caa0213 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java @@ -12,6 +12,7 @@ import com.sap.sailing.server.gateway.deserialization.JsonDeserializationExcepti import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.Helpers; import com.sap.sailing.server.gateway.serialization.racegroup.impl.RaceRowsOfSeriesWithRowsSerializer; +import com.sap.sailing.server.gateway.serialization.racegroup.impl.SeriesWithRowsJsonSerializer; public class SeriesWithRowsDeserializer implements JsonDeserializer { @@ -22,9 +23,9 @@ public class SeriesWithRowsDeserializer implements JsonDeserializer rows = new ArrayList(); for (Object fleetObject : Helpers.getNestedArraySafe(object, RaceRowsOfSeriesWithRowsSerializer.FIELD_FLEETS)) { diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/SeriesWithRowsJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/SeriesWithRowsJsonSerializer.java index ec114b43381..5dafbc37fe2 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/SeriesWithRowsJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racegroup/impl/SeriesWithRowsJsonSerializer.java @@ -9,6 +9,7 @@ import com.sap.sailing.server.gateway.serialization.ExtensionJsonSerializer; public class SeriesWithRowsJsonSerializer extends ExtendableJsonSerializer { public static final String FIELD_NAME = "name"; public static final String FIELD_IS_MEDAL = "isMedal"; + public static final String FIELD_FLEETS_CAN_RUN_IN_PARALLEL = "isFleetsCanRunInParallel"; public SeriesWithRowsJsonSerializer(ExtensionJsonSerializer extensionSerializer) { super(extensionSerializer); @@ -17,10 +18,9 @@ public class SeriesWithRowsJsonSerializer extends ExtendableJsonSerializer Date: Fri, 22 Apr 2016 00:30:35 +0200 Subject: [PATCH 33/57] cleaned up after merging bug3532 branch Change-Id: I22e052688104113d1c13e3ca85fa7fadd5d5e4cc --- .../racegroup/impl/CurrentRaceFilterImplTest.java | 14 ++++++++------ .../base/racegroup/impl/CurrentRaceFilterImpl.java | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index 7d7dda39ca7..e46d7c93330 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -57,7 +58,7 @@ public class CurrentRaceFilterImplTest { final List _505RaceRows = new ArrayList<>(); final RaceRow _505RaceRow = createRaceRow(/* firstRaceColumnNumber */ 1, /* numberOfRaces */ 9, /* raceColumnNamePrefix */ "R", /* fleetName */ "Default"); _505RaceRows.add(_505RaceRow); - _505Series.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, _505RaceRows)); + _505Series.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ false, _505RaceRows)); final RaceGroup _505 = new RaceGroupImpl("505", /* displayName */ null, new BoatClassImpl(BoatClassMasterdata._5O5.getDisplayName(), BoatClassMasterdata._5O5), /* courseArea */ null, _505Series, /* regattaConfiguration */ null); @@ -67,7 +68,7 @@ public class CurrentRaceFilterImplTest { private RaceGroup createYesRegatta() { final List yesSeries = new ArrayList<>(); final List yesRaceRows = Arrays.asList(createRaceRow(1, 9, "R", "Yellow"), createRaceRow(1, 9, "R", "Blue")); - yesSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, yesRaceRows)); + yesSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, yesRaceRows)); final RaceGroup yes = new RaceGroupImpl("YES", /* displayName */ null, new BoatClassImpl(BoatClassMasterdata._470.getDisplayName(), BoatClassMasterdata._470), /* courseArea */ null, yesSeries, /* regattaConfiguration */ null); @@ -77,11 +78,11 @@ public class CurrentRaceFilterImplTest { private RaceGroup createIsafRegatta() { final List isafSeries = new ArrayList<>(); final List isafQualificationRaceRows = Arrays.asList(createRaceRow(1, 5, "Q", "Yellow"), createRaceRow(1, 5, "Q", "Blue")); - isafSeries.add(new SeriesWithRowsImpl("Qualification", /* isMedal */ false, isafQualificationRaceRows)); + isafSeries.add(new SeriesWithRowsImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, isafQualificationRaceRows)); final List isafFinalRaceRows = Arrays.asList(createRaceRow(6, 5, "F", "Gold"), createRaceRow(6, 5, "F", "Silver")); - isafSeries.add(new SeriesWithRowsImpl("Final", /* isMedal */ false, isafFinalRaceRows)); + isafSeries.add(new SeriesWithRowsImpl("Final", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, isafFinalRaceRows)); final List isafMedalRaceRows = Arrays.asList(createRaceRow(1, 1, "M", "Medal")); - isafSeries.add(new SeriesWithRowsImpl("Medal", /* isMedal */ true, isafMedalRaceRows)); + isafSeries.add(new SeriesWithRowsImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, isafMedalRaceRows)); final RaceGroup isaf = new RaceGroupImpl("ISAF", /* displayName */ null, new BoatClassImpl(BoatClassMasterdata._470.getDisplayName(), BoatClassMasterdata._470), /* courseArea */ null, isafSeries, /* regattaConfiguration */ null); @@ -91,7 +92,7 @@ public class CurrentRaceFilterImplTest { private RaceGroup createLeagueRegatta() { final List leagueSeries = new ArrayList<>(); final List leagueRaceRows = Arrays.asList(createRaceRow(1, 15, "F", "Red"), createRaceRow(1, 15, "F", "Green"), createRaceRow(1, 15, "F", "Blue")); - leagueSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, leagueRaceRows)); + leagueSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ false, leagueRaceRows)); final RaceGroup league = new RaceGroupImpl("League", /* displayName */ null, new BoatClassImpl(BoatClassMasterdata.J70.getDisplayName(), BoatClassMasterdata.J70), /* courseArea */ null, leagueSeries, /* regattaConfiguration */ null); @@ -131,6 +132,7 @@ public class CurrentRaceFilterImplTest { @Test public void testBasicFiltering() { // with all races unscheduled we can expect the first race of each regatta's first series to show + final Set currentRaces = fixture.getCurrentRaces(); } private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index c2d6cd14f89..50b1f1f18ed 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -79,7 +79,7 @@ public class CurrentRaceFilterImpl implements CurrentR */ private boolean hasNoPredecessor(FilterableRace race) { return race.getZeroBasedSeriesIndex() == 0 && race.getZeroBasedIndexInFleet() == 0 && - (race.getSeries().isFleetsRunInParallel() || Util.indexOf(race.getSeries().getFleets(), race.getFleet()) == 0); + (race.getSeries().isFleetsCanRunInParallel() || Util.indexOf(race.getSeries().getFleets(), race.getFleet()) == 0); } /** @@ -104,7 +104,7 @@ public class CurrentRaceFilterImpl implements CurrentR private boolean hasImmediatePredecessorThatIsAtLeastScheduled(FilterableRace race) { final Set immediatePredecessors = new HashSet<>(); final RaceGroup raceGroup = race.getRaceGroup(); - if (race.getSeries().isFleetsRunInParallel()) { + if (race.getSeries().isFleetsCanRunInParallel()) { if (race.getZeroBasedIndexInFleet() == 0) { // it's in the first column; for fleets that may run in parallel, the immediate predecessors are // in the last column of the immediately preceding series: @@ -154,7 +154,7 @@ public class CurrentRaceFilterImpl implements CurrentR private Iterable getLastRacesFromImmediatelyPrecedingSeries(FilterableRace race, final RaceGroup raceGroup) { final Set lastRacesInImmediatelyPrecedingSeries = new HashSet<>(); final SeriesWithRows immediatelyPrecedingSeries = Util.get(raceGroup.getSeries(), race.getZeroBasedSeriesIndex()-1); - if (immediatelyPrecedingSeries.isFleetsRunInParallel()) { + if (immediatelyPrecedingSeries.isFleetsCanRunInParallel()) { // add all last races of all fleets in immediately preceding series because they may be run in parallel for (final RaceRow rowInImmediatelyPrecedingSeries : immediatelyPrecedingSeries.getRaceRows()) { final T lastRaceInRow = getLastRaceInRow(raceGroup, immediatelyPrecedingSeries, rowInImmediatelyPrecedingSeries); From 27ddfd4d807c7e6c4f7083c44a9e731408d39cbc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 01:03:39 +0200 Subject: [PATCH 34/57] added the first meaningful tests for filtering in non-trivial regatta structures with different formats, all races UNSCHEDULED Change-Id: Idb59b34701a50b9ca67741aba30539c15974d179 --- .../impl/CurrentRaceFilterImplTest.java | 31 ++++++++++++++++++- .../racegroup/impl/SimpleFilterableRace.java | 6 ++++ .../base/racegroup/RaceGroupSeries.java | 6 ++-- .../base/racegroup/RaceGroupSeriesFleet.java | 10 +++--- .../RaceGroupSeriesFleetRaceColumn.java | 5 +++ .../racegroup/impl/CurrentRaceFilterImpl.java | 5 ++- .../base/racegroup/impl/RaceCellImpl.java | 6 ++++ .../base/racegroup/impl/RaceRowImpl.java | 5 +++ .../racegroup/impl/SeriesWithRowsImpl.java | 6 ++++ 9 files changed, 67 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index e46d7c93330..d2d34fd4fa3 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; @@ -130,9 +131,37 @@ public class CurrentRaceFilterImplTest { } @Test - public void testBasicFiltering() { + public void testBasicFilteringForFirstUnscheduledRaces() { // with all races unscheduled we can expect the first race of each regatta's first series to show final Set currentRaces = fixture.getCurrentRaces(); + + { + final Set current505Races = currentRaces.stream().filter(r->r.getRaceGroup() == _505).collect(Collectors.toSet()); + assertEquals(1, current505Races.size()); + final SimpleFilterableRace firstRace = current505Races.iterator().next(); + assertEquals("R1", firstRace.getRaceColumnName()); + } + { + final Set currentYesRaces = currentRaces.stream().filter(r->r.getRaceGroup() == yes).collect(Collectors.toSet()); + assertEquals(2, currentYesRaces.size()); + for (final SimpleFilterableRace currentYesRace : currentYesRaces) { + assertEquals("R1", currentYesRace.getRaceColumnName()); + } + } + { + final Set currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet()); + assertEquals(2, currentIsafRaces.size()); + for (final SimpleFilterableRace currentYesRace : currentIsafRaces) { + assertEquals("Q1", currentYesRace.getRaceColumnName()); + } + } + { + final Set currentLeagueRaces = currentRaces.stream().filter(r->r.getRaceGroup() == league).collect(Collectors.toSet()); + assertEquals(1, currentLeagueRaces.size()); + final SimpleFilterableRace firstRace = currentLeagueRaces.iterator().next(); + assertEquals("Red", firstRace.getFleet().getName()); + assertEquals("F1", firstRace.getRaceColumnName()); + } } private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java index 1fecbdc179a..92390ccf061 100755 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/SimpleFilterableRace.java @@ -66,4 +66,10 @@ public class SimpleFilterableRace implements FilterableRace { return raceColumnName; } + @Override + public String toString() { + return "SimpleFilterableRace ["+getRaceGroup().getName() + " - " + getSeries().getName() + " - " + + getFleet().getName() + " - " + getRaceColumnName() + "]"; + } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java index 41048c76332..196a43b087a 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java @@ -15,9 +15,9 @@ import com.sap.sse.common.Util; */ public class RaceGroupSeries { - private RaceGroup raceGroup; - private SeriesBase series; - private int seriesOrder; + private final RaceGroup raceGroup; + private final SeriesBase series; + private final int seriesOrder; public RaceGroupSeries(FilterableRace race) { this(race.getRaceGroup(), race.getSeries()); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java index 5bb3fcf0894..69848124e6d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleet.java @@ -16,19 +16,17 @@ import com.sap.sse.common.Util; */ public class RaceGroupSeriesFleet extends RaceGroupSeries { - private Fleet fleet; - private int fleetOrder; + private final Fleet fleet; + private final int fleetOrder; public RaceGroupSeriesFleet(FilterableRace race) { - super(race); - fleet = race.getFleet(); - fleetOrder = getFleetIndex(race.getSeries().getFleets(), race.getFleet()); + this(race.getRaceGroup(), race.getSeries(), race.getFleet()); } public RaceGroupSeriesFleet(RaceGroup raceGroup, SeriesBase series, Fleet fleet) { super(raceGroup, series); this.fleet = fleet; - getFleetIndex(series.getFleets(), fleet); + fleetOrder = getFleetIndex(series.getFleets(), fleet); } private int getFleetIndex(Iterable fleets, Fleet fleet) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java index 9b00b83183c..660da25f19c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesFleetRaceColumn.java @@ -53,4 +53,9 @@ public class RaceGroupSeriesFleetRaceColumn extends RaceGroupSeriesFleet { return false; return true; } + + @Override + public String toString() { + return getRaceGroupName() + " - " + getSeriesName() + " - " + getFleetName() + " - " + getRaceColumnName(); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 50b1f1f18ed..4ca0c761c3e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -12,7 +12,6 @@ import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter; import com.sap.sailing.domain.base.racegroup.FilterableRace; import com.sap.sailing.domain.base.racegroup.RaceCell; import com.sap.sailing.domain.base.racegroup.RaceGroup; -import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleetRaceColumn; import com.sap.sailing.domain.base.racegroup.RaceRow; import com.sap.sailing.domain.base.racegroup.SeriesWithRows; @@ -38,13 +37,13 @@ import com.sap.sse.common.Util; */ public class CurrentRaceFilterImpl implements CurrentRaceFilter { private final Iterable allRaces; - private final Map racesByRaceGroupSeriesFleet; + private final Map racesByRaceGroupSeriesFleet; public CurrentRaceFilterImpl(Iterable allRaces) { this.allRaces = allRaces; this.racesByRaceGroupSeriesFleet = new HashMap<>(); for (final T race : allRaces) { - racesByRaceGroupSeriesFleet.put(new RaceGroupSeriesFleet(race), race); + racesByRaceGroupSeriesFleet.put(new RaceGroupSeriesFleetRaceColumn(race), race); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java index b120a9c734f..cb69b6d7b83 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceCellImpl.java @@ -69,4 +69,10 @@ public class RaceCellImpl extends NamedImpl implements RaceCell { public int getZeroBasedIndexInFleet() { return zeroBasedIndexInFleet; } + + @Override + public String toString() { + return "RaceCellImpl [factor=" + factor + ", explicitFactor=" + explicitFactor + ", zeroBasedIndexInFleet=" + + zeroBasedIndexInFleet + ", getName()=" + getName() + "]"; + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java index 9be6959994a..6a9ca58b07b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceRowImpl.java @@ -28,4 +28,9 @@ public class RaceRowImpl implements RaceRow { return races; } + @Override + public String toString() { + return "RaceRowImpl [fleet=" + fleet + ", races=" + races + "]"; + } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java index c8e5af0761a..fad62d6b128 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/SeriesWithRowsImpl.java @@ -65,4 +65,10 @@ public class SeriesWithRowsImpl implements SeriesWithRows { public boolean isFleetsCanRunInParallel() { return isFleetsCanRunInParallel; } + + @Override + public String toString() { + return "SeriesWithRowsImpl [name=" + name + ", raceRows=" + raceRows + ", isMedal=" + isMedal + + ", isFleetsCanRunInParallel=" + isFleetsCanRunInParallel + "]"; + } } From 245bfc9b0b796481f0322ab0306ad4fc153d4d31 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 01:34:43 +0200 Subject: [PATCH 35/57] the next non-trivial test case for league race filtering after starting the first race Change-Id: I2a4d0dc85700a8c6781af5a9be62eff6dbdb1721 --- .../impl/CurrentRaceFilterImplTest.java | 23 +++++++++++++++++++ .../racegroup/impl/CurrentRaceFilterImpl.java | 14 +++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index d2d34fd4fa3..e62bb84dfeb 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.racegroup.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; @@ -164,6 +165,28 @@ public class CurrentRaceFilterImplTest { } } + @Test + public void testSchedulingFirstIsafRace() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + get(isaf, "Q1", "Yellow").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet()); + assertEquals(3, currentIsafRaces.size()); + assertTrue(currentIsafRaces.contains(get(isaf, "Q1", "Yellow"))); // it's scheduled, so it shows + assertTrue(currentIsafRaces.contains(get(isaf, "Q1", "Blue"))); // it's unscheduled, first in fleet + assertTrue(currentIsafRaces.contains(get(isaf, "Q2", "Yellow"))); // predecessor Q1/Yellow is scheduled, so show this as next in fleet + } + + @Test + public void testFinishingFirstLeagueRace() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + get(league, "F1", "Red").setStatus(RaceLogRaceStatus.FINISHED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentLeagueRaces = currentRaces.stream().filter(r->r.getRaceGroup() == league).collect(Collectors.toSet()); + assertEquals(1, currentLeagueRaces.size()); + assertTrue(currentLeagueRaces.contains(get(league, "F1", "Green"))); // Red is finished, don't show it anymore + } + private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) { for (final SeriesWithRows series : raceGroup.getSeries()) { final Fleet fleet = getFleet(series, fleetName); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 4ca0c761c3e..973cd696b96 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -59,12 +59,16 @@ public class CurrentRaceFilterImpl implements CurrentR public Set getCurrentRaces() { final Set filteredRaces = new HashSet<>(); for (final T race : allRaces) { - if ((race.getStatus().getOrderNumber() >= RaceLogRaceStatus.SCHEDULED.getOrderNumber() && - race.getStatus().getOrderNumber() <= RaceLogRaceStatus.FINISHING.getOrderNumber()) || - hasNoPredecessor(race) || - hasImmediatePredecessorThatIsAtLeastScheduled(race)) { - // show the race + if (race.getStatus().getOrderNumber() >= RaceLogRaceStatus.SCHEDULED.getOrderNumber() && + race.getStatus().getOrderNumber() <= RaceLogRaceStatus.FINISHING.getOrderNumber()) { filteredRaces.add(race); + } else { + final boolean hasNoPredecessor=hasNoPredecessor(race); + if ((hasNoPredecessor && race.getStatus() == RaceLogRaceStatus.UNSCHEDULED) || + (!hasNoPredecessor && hasImmediatePredecessorThatIsAtLeastScheduled(race))) { + // show the race + filteredRaces.add(race); + } } } return filteredRaces; From eceedec6ce3e73d98d95be9fa5bfc4d6ddf87403 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 01:37:11 +0200 Subject: [PATCH 36/57] another league start test Change-Id: I34cd8075b9fa14afda006ed7893a24c442a303be --- .../racegroup/impl/CurrentRaceFilterImplTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index e62bb84dfeb..add4694e525 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -187,6 +187,19 @@ public class CurrentRaceFilterImplTest { assertTrue(currentLeagueRaces.contains(get(league, "F1", "Green"))); // Red is finished, don't show it anymore } + @Test + public void testSkippingSecondLeagueRace() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + get(league, "F1", "Red").setStatus(RaceLogRaceStatus.FINISHED); + get(league, "F1", "Blue").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentLeagueRaces = currentRaces.stream().filter(r->r.getRaceGroup() == league).collect(Collectors.toSet()); + assertEquals(3, currentLeagueRaces.size()); + assertTrue(currentLeagueRaces.contains(get(league, "F1", "Green"))); // Red is finished, don't show it anymore; show the next one instead which is Green + assertTrue(currentLeagueRaces.contains(get(league, "F1", "Blue"))); // show a scheduled race + assertTrue(currentLeagueRaces.contains(get(league, "F2", "Red"))); // This is the next one after the scheduled F1/Blue + } + private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) { for (final SeriesWithRows series : raceGroup.getSeries()) { final Fleet fleet = getFleet(series, fleetName); From 360e2e34e8941bf171bc3181fc59d44351bb7ae5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 01:47:20 +0200 Subject: [PATCH 37/57] added another non-trivial test for ISAF format which failed and pointed at the need for hiding FINISHED races Change-Id: Iccd8ac79d4393858e795782aa7df6a17001bfed9 --- .../impl/CurrentRaceFilterImplTest.java | 30 ++++++++++++++++++- .../racegroup/impl/CurrentRaceFilterImpl.java | 17 ++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index add4694e525..a184ce98152 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -164,7 +164,18 @@ public class CurrentRaceFilterImplTest { assertEquals("F1", firstRace.getRaceColumnName()); } } - + + @Test + public void testSchedulingFirst505Race() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + get(_505, "R1", "Default").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set current505Races = currentRaces.stream().filter(r->r.getRaceGroup() == _505).collect(Collectors.toSet()); + assertEquals(2, current505Races.size()); + assertTrue(current505Races.contains(get(_505, "R1", "Default"))); // it's scheduled, so it shows + assertTrue(current505Races.contains(get(_505, "R2", "Default"))); // it's unscheduled and has a scheduled immediate predecessor + } + @Test public void testSchedulingFirstIsafRace() { // with all races unscheduled we can expect the first race of each regatta's first series to show @@ -177,6 +188,23 @@ public class CurrentRaceFilterImplTest { assertTrue(currentIsafRaces.contains(get(isaf, "Q2", "Yellow"))); // predecessor Q1/Yellow is scheduled, so show this as next in fleet } + @Test + public void testSchedulingAllIsafQualificationRacesButOne() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + for (int i=1; i<=4; i++) { + get(isaf, "Q"+i, "Yellow").setStatus(RaceLogRaceStatus.FINISHED); + get(isaf, "Q"+i, "Blue").setStatus(RaceLogRaceStatus.FINISHED); + } + get(isaf, "Q5", "Blue").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet()); + assertEquals(4, currentIsafRaces.size()); + assertTrue(currentIsafRaces.contains(get(isaf, "Q5", "Yellow"))); // it's unscheduled; its predecessor Q4/Yellow is FINISHED; show it + assertTrue(currentIsafRaces.contains(get(isaf, "Q5", "Blue"))); // it's scheduled + assertTrue(currentIsafRaces.contains(get(isaf, "F6", "Gold"))); // Q5/Blue is a scheduled immediate predecessor + assertTrue(currentIsafRaces.contains(get(isaf, "F6", "Silver"))); // Q5/Blue is a scheduled immediate predecessor + } + @Test public void testFinishingFirstLeagueRace() { // with all races unscheduled we can expect the first race of each regatta's first series to show diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 973cd696b96..748f19aeccc 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -59,15 +59,16 @@ public class CurrentRaceFilterImpl implements CurrentR public Set getCurrentRaces() { final Set filteredRaces = new HashSet<>(); for (final T race : allRaces) { - if (race.getStatus().getOrderNumber() >= RaceLogRaceStatus.SCHEDULED.getOrderNumber() && - race.getStatus().getOrderNumber() <= RaceLogRaceStatus.FINISHING.getOrderNumber()) { - filteredRaces.add(race); - } else { - final boolean hasNoPredecessor=hasNoPredecessor(race); - if ((hasNoPredecessor && race.getStatus() == RaceLogRaceStatus.UNSCHEDULED) || - (!hasNoPredecessor && hasImmediatePredecessorThatIsAtLeastScheduled(race))) { - // show the race + if (race.getStatus().getOrderNumber() <= RaceLogRaceStatus.FINISHING.getOrderNumber()) { // don't show finished races + if (race.getStatus().getOrderNumber() >= RaceLogRaceStatus.SCHEDULED.getOrderNumber()) { filteredRaces.add(race); + } else { + final boolean hasNoPredecessor=hasNoPredecessor(race); + if ((hasNoPredecessor && race.getStatus() == RaceLogRaceStatus.UNSCHEDULED) || + (!hasNoPredecessor && hasImmediatePredecessorThatIsAtLeastScheduled(race))) { + // show the race + filteredRaces.add(race); + } } } } From 5be09b667a7ea8893a78ba525aefc0a31b351abf Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 02:27:04 +0200 Subject: [PATCH 38/57] added null check for filteredRaces Change-Id: I0b2c32977cb539f772afc7ddbe26e24b52f83deb --- .../app/ui/adapters/racelist/ManagedRaceListAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index 170043f7bb8..9008acc23bb 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -284,7 +284,9 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple public void onResult(List filteredRaces) { synchronized (mLockObject) { mShownViewItems.clear(); - mShownViewItems.addAll(getShownViewItems(filteredRaces)); + if (filteredRaces != null) { + mShownViewItems.addAll(getShownViewItems(filteredRaces)); + } notifyDataSetChanged(); } } From 47e0f90721b08d4a4617b08c4c49ec2eccc60f7b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 08:41:32 +0200 Subject: [PATCH 39/57] create view items only when the underlying set of races has changed; this improves the "tap" problem slightly, but it's still not good Change-Id: I698eeb119a4dd3fd6067573c04ae7fb4be2093d0 --- .../app/ui/adapters/racelist/ManagedRaceListAdapter.java | 6 +++++- .../racecommittee/app/ui/fragments/RaceListFragment.java | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index 9008acc23bb..a305eaa6f5b 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -106,7 +106,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); - createViewItemsForRacesAndSeries(); getFilter().refreshRegattaStructures(); } @@ -182,6 +181,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple @Override public void onClick(View v) { Intent intent = new Intent(AppConstants.INTENT_ACTION_SHOW_PROTEST); + // TODO don't use toString() to convey semantics and perform check; bug 3617 intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, header.toString()); BroadcastManager.getInstance(getContext()).addIntent(intent); } @@ -529,4 +529,8 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple this.index = index; } } + + public void onRacesChanged() { + createViewItemsForRacesAndSeries(); + } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 873205337e9..3b359417906 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -411,6 +411,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis registerOnAllRaces(); // prepare views and do initial filtering filterChanged(); + mAdapter.onRacesChanged(); mAdapter.notifyDataSetChanged(); } From b3cfa3b87ecc4133fa0224e2a62f0b2afa97d105 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 09:35:48 +0200 Subject: [PATCH 40/57] refresh regatta structures on filter only when the set of all races has changed; only then does the filter need to be re-constructed; the filter now also receives a *copy* of allRaces; this way, changes that the RaceListFragment performs on its mAllRaces collection cannot lead to inconsistent implicit "live" updates in the CurrentRaceFilterImpl instance. When I think about it, I'd like to ensure this in CurrentRaceFilterImpl itself... Change-Id: I89617bc661d3940e5374536541fa07c16c42bbc9 --- .../app/ui/adapters/racelist/ManagedRaceListAdapter.java | 7 +------ .../racecommittee/app/ui/adapters/racelist/RaceFilter.java | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index a305eaa6f5b..8f2c8130e82 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -103,12 +103,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple factor_format = new DecimalFormat(context.getString(R.string.race_factor_format)); } - @Override - public void notifyDataSetChanged() { - super.notifyDataSetChanged(); - getFilter().refreshRegattaStructures(); - } - private void createViewItemsForRacesAndSeries() { viewItemsRaces.clear(); viewItemsSeriesHeaders.clear(); @@ -532,5 +526,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple public void onRacesChanged() { createViewItemsForRacesAndSeries(); + getFilter().refreshRegattaStructures(); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index e32288b65fe..5b9e6a64b95 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -1,6 +1,7 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -75,7 +76,7 @@ public class RaceFilter extends Filter { } public void refreshRegattaStructures() { - this.currentRaceFilter = new CurrentRaceFilterImpl(allRaces); + this.currentRaceFilter = new CurrentRaceFilterImpl(new HashSet<>(allRaces)); filter(""); } From 222eb3beffdf8021b2285a641008736e8dcf6dc9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 09:41:13 +0200 Subject: [PATCH 41/57] made copying the allRaces structure fool-proof in CurrentRaceFilterImpl Change-Id: I2eff873433cf1a3303ceb5bd3fb410fef244e000 --- .../base/racegroup/impl/CurrentRaceFilterImpl.java | 12 +++++++++++- .../app/ui/adapters/racelist/RaceFilter.java | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 748f19aeccc..4c9335ef7e6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.base.racegroup.impl; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -39,8 +40,17 @@ public class CurrentRaceFilterImpl implements CurrentR private final Iterable allRaces; private final Map racesByRaceGroupSeriesFleet; + /** + * Constructs a filter that can determine "current races" from a set of races representing all races of + * a set of regattas / race groups. A copy of {@code allRaces} is constructed here, so later outside modifications + * to {@code allRaces} will not have any effect on this filter. Clients need to construct a new instance + * if the set of all races has changed. + * @param allRaces + */ public CurrentRaceFilterImpl(Iterable allRaces) { - this.allRaces = allRaces; + final Collection copyOfAllRaces = new HashSet<>(); + Util.addAll(allRaces, copyOfAllRaces); + this.allRaces = copyOfAllRaces; this.racesByRaceGroupSeriesFleet = new HashMap<>(); for (final T race : allRaces) { racesByRaceGroupSeriesFleet.put(new RaceGroupSeriesFleetRaceColumn(race), race); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 5b9e6a64b95..61f0fbc1bbd 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -76,7 +76,7 @@ public class RaceFilter extends Filter { } public void refreshRegattaStructures() { - this.currentRaceFilter = new CurrentRaceFilterImpl(new HashSet<>(allRaces)); + this.currentRaceFilter = new CurrentRaceFilterImpl(allRaces); filter(""); } From 085e21a66167c24180a35e856b6170e78ba5686c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 09:47:41 +0200 Subject: [PATCH 42/57] formatting only Change-Id: Ibdba8b2793a585c34be878c235e6c83bee2d7850 --- .../domain/persistence/impl/DomainObjectFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index bf7f91277a4..04b13c5a846 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -1191,7 +1191,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { // isFleetCanRunInParallel is a new field -> set to true when it does not exist in older db versions boolean isFleetsCanRunInParallel = true; Object isFleetCanRunInParallelObject = dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name()); - if(isFleetCanRunInParallelObject != null) { + if (isFleetCanRunInParallelObject != null) { isFleetsCanRunInParallel = (Boolean) dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name()); } Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name()); From 9792d76ccd901ba2221484adce182d4fdb07ae54 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 09:48:03 +0200 Subject: [PATCH 43/57] added more race filter tests Change-Id: Ib7ab4693c633dddf2026772b1914f5f2410f2a5f --- .../impl/CurrentRaceFilterImplTest.java | 16 +++++++++++++++- .../racegroup/impl/CurrentRaceFilterImpl.java | 9 ++++----- .../app/ui/adapters/racelist/RaceFilter.java | 1 - 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index a184ce98152..fe99d4a45e6 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -190,7 +190,6 @@ public class CurrentRaceFilterImplTest { @Test public void testSchedulingAllIsafQualificationRacesButOne() { - // with all races unscheduled we can expect the first race of each regatta's first series to show for (int i=1; i<=4; i++) { get(isaf, "Q"+i, "Yellow").setStatus(RaceLogRaceStatus.FINISHED); get(isaf, "Q"+i, "Blue").setStatus(RaceLogRaceStatus.FINISHED); @@ -205,6 +204,21 @@ public class CurrentRaceFilterImplTest { assertTrue(currentIsafRaces.contains(get(isaf, "F6", "Silver"))); // Q5/Blue is a scheduled immediate predecessor } + @Test + public void testFinishingAllIsafQualificationAndFinalRaces() { + // the medal race shall be the only one remaining + for (int i=1; i<=5; i++) { + get(isaf, "Q"+i, "Yellow").setStatus(RaceLogRaceStatus.FINISHED); + get(isaf, "Q"+i, "Blue").setStatus(RaceLogRaceStatus.FINISHED); + get(isaf, "F"+(i+5), "Gold").setStatus(RaceLogRaceStatus.FINISHED); + get(isaf, "F"+(i+5), "Silver").setStatus(RaceLogRaceStatus.FINISHED); + } + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet()); + assertEquals(1, currentIsafRaces.size()); + assertTrue(currentIsafRaces.contains(get(isaf, "M1", "Medal"))); + } + @Test public void testFinishingFirstLeagueRace() { // with all races unscheduled we can expect the first race of each regatta's first series to show diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java index 4c9335ef7e6..582fa77816f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImpl.java @@ -41,11 +41,10 @@ public class CurrentRaceFilterImpl implements CurrentR private final Map racesByRaceGroupSeriesFleet; /** - * Constructs a filter that can determine "current races" from a set of races representing all races of - * a set of regattas / race groups. A copy of {@code allRaces} is constructed here, so later outside modifications - * to {@code allRaces} will not have any effect on this filter. Clients need to construct a new instance - * if the set of all races has changed. - * @param allRaces + * Constructs a filter that can determine "current races" from a set of races representing all races of a set of + * regattas / race groups. A copy of {@code allRaces} is constructed here, so later outside modifications to + * {@code allRaces} will not have any effect on this filter. Clients need to construct a new instance if the set of + * all races has changed. */ public CurrentRaceFilterImpl(Iterable allRaces) { final Collection copyOfAllRaces = new HashSet<>(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java index 61f0fbc1bbd..e32288b65fe 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/RaceFilter.java @@ -1,7 +1,6 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; From 65ae7d3a816740618477dee6bb902f14a02ca590 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 10:07:01 +0200 Subject: [PATCH 44/57] added ESS-specific tests for knockout format Change-Id: If35277865d4c0b49e6f9bfa3b94e49b03e1b71ab --- .../impl/CurrentRaceFilterImplTest.java | 123 +++++++++++++++++- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java index fe99d4a45e6..64055e71e16 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/base/racegroup/impl/CurrentRaceFilterImplTest.java @@ -34,6 +34,7 @@ public class CurrentRaceFilterImplTest { private RaceGroup yes; private RaceGroup isaf; private RaceGroup league; + private RaceGroup ess; private Map races; @Before @@ -52,6 +53,8 @@ public class CurrentRaceFilterImplTest { races.putAll(getAllRaces(isaf)); league = createLeagueRegatta(); races.putAll(getAllRaces(league)); + ess = createEssRegatta(); + races.putAll(getAllRaces(ess)); fixture = new CurrentRaceFilterImpl<>(races.values()); } @@ -91,6 +94,26 @@ public class CurrentRaceFilterImplTest { return isaf; } + private RaceGroup createEssRegatta() { + final List essSeries = new ArrayList<>(); + final List essDay1RaceRows = Arrays.asList(createRaceRow(1, 5, "R", "Default")); + essSeries.add(new SeriesWithRowsImpl("Day 1", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay1RaceRows)); + final List essDay2RaceRows = Arrays.asList(createRaceRow(6, 5, "R", "Default")); + essSeries.add(new SeriesWithRowsImpl("Day 2", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay2RaceRows)); + final List essKnockoutQualificationRaceRows = Arrays.asList(createRaceRow(1, 1, "Q", "Yellow"), createRaceRow(1, 1, "Q", "Blue")); + essSeries.add(new SeriesWithRowsImpl("Knockout Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essKnockoutQualificationRaceRows)); + final List essKnockoutScoringRaceRows = Arrays.asList(createRaceRow(1, 1, "F", "Gold"), createRaceRow(1, 1, "F", "Silver")); + essSeries.add(new SeriesWithRowsImpl("Knockout Finals", /* isMedal */ false, /* isFleetsCanRunInParallel */ false, essKnockoutScoringRaceRows)); + final List essDay3RaceRows = Arrays.asList(createRaceRow(11, 5, "R", "Default")); + essSeries.add(new SeriesWithRowsImpl("Day 3", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay3RaceRows)); + final List essDay4RaceRows = Arrays.asList(createRaceRow(16, 5, "R", "Default")); + essSeries.add(new SeriesWithRowsImpl("Day 4", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay4RaceRows)); + final RaceGroup ess = new RaceGroupImpl("ESS", /* displayName */ null, + new BoatClassImpl(BoatClassMasterdata.EXTREME_40.getDisplayName(), BoatClassMasterdata.EXTREME_40), + /* courseArea */ null, essSeries, /* regattaConfiguration */ null); + return ess; + } + private RaceGroup createLeagueRegatta() { final List leagueSeries = new ArrayList<>(); final List leagueRaceRows = Arrays.asList(createRaceRow(1, 15, "F", "Red"), createRaceRow(1, 15, "F", "Green"), createRaceRow(1, 15, "F", "Blue")); @@ -163,6 +186,13 @@ public class CurrentRaceFilterImplTest { assertEquals("Red", firstRace.getFleet().getName()); assertEquals("F1", firstRace.getRaceColumnName()); } + { + final Set currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(1, currentEssRaces.size()); + final SimpleFilterableRace firstRace = currentEssRaces.iterator().next(); + assertEquals("Default", firstRace.getFleet().getName()); + assertEquals("R1", firstRace.getRaceColumnName()); + } } @Test @@ -181,11 +211,94 @@ public class CurrentRaceFilterImplTest { // with all races unscheduled we can expect the first race of each regatta's first series to show get(isaf, "Q1", "Yellow").setStatus(RaceLogRaceStatus.SCHEDULED); final Set currentRaces = fixture.getCurrentRaces(); - final Set currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet()); - assertEquals(3, currentIsafRaces.size()); - assertTrue(currentIsafRaces.contains(get(isaf, "Q1", "Yellow"))); // it's scheduled, so it shows - assertTrue(currentIsafRaces.contains(get(isaf, "Q1", "Blue"))); // it's unscheduled, first in fleet - assertTrue(currentIsafRaces.contains(get(isaf, "Q2", "Yellow"))); // predecessor Q1/Yellow is scheduled, so show this as next in fleet + final Set currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet()); + assertEquals(3, currentEssRaces.size()); + assertTrue(currentEssRaces.contains(get(isaf, "Q1", "Yellow"))); // it's scheduled, so it shows + assertTrue(currentEssRaces.contains(get(isaf, "Q1", "Blue"))); // it's unscheduled, first in fleet + assertTrue(currentEssRaces.contains(get(isaf, "Q2", "Yellow"))); // predecessor Q1/Yellow is scheduled, so show this as next in fleet + } + + @Test + public void testSchedulingFirstEssRace() { + // with all races unscheduled we can expect the first race of each regatta's first series to show + get(ess, "R1", "Default").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(2, currentEssRaces.size()); + assertTrue(currentEssRaces.contains(get(ess, "R1", "Default"))); // it's scheduled, so it shows + assertTrue(currentEssRaces.contains(get(ess, "R2", "Default"))); // it's unscheduled, first after last scheduled + } + + @Test + public void testFinishingAllEssRacesBeforeKnockoutQualification() { + for (int i=1; i<=10; i++) { + get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED); + } + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsaEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(2, currentIsaEssRaces.size()); + assertTrue(currentIsaEssRaces.contains(get(ess, "Q1", "Yellow"))); + assertTrue(currentIsaEssRaces.contains(get(ess, "Q1", "Blue"))); + } + + @Test + public void testFinishingAllEssRacesBeforeKnockoutQualificationAndSchedulingFirstKnockout() { + for (int i=1; i<=10; i++) { + get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED); + } + get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsaEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(3, currentIsaEssRaces.size()); + assertTrue(currentIsaEssRaces.contains(get(ess, "Q1", "Yellow"))); // scheduled + assertTrue(currentIsaEssRaces.contains(get(ess, "Q1", "Blue"))); // unscheduled + assertTrue(currentIsaEssRaces.contains(get(ess, "F1", "Gold"))); // first unscheduled in series; last column in preceding series has scheduled race + // F1/Silver remains invisible because the knockout finals runs its fleets in sequence + } + + @Test + public void testFinishingAllEssRacesBeforeKnockoutFinals() { + for (int i=1; i<=10; i++) { + get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED); + } + get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.FINISHED); + get(ess, "Q1", "Blue").setStatus(RaceLogRaceStatus.FINISHED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsaEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(1, currentIsaEssRaces.size()); + assertTrue(currentIsaEssRaces.contains(get(ess, "F1", "Gold"))); // first unscheduled in series; last column in preceding series has scheduled race + // F1/Silver remains invisible because the knockout finals runs its fleets in sequence + } + + @Test + public void testFinishingAllEssRacesBeforeKnockoutFinalsAndGoldRaceStarted() { + for (int i=1; i<=10; i++) { + get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED); + } + get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.FINISHED); + get(ess, "Q1", "Blue").setStatus(RaceLogRaceStatus.FINISHED); + get(ess, "F1", "Gold").setStatus(RaceLogRaceStatus.RUNNING); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsaEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(2, currentIsaEssRaces.size()); + assertTrue(currentIsaEssRaces.contains(get(ess, "F1", "Gold"))); // running + assertTrue(currentIsaEssRaces.contains(get(ess, "F1", "Silver"))); // first unscheduled with a predecessor that's running + } + + @Test + public void testFinishingAllEssRacesBeforeKnockoutFinalsAndGoldRaceFinishedAndSilverRaceScheduled() { + for (int i=1; i<=10; i++) { + get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED); + } + get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.FINISHED); + get(ess, "Q1", "Blue").setStatus(RaceLogRaceStatus.FINISHED); + get(ess, "F1", "Gold").setStatus(RaceLogRaceStatus.FINISHED); + get(ess, "F1", "Silver").setStatus(RaceLogRaceStatus.SCHEDULED); + final Set currentRaces = fixture.getCurrentRaces(); + final Set currentIsaEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet()); + assertEquals(2, currentIsaEssRaces.size()); + assertTrue(currentIsaEssRaces.contains(get(ess, "F1", "Silver"))); // scheduled + assertTrue(currentIsaEssRaces.contains(get(ess, "R11", "Default"))); // first unscheduled in series with the last race in the preceding series scheduled } @Test From 1f1010ae954ceefa4b6004d9373157c883ea61b3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 10:24:56 +0200 Subject: [PATCH 45/57] don't mark translated message strings as translatable=false Change-Id: Ic5d54754f8dbb1c087cbf85433c556fe1d67cffc --- .../res/values/strings_uss_sailing.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values/strings_uss_sailing.xml b/mobile/com.sap.sailing.android.tracking.app/res/values/strings_uss_sailing.xml index 819858d6e45..e011b303434 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/values/strings_uss_sailing.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/values/strings_uss_sailing.xml @@ -1,7 +1,7 @@ - The SAP Sail InSight app supports live tracking of race boats by leveraging mobile phone technology, allowing the ability for friends and fans to follow racing whether onsite or online through live leaderboards and 2D visualization. Following racing sailors can replay their races allowing the opportunity to review performance and optimize strategies all available at: https://www.sapsailing.com\n\nSAP supports US Sailing through the implementation of a range of innovative technology solutions, delivering analytics to help teams and sailors improve performance whilst also providing applications to help sailing operations run more efficiently.\n\nAs the national governing body for sailing, US Sailing provides leadership, integrity and growth for the sport including training and education programs for instructors and race officials alongside administration and oversight of competitive sailing across the country. - https://www.sapsailing.com + The SAP Sail InSight app supports live tracking of race boats by leveraging mobile phone technology, allowing the ability for friends and fans to follow racing whether onsite or online through live leaderboards and 2D visualization. Following racing sailors can replay their races allowing the opportunity to review performance and optimize strategies all available at: https://www.sapsailing.com\n\nSAP supports US Sailing through the implementation of a range of innovative technology solutions, delivering analytics to help teams and sailors improve performance whilst also providing applications to help sailing operations run more efficiently.\n\nAs the national governing body for sailing, US Sailing provides leadership, integrity and growth for the sport including training and education programs for instructors and race officials alongside administration and oversight of competitive sailing across the country. + https://www.sapsailing.com \ No newline at end of file From 61e6f1a1133be5a06e3e18907219b5ca496eec2f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 11:49:24 +0200 Subject: [PATCH 46/57] replaced external IP address of RabbitMQ by rabbit.sapsailing.com Change-Id: I877990fe29277884cd3251f187c8a284a4d25ea7 --- wiki/kieler-woche-2015.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wiki/kieler-woche-2015.md b/wiki/kieler-woche-2015.md index dbd26340daa..466d27c74ec 100644 --- a/wiki/kieler-woche-2015.md +++ b/wiki/kieler-woche-2015.md @@ -46,7 +46,7 @@ Whichever master is current, we map the main Apache URL http://kielerwoche2015-m - http://club.tractrac.com/events/event_20150625_KielerWoch/jsonservice.php for the Silbernes Band ## Replication -- RabbitMQ server for replication: 52.16.112.6 (Web: http://52.16.112.6:15672/) or internal host name rabbit.internal.sapsailing.com) +- RabbitMQ server for replication: rabbit.sapsailing.com (Web: http://rabbit.sapsailing.com:15672/) or internal host name rabbit.internal.sapsailing.com) - Replication Exchange Name: KW2015 ## Switching from one Master to Another @@ -64,7 +64,7 @@ Whichever master is current, we map the main Apache URL http://kielerwoche2015-m - upgrade the replicas (including the Cube Replica) to the new master if the new master runs a different release than the old master, using `refreshInstance.sh` - adjust the replicas' (including the Cube Replica) `env.sh` to point to the new master with their `REPLICATE_MASTER_SERVLET_HOST` setting - restart the Java server instance on the replicas (including the Cube Replica), using the usual `./stop; ./start` sequence. This will automatically re-start the replication - - when the replicas have finished their initial load (can be verified by looking at the RabbitMQ queues at http://ec2-52-16-112-6.eu-west-1.compute.amazonaws.com:15672/#/queues which should no longer have any "initialLoad" queues), add them again to the load balancer + - when the replicas have finished their initial load (can be verified by looking at the RabbitMQ queues at http://rabbit.sapsailing.com:15672/#/queues which should no longer have any "initialLoad" queues), add them again to the load balancer - in the Cube server's Apache configuration let http://kielerwoche2015.sapsailing.com point again to the local replica by editing `/etc/apache2/sites-enabled/001-events.conf`, then as user `root` run `service httpd reload` - shut down the other master server to avoid confusion with database writes @@ -75,4 +75,4 @@ Whichever master is current, we map the main Apache URL http://kielerwoche2015-m ## Local network setup basically see [[Set up local network with replication server|wiki/event-network-with-replica]] operating with a Meraki and a local replica server using 1 physical WAN interface on the Meraki. Meraki network config for this event can be found under [[Meraki Dashboard|https://n142.meraki.com/KiWo-TraWo/n/Rzu9tdoc]] -- Accessing replica server from `sailing-demo.sapsailing.com` via ssh or http from `ADMIN` network \ No newline at end of file +- Accessing replica server from `sailing-demo.sapsailing.com` via ssh or http from `ADMIN` network From 64d3e58aa187fc1889130b730ec7111f0728222a Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 22 Apr 2016 11:58:00 +0200 Subject: [PATCH 47/57] added fleetsCanRunInParallel also to SeriesJsonSerializer (there is no Deserializer) --- .../impl/SeriesJsonSerializer.java | 166 +++++++++--------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/SeriesJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/SeriesJsonSerializer.java index ddfa73c4d31..e2b094e6284 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/SeriesJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/SeriesJsonSerializer.java @@ -1,82 +1,84 @@ -package com.sap.sailing.server.gateway.serialization.impl; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class SeriesJsonSerializer implements JsonSerializer { - public static final String FIELD_NAME = "name"; - public static final String FIELD_FLEETS = "fleets"; - public static final String FIELD_RACES = "races"; // TODO: 'races' is wrong here... it's actually 'raceColumns' - public static final String FIELD_TRACKED_RACES = "trackedRaces"; - public static final String FIELD_STARTS_WITH_ZERO_SCORE = "startsWithZeroScore"; - public static final String FIELD_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING = "hasSplitFleetContiguousScoring"; - public static final String FIELD_IS_MEDAL_SERIES = "isMedalSeries"; - - private final JsonSerializer fleetSerializer; - - public SeriesJsonSerializer(JsonSerializer fleetSerializer) { - this.fleetSerializer = fleetSerializer; - } - - public JSONObject serialize(Series series) { - JSONObject result = new JSONObject(); - - result.put(FIELD_NAME, series.getName()); - result.put(FIELD_IS_MEDAL_SERIES, series.isMedal()); - result.put(FIELD_STARTS_WITH_ZERO_SCORE, series.isStartsWithZeroScore()); - result.put(FIELD_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING, series.hasSplitFleetContiguousScoring()); - - JSONArray fleetsJson = new JSONArray(); - for (Fleet fleet : series.getFleets()) { - fleetsJson.add(fleetSerializer.serialize(fleet)); - } - result.put(FIELD_FLEETS, fleetsJson); - - JSONArray racesJson = new JSONArray(); - for (RaceColumn raceColumn : series.getRaceColumns()) { - racesJson.add(raceColumn.getName()); - } - result.put(FIELD_RACES, racesJson); - - JSONObject trackedRacesJson = new JSONObject(); - JSONArray trackedFleetsJson = new JSONArray(); - for (Fleet fleet : series.getFleets()) { - JSONObject trackedFleetJson = new JSONObject(); - trackedFleetJson.put(FIELD_NAME, fleet.getName()); - JSONArray racesPerFleetJson = new JSONArray(); - for (RaceColumn raceColumn : series.getRaceColumns()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - JSONObject raceColumnJson = new JSONObject(); - raceColumnJson.put(FIELD_NAME, raceColumn.getName()); - raceColumnJson.put("isMedalRace" , raceColumn.isMedalRace()); - if (trackedRace != null) { - raceColumnJson.put("isLive", trackedRace.isLive(MillisecondsTimePoint.now())); - raceColumnJson.put("isTracked", true); - raceColumnJson.put("trackedRaceName", trackedRace.getRace().getName()); - raceColumnJson.put("hasGpsData", trackedRace.hasGPSData()); - raceColumnJson.put("hasWindData", trackedRace.hasWindData()); - } else { - raceColumnJson.put("isLive", false); - raceColumnJson.put("isTracked", false); - raceColumnJson.put("trackedRaceName", null); - raceColumnJson.put("hasGpsData", false); - raceColumnJson.put("hasWindData", false); - } - racesPerFleetJson.add(raceColumnJson); - } - trackedFleetJson.put(FIELD_RACES, racesPerFleetJson); - trackedFleetsJson.add(trackedFleetJson); - } - trackedRacesJson.put(FIELD_FLEETS, trackedFleetsJson); - result.put(FIELD_TRACKED_RACES, trackedRacesJson); - - return result; - } -} +package com.sap.sailing.server.gateway.serialization.impl; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class SeriesJsonSerializer implements JsonSerializer { + public static final String FIELD_NAME = "name"; + public static final String FIELD_FLEETS = "fleets"; + public static final String FIELD_RACES = "races"; // TODO: 'races' is wrong here... it's actually 'raceColumns' + public static final String FIELD_TRACKED_RACES = "trackedRaces"; + public static final String FIELD_STARTS_WITH_ZERO_SCORE = "startsWithZeroScore"; + public static final String FIELD_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING = "hasSplitFleetContiguousScoring"; + public static final String FIELD_IS_MEDAL_SERIES = "isMedalSeries"; + public static final String FIELD_FLEETS_CAN_RUN_IN_PARALLEL = "fleetsCanRunInParallel"; + + private final JsonSerializer fleetSerializer; + + public SeriesJsonSerializer(JsonSerializer fleetSerializer) { + this.fleetSerializer = fleetSerializer; + } + + public JSONObject serialize(Series series) { + JSONObject result = new JSONObject(); + + result.put(FIELD_NAME, series.getName()); + result.put(FIELD_IS_MEDAL_SERIES, series.isMedal()); + result.put(FIELD_FLEETS_CAN_RUN_IN_PARALLEL, series.isFleetsCanRunInParallel()); + result.put(FIELD_STARTS_WITH_ZERO_SCORE, series.isStartsWithZeroScore()); + result.put(FIELD_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING, series.hasSplitFleetContiguousScoring()); + + JSONArray fleetsJson = new JSONArray(); + for (Fleet fleet : series.getFleets()) { + fleetsJson.add(fleetSerializer.serialize(fleet)); + } + result.put(FIELD_FLEETS, fleetsJson); + + JSONArray racesJson = new JSONArray(); + for (RaceColumn raceColumn : series.getRaceColumns()) { + racesJson.add(raceColumn.getName()); + } + result.put(FIELD_RACES, racesJson); + + JSONObject trackedRacesJson = new JSONObject(); + JSONArray trackedFleetsJson = new JSONArray(); + for (Fleet fleet : series.getFleets()) { + JSONObject trackedFleetJson = new JSONObject(); + trackedFleetJson.put(FIELD_NAME, fleet.getName()); + JSONArray racesPerFleetJson = new JSONArray(); + for (RaceColumn raceColumn : series.getRaceColumns()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + JSONObject raceColumnJson = new JSONObject(); + raceColumnJson.put(FIELD_NAME, raceColumn.getName()); + raceColumnJson.put("isMedalRace" , raceColumn.isMedalRace()); + if (trackedRace != null) { + raceColumnJson.put("isLive", trackedRace.isLive(MillisecondsTimePoint.now())); + raceColumnJson.put("isTracked", true); + raceColumnJson.put("trackedRaceName", trackedRace.getRace().getName()); + raceColumnJson.put("hasGpsData", trackedRace.hasGPSData()); + raceColumnJson.put("hasWindData", trackedRace.hasWindData()); + } else { + raceColumnJson.put("isLive", false); + raceColumnJson.put("isTracked", false); + raceColumnJson.put("trackedRaceName", null); + raceColumnJson.put("hasGpsData", false); + raceColumnJson.put("hasWindData", false); + } + racesPerFleetJson.add(raceColumnJson); + } + trackedFleetJson.put(FIELD_RACES, racesPerFleetJson); + trackedFleetsJson.add(trackedFleetJson); + } + trackedRacesJson.put(FIELD_FLEETS, trackedFleetsJson); + result.put(FIELD_TRACKED_RACES, trackedRacesJson); + + return result; + } +} From 3396cc6b1d46688da078c9e5b57d8a3ca9b5da91 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 12:12:14 +0200 Subject: [PATCH 48/57] added AdminConsole support for parallel/sequential fleet execution for series *creation* Change-Id: If2f3a6d9c2684f84c162608ca2b57ca884e05ad0 --- .../adminconsole/RegattaDetailsComposite.java | 13 ++++++++----- .../SeriesWithFleetsCreateDialog.java | 19 +++++++++++++------ .../SeriesWithFleetsDefaultCreateDialog.java | 1 + 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java index 18e088d6ddc..cdf9d168b85 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java @@ -120,41 +120,43 @@ public class RegattaDetailsComposite extends Composite { private CellTable createRegattaSeriesTable() { CellTable table = new BaseCelltable(/* pageSize */10000, tableRes); table.setWidth("100%"); - TextColumn seriesNameColumn = new TextColumn() { @Override public String getValue(SeriesDTO series) { return series.getName(); } }; - TextColumn isMedalSeriesColumn = new TextColumn() { @Override public String getValue(SeriesDTO series) { return series.isMedal() ? stringMessages.yes() : stringMessages.no(); } }; - TextColumn startsWithZeroScoreColumn = new TextColumn() { @Override public String getValue(SeriesDTO series) { return series.isStartsWithZeroScore() ? stringMessages.yes() : stringMessages.no(); } }; - TextColumn hasSplitFleetContiguousScoringColumn = new TextColumn() { @Override public String getValue(SeriesDTO series) { return series.hasSplitFleetContiguousScoring() ? stringMessages.yes() : stringMessages.no(); } }; - TextColumn isFirstColumnIsNonDiscardableCarryForwardColumn = new TextColumn() { @Override public String getValue(SeriesDTO series) { return series.isFirstColumnIsNonDiscardableCarryForward() ? stringMessages.yes() : stringMessages.no(); } }; + TextColumn isFleetsCanRunInParallelColumn = new TextColumn() { + @Override + public String getValue(SeriesDTO series) { + return series.isFleetsCanRunInParallel() ? stringMessages.yes() : stringMessages.no(); + } + }; + TextColumn racesColumn = new TextColumn() { @Override @@ -253,6 +255,7 @@ public class RegattaDetailsComposite extends Composite { table.addColumn(isFirstColumnIsNonDiscardableCarryForwardColumn, stringMessages.firstRaceIsNonDiscardableCarryForward()); table.addColumn(startsWithZeroScoreColumn, stringMessages.startsWithZeroScore()); table.addColumn(hasSplitFleetContiguousScoringColumn, stringMessages.hasSplitFleetContiguousScoring()); + table.addColumn(isFleetsCanRunInParallelColumn, stringMessages.canFleetsRunInParallel()); table.addColumn(seriesActionColumn, stringMessages.actions()); return table; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java index 1972066466d..abcfad84eb2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java @@ -34,6 +34,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { protected final TextBox nameEntryField; protected CheckBox isMedalSeriesCheckbox; + protected CheckBox fleetsCanRunInParallelCheckbox; protected CheckBox startsWithZeroScoreCheckbox; protected CheckBox hasSplitFleetContiguousScoringCheckbox; protected CheckBox firstColumnIsNonDiscardableCarryForwardCheckbox; @@ -122,6 +123,10 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { isMedalSeriesCheckbox = createCheckbox(stringMessages.medalSeries()); isMedalSeriesCheckbox.ensureDebugId("MedalSeriesCheckbox"); + fleetsCanRunInParallelCheckbox = createCheckbox(stringMessages.canFleetsRunInParallel()); + fleetsCanRunInParallelCheckbox.setValue(true); + fleetsCanRunInParallelCheckbox.ensureDebugId("FleetsCanRaceInParallelSeriesCheckbox"); + startsWithZeroScoreCheckbox = createCheckbox(stringMessages.startsWithZeroScore()); startsWithZeroScoreCheckbox.ensureDebugId("StartsWithZeroScoreCheckbox"); @@ -162,6 +167,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { protected SeriesDTO getResult() { series.setName(nameEntryField.getText()); series.setMedal(isMedalSeriesCheckbox.getValue()); + series.setFleetsCanRunInParallel(fleetsCanRunInParallelCheckbox.getValue()); series.setStartsWithZeroScore(startsWithZeroScoreCheckbox.getValue()); series.setSplitFleetContiguousScoring(hasSplitFleetContiguousScoringCheckbox.getValue()); series.setFirstColumnIsNonDiscardableCarryForward(firstColumnIsNonDiscardableCarryForwardCheckbox.getValue()); @@ -177,16 +183,17 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { if (additionalWidget != null) { panel.add(additionalWidget); } - Grid formGrid = new Grid(7, 2); + Grid formGrid = new Grid(8, 2); panel.add(formGrid); formGrid.setWidget(0, 0, new Label(stringMessages.name() + ":")); formGrid.setWidget(0, 1, nameEntryField); formGrid.setWidget(1, 1, isMedalSeriesCheckbox); - formGrid.setWidget(2, 1, startsWithZeroScoreCheckbox); - formGrid.setWidget(3, 1, hasSplitFleetContiguousScoringCheckbox); - formGrid.setWidget(4, 1, firstColumnIsNonDiscardableCarryForwardCheckbox); - formGrid.setWidget(5, 1, useSeriesResultDiscardingThresholdsCheckbox); - formGrid.setWidget(6, 1, discardThresholdBoxes.getWidget()); + formGrid.setWidget(2, 1, fleetsCanRunInParallelCheckbox); + formGrid.setWidget(3, 1, startsWithZeroScoreCheckbox); + formGrid.setWidget(4, 1, hasSplitFleetContiguousScoringCheckbox); + formGrid.setWidget(5, 1, firstColumnIsNonDiscardableCarryForwardCheckbox); + formGrid.setWidget(6, 1, useSeriesResultDiscardingThresholdsCheckbox); + formGrid.setWidget(7, 1, discardThresholdBoxes.getWidget()); TabPanel tabPanel = new TabPanel(); tabPanel.setWidth("100%"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java index 2ba1ac26a8b..934143ba7bb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java @@ -32,6 +32,7 @@ public class SeriesWithFleetsDefaultCreateDialog extends SeriesWithFleetsCreateD super(Collections. emptyList(), stringMessages, defaultSeries.getDiscardThresholds(), callback); nameEntryField.setText(defaultSeries.getName()); // Otherwise an errorMessage will pop up isMedalSeriesCheckbox.setValue(defaultSeries.isFirstColumnIsNonDiscardableCarryForward()); + fleetsCanRunInParallelCheckbox.setValue(defaultSeries.isFleetsCanRunInParallel()); startsWithZeroScoreCheckbox.setValue(defaultSeries.isStartsWithZeroScore()); hasSplitFleetContiguousScoringCheckbox.setValue(defaultSeries.hasSplitFleetContiguousScoring()); firstColumnIsNonDiscardableCarryForwardCheckbox.setValue(defaultSeries.isFirstColumnIsNonDiscardableCarryForward()); From c3080caad1c6b0468b147d70bea1977fde6949a1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 12:47:51 +0200 Subject: [PATCH 49/57] catch the case where for what is probably a race condition due to asynchronous race loading and filter UI application a view item cannot be found Change-Id: I994b0ef297bfcc7cae8428792289ec6ba55940cd --- .../racelist/ManagedRaceListAdapter.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index 8f2c8130e82..ccb0eca9446 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -44,6 +44,7 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.text.TextUtils; +import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; @@ -314,7 +315,14 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple @Override public int compare(RaceListDataTypeRace lhs, RaceListDataTypeRace rhs) { - return c.compare(lhs.getRace(), rhs.getRace()); + final int result; + if (lhs != null && rhs != null) { + result = c.compare(lhs.getRace(), rhs.getRace()); + } else { + Log.e(TAG, "Internal error; found null for NavDrawer item while sorting"); + result = 0; + } + return result; } }); for (final RaceListDataTypeRace raceItem : raceItems) { @@ -334,7 +342,12 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple raceItemsInSeries = new ArrayList<>(); raceItemsByHeader.put(seriesHeader, raceItemsInSeries); } - raceItemsInSeries.add(viewItemsRaces.get(race)); + final RaceListDataTypeRace viewItemForRace = viewItemsRaces.get(race); + if (viewItemForRace != null) { + raceItemsInSeries.add(viewItemForRace); + } else { + Log.w(TAG, "A view item for race "+race+" provided by the filter could not be found"); + } } return raceItemsByHeader; } @@ -525,7 +538,9 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple } public void onRacesChanged() { - createViewItemsForRacesAndSeries(); - getFilter().refreshRegattaStructures(); + synchronized (mLockObject) { + createViewItemsForRacesAndSeries(); + getFilter().refreshRegattaStructures(); + } } } From 53ba7f55401f9c20922789d390d8f954836d777f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 13:19:05 +0200 Subject: [PATCH 50/57] fixed initialization order of filter and adapter, making it a lot less likely that filtered races are not found Change-Id: I544f6a9ed9e2653160a1b909f6eace78b0d256be --- .../app/ui/adapters/racelist/ManagedRaceListAdapter.java | 2 +- .../racecommittee/app/ui/fragments/RaceListFragment.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index ccb0eca9446..9bfd77f33d7 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -346,7 +346,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple if (viewItemForRace != null) { raceItemsInSeries.add(viewItemForRace); } else { - Log.w(TAG, "A view item for race "+race+" provided by the filter could not be found"); + Log.w(TAG, "A view item for race "+race+" provided by the filter could not be found", new RuntimeException("Here is where it happened")); } } return raceItemsByHeader; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 3b359417906..d2e4697879c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -409,10 +409,11 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis mAllRaces.clear(); Util.addAll(races, mAllRaces); registerOnAllRaces(); - // prepare views and do initial filtering - filterChanged(); + // prepare views and do initial filtering; update the adapter first, so it can create the new + // view items for the new races; then trigger the filter mAdapter.onRacesChanged(); mAdapter.notifyDataSetChanged(); + filterChanged(); } public void openDrawer() { From fcd845955c936935a8abc48192f01bd69f8914ad Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 14:06:58 +0200 Subject: [PATCH 51/57] made series deserializer tolerant against missing "parallel" flag on the series, defaulting to true Change-Id: Id47ed0a98e9277a302682b21933778f3e7fcf6cb --- .../racegroup/impl/SeriesWithRowsDeserializer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java index 5913caa0213..dcb3366cd2d 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racegroup/impl/SeriesWithRowsDeserializer.java @@ -25,7 +25,10 @@ public class SeriesWithRowsDeserializer implements JsonDeserializer rows = new ArrayList(); for (Object fleetObject : Helpers.getNestedArraySafe(object, RaceRowsOfSeriesWithRowsSerializer.FIELD_FLEETS)) { From d9ffdc313694a9a78b094dc9b1f64d5f1c261a65 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 14:52:28 +0200 Subject: [PATCH 52/57] arbitrate sorting ties if the index in fleet hasn't been transmitted, e.g., by an old server, use the NaturalComparator on the race column name Change-Id: I0e5a42ac44b7447d914dd5db1e13923836119b3c --- .../domain/base/racegroup/CurrentRaceComparator.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java index b6ce06b88ff..1d5a51fe8a1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.base.racegroup; import java.util.Comparator; import com.sap.sse.common.Util; +import com.sap.sse.common.util.NaturalComparator; /** * When current races of a regatta are to be displayed to a user, a reasonable ordering is key to usability. This @@ -17,15 +18,20 @@ import com.sap.sse.common.Util; * */ public class CurrentRaceComparator implements Comparator { + private final NaturalComparator naturalComparator = new NaturalComparator(); + @Override public int compare(FilterableRace o1, FilterableRace o2) { int result = getRaceGroupDisplayName(o1).compareTo(getRaceGroupDisplayName(o2)); if (result == 0) { result = o1.getZeroBasedSeriesIndex() - o2.getZeroBasedSeriesIndex(); if (result == 0) { - result = o1.getZeroBasedIndexInFleet() - o2.getZeroBasedIndexInFleet(); + result = Util.indexOf(o1.getSeries().getFleets(), o1.getFleet()) - Util.indexOf(o2.getSeries().getFleets(), o2.getFleet()); if (result == 0) { - result = Util.indexOf(o1.getSeries().getFleets(), o1.getFleet()) - Util.indexOf(o2.getSeries().getFleets(), o2.getFleet()); + result = o1.getZeroBasedIndexInFleet() - o2.getZeroBasedIndexInFleet(); + if (result == 0) { + result = naturalComparator.compare(o1.getRaceColumnName(), o2.getRaceColumnName()); + } } } } From 56497de95f25a2224fd326bf4dbe9c1bfba2e8f0 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 15:12:32 +0200 Subject: [PATCH 53/57] fixed series comparator Change-Id: Ib7e6ec94dc7881e873ca622d40ca9d93079b5a45 --- .../domain/base/racegroup/CurrentRaceComparator.java | 6 +++--- .../domain/base/racegroup/RaceGroupSeriesComparator.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java index 1d5a51fe8a1..c80dcaa0149 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/CurrentRaceComparator.java @@ -26,11 +26,11 @@ public class CurrentRaceComparator implements Comparator { if (result == 0) { result = o1.getZeroBasedSeriesIndex() - o2.getZeroBasedSeriesIndex(); if (result == 0) { - result = Util.indexOf(o1.getSeries().getFleets(), o1.getFleet()) - Util.indexOf(o2.getSeries().getFleets(), o2.getFleet()); + result = o1.getZeroBasedIndexInFleet() - o2.getZeroBasedIndexInFleet(); if (result == 0) { - result = o1.getZeroBasedIndexInFleet() - o2.getZeroBasedIndexInFleet(); + result = naturalComparator.compare(o1.getRaceColumnName(), o2.getRaceColumnName()); if (result == 0) { - result = naturalComparator.compare(o1.getRaceColumnName(), o2.getRaceColumnName()); + result = Util.indexOf(o1.getSeries().getFleets(), o1.getFleet()) - Util.indexOf(o2.getSeries().getFleets(), o2.getFleet()); } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java index c8e77c3f9c7..22c84fbc301 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java @@ -17,9 +17,9 @@ import java.util.Comparator; public class RaceGroupSeriesComparator implements Comparator { @Override public int compare(RaceGroupSeries o1, RaceGroupSeries o2) { - int result = o1.getDisplayName().compareTo(o2.getDisplayName()); + int result = o1.getSeriesOrder() - o2.getSeriesOrder(); if (result == 0) { - result = o1.getSeriesOrder() - o2.getSeriesOrder(); + result = o1.getDisplayName().compareTo(o2.getDisplayName()); } return result; } From 64eca8c4e1f8c113adeb345e20ab4e654fe246bd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 15:21:44 +0200 Subject: [PATCH 54/57] consider RaceGroup as first criterion for series ordering Change-Id: I9b2f06ec2c833c6f0a7f4531169af282f1bca859 --- .../domain/base/racegroup/RaceGroupSeriesComparator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java index 22c84fbc301..d760beee9fc 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java @@ -17,9 +17,12 @@ import java.util.Comparator; public class RaceGroupSeriesComparator implements Comparator { @Override public int compare(RaceGroupSeries o1, RaceGroupSeries o2) { - int result = o1.getSeriesOrder() - o2.getSeriesOrder(); + int result = o1.getRaceGroup().getDisplayName().compareTo(o2.getRaceGroup().getDisplayName()); if (result == 0) { - result = o1.getDisplayName().compareTo(o2.getDisplayName()); + result = o1.getSeriesOrder() - o2.getSeriesOrder(); + if (result == 0) { + result = o1.getDisplayName().compareTo(o2.getDisplayName()); + } } return result; } From 6ecb70d4c51a3401f4f5f70b54360f3f9ea478ff Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 22 Apr 2016 15:54:14 +0200 Subject: [PATCH 55/57] infer zeroBasedIndexInFleet if not transmitted explicity by searching through the RaceRow/RaceCell combo; slow but effective Change-Id: Ib66bd39bdc66a95ed853d7529de5c209865d15c5 --- .../data/parsers/ManagedRacesDataParser.java | 3 +-- .../app/domain/FleetIdentifier.java | 4 ++-- .../app/domain/impl/FleetIdentifierImpl.java | 8 +++---- .../impl/ManagedRaceIdentifierImpl.java | 4 ++-- .../app/domain/impl/ManagedRaceImpl.java | 23 ++++++++++++++++--- .../SimpleRaceIdentifierEscapingTest.java | 4 ++-- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java index 0b0789dc0ef..c6cb55eba88 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/parsers/ManagedRacesDataParser.java @@ -11,7 +11,6 @@ import org.json.simple.JSONValue; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.configuration.ConfigurationLoader; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.racegroup.RaceCell; @@ -72,7 +71,7 @@ public class ManagedRacesDataParser implements DataParser configurationLoader = globalConfigurationLoader; RegattaConfiguration localConfiguration = raceGroup.getRegattaConfiguration(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/FleetIdentifier.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/FleetIdentifier.java index 597b257aa5b..b1f87e47112 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/FleetIdentifier.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/FleetIdentifier.java @@ -1,14 +1,14 @@ package com.sap.sailing.racecommittee.app.domain; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; public interface FleetIdentifier { public Fleet getFleet(); - public SeriesBase getSeries(); + public SeriesWithRows getSeries(); public RaceGroup getRaceGroup(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java index 0ab155c64b0..25ecf802f76 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/FleetIdentifierImpl.java @@ -1,18 +1,18 @@ package com.sap.sailing.racecommittee.app.domain.impl; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; import com.sap.sailing.racecommittee.app.domain.FleetIdentifier; import com.sap.sse.common.Util.Triple; public class FleetIdentifierImpl implements FleetIdentifier { private Fleet fleet; - private SeriesBase series; + private SeriesWithRows series; private RaceGroup raceGroup; - public FleetIdentifierImpl(Fleet fleet, SeriesBase series, RaceGroup raceGroup) { + public FleetIdentifierImpl(Fleet fleet, SeriesWithRows series, RaceGroup raceGroup) { this.fleet = fleet; this.series = series; this.raceGroup = raceGroup; @@ -22,7 +22,7 @@ public class FleetIdentifierImpl implements FleetIdentifier { return fleet; } - public SeriesBase getSeries() { + public SeriesWithRows getSeries() { return series; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceIdentifierImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceIdentifierImpl.java index 10202c3a4a5..4390fe5f574 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceIdentifierImpl.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceIdentifierImpl.java @@ -1,8 +1,8 @@ package com.sap.sailing.racecommittee.app.domain.impl; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; import com.sap.sailing.racecommittee.app.domain.FleetIdentifier; import com.sap.sailing.racecommittee.app.domain.ManagedRaceIdentifier; @@ -10,7 +10,7 @@ public class ManagedRaceIdentifierImpl extends FleetIdentifierImpl implements Ma private String raceColumnName; - public ManagedRaceIdentifierImpl(String raceColumnName, Fleet fleetWithRaceNames, SeriesBase series, RaceGroup raceGroup) { + public ManagedRaceIdentifierImpl(String raceColumnName, Fleet fleetWithRaceNames, SeriesWithRows series, RaceGroup raceGroup) { super(fleetWithRaceNames, series, raceGroup); this.raceColumnName = raceColumnName; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java index 88a8e43727a..c79c4f17b9d 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/ManagedRaceImpl.java @@ -11,9 +11,9 @@ import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceCell; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.AndroidRaceLogResolver; @@ -97,7 +97,7 @@ public class ManagedRaceImpl implements ManagedRace { } @Override - public SeriesBase getSeries() { + public SeriesWithRows getSeries() { return identifier.getSeries(); } @@ -223,7 +223,24 @@ public class ManagedRaceImpl implements ManagedRace { @Override public int getZeroBasedIndexInFleet() { - return zeroBasedIndexInFleet; + int result = -1; + if (zeroBasedIndexInFleet != -1) { + // it was properly delivered by a compatible server; that's our result + result = zeroBasedIndexInFleet; + } else { + // we deal with an incompatible server that doesn't know about this field yet; + // try to compute from the surrounding race group: + int i=0; + for (final RaceCell cell : getSeries().getRaceRow(getFleet()).getCells()) { + if (cell.getName().equals(getRaceColumnName())) { + result = i; + break; + } + i++; + } + // if a cell with this race's race column name is not found, leave the index at -1 + } + return result; } @Override diff --git a/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java b/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java index d74114461d8..be50ab207a4 100755 --- a/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java +++ b/mobile/com.sap.sailing.racecommittee.app/tests-jvm/src/com/sap/sailing/racecommittee/app/domain/impl/SimpleRaceIdentifierEscapingTest.java @@ -7,8 +7,8 @@ import static org.mockito.Mockito.when; import org.junit.Test; import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.SeriesBase; import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.SeriesWithRows; import com.sap.sailing.domain.base.racegroup.impl.RaceGroupImpl; import com.sap.sailing.racecommittee.app.domain.ManagedRaceIdentifier; import com.sap.sse.common.Util.Triple; @@ -43,7 +43,7 @@ public class SimpleRaceIdentifierEscapingTest { private String build(final String leaderboardName, final String seriesName, final String fleetName, final String raceColumnName) { Fleet fleet = mock(Fleet.class); - SeriesBase series = mock(SeriesBase.class); + SeriesWithRows series = mock(SeriesWithRows.class); RaceGroup raceGroup = mock(RaceGroupImpl.class); when(fleet.getName()).thenReturn(fleetName); From 4224a14b1e51e98b31ba818eef93b04efb22d4dd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 23 Apr 2016 13:39:15 +0200 Subject: [PATCH 56/57] don't assume that RaceGroup.getDisplayName always yields a non-null value Change-Id: I3a5c542fd93ab4bac4bf906efb39d9e07ae6f9ea --- .../domain/base/racegroup/RaceGroupSeriesComparator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java index d760beee9fc..39a8c732d99 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeriesComparator.java @@ -17,7 +17,7 @@ import java.util.Comparator; public class RaceGroupSeriesComparator implements Comparator { @Override public int compare(RaceGroupSeries o1, RaceGroupSeries o2) { - int result = o1.getRaceGroup().getDisplayName().compareTo(o2.getRaceGroup().getDisplayName()); + int result = o1.getRaceGroup().getName().compareTo(o2.getRaceGroup().getName()); if (result == 0) { result = o1.getSeriesOrder() - o2.getSeriesOrder(); if (result == 0) { From f7cf2d0ec8c21824ba031a1c5c273a23d31dd13a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 23 Apr 2016 15:29:26 +0200 Subject: [PATCH 57/57] re-established mRacesByGroup; set protest flag click listener only once per protest_image view Change-Id: Icb70244dec996bd04980f22afa55c3d9cbab54dc --- .../base/racegroup/RaceGroupSeries.java | 1 - .../racecommittee/app/AppConstants.java | 4 +++ .../racelist/ManagedRaceListAdapter.java | 24 ++++++++++------ .../app/ui/fragments/RaceListFragment.java | 28 +++++++++++-------- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java index 196a43b087a..6df02d05669 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroupSeries.java @@ -106,5 +106,4 @@ public class RaceGroupSeries { } return true; } - } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppConstants.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppConstants.java index 2362c8bd529..3d915925d2b 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppConstants.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppConstants.java @@ -80,6 +80,10 @@ public class AppConstants { public final static String INTENT_ACTION_SHOW_MAIN_CONTENT = PACKAGE_NAME + ".action.show.main"; public final static String INTENT_ACTION_SHOW_SUMMARY_CONTENT = PACKAGE_NAME + ".action.show.summary"; + /** + * As extra this intent expects the String obtained for a FilterableRace / ManagedRace using + * {@code new RaceGroupSeries(race.getRaceGroup(), race.getSeries()).getDisplayName()}. + */ public final static String INTENT_ACTION_SHOW_PROTEST = PACKAGE_NAME + ".action.show.protest"; public final static String INTENT_ACTION_REMOVE_PROTEST = PACKAGE_NAME + ".action.remove.protest"; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index 9bfd77f33d7..631eff7fe50 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -79,6 +80,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple private TextView boat_class; private TextView fleet_series; private ImageView protest_image; + private final Set protestFlagsWithClickListenerSet; private ImageView has_dependent_races; private SimpleDateFormat dateFormat; private RaceListDataType mSelectedRace; @@ -91,6 +93,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple public ManagedRaceListAdapter(Context context, Set allRaces) { super(context, 0); + protestFlagsWithClickListenerSet = new HashSet<>(); mAllRaces = allRaces; mShownViewItems = new ArrayList<>(); this.viewItemsRaces = new HashMap<>(); @@ -172,15 +175,18 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple fleet_series.setVisibility(View.VISIBLE); } protest_image.setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), flag_size)); - protest_image.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(AppConstants.INTENT_ACTION_SHOW_PROTEST); - // TODO don't use toString() to convey semantics and perform check; bug 3617 - intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, header.toString()); - BroadcastManager.getInstance(getContext()).addIntent(intent); - } - }); + if (!protestFlagsWithClickListenerSet.contains(protest_image)) { + protestFlagsWithClickListenerSet.add(protest_image); + protest_image.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(AppConstants.INTENT_ACTION_SHOW_PROTEST); + // TODO don't use toString() to convey semantics and perform check; bug 3617 + intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, new RaceGroupSeries(header.getRaceGroup(), header.getSeries()).getDisplayName()); + BroadcastManager.getInstance(getContext()).addIntent(intent); + } + }); + } } else if (type == ViewType.RACE.index) { final RaceListDataTypeRace race = (RaceListDataTypeRace) raceListElement; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index d2e4697879c..c83d61505b9 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -1,5 +1,6 @@ package com.sap.sailing.racecommittee.app.ui.fragments; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; @@ -12,6 +13,7 @@ import com.sap.sailing.android.shared.util.BitmapHelper; import com.sap.sailing.android.shared.util.BroadcastManager; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.AppPreferences; @@ -25,7 +27,6 @@ import com.sap.sailing.racecommittee.app.ui.adapters.racelist.ManagedRaceListAda import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace; import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.ProtestTimeDialogFragment; -import com.sap.sailing.racecommittee.app.utils.RaceHelper; import com.sap.sailing.racecommittee.app.utils.StringHelper; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; import com.sap.sailing.racecommittee.app.utils.TickListener; @@ -121,9 +122,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis public static void showProtest(Context context, ManagedRace race) { Intent intent = new Intent(AppConstants.INTENT_ACTION_SHOW_PROTEST); - String extra = race.getRaceGroup().getName(); - extra += RaceHelper.getSeriesName(race.getSeries()); - extra += RaceHelper.getFleetName(race.getFleet()); + String extra = new RaceGroupSeries(race.getRaceGroup(), race.getSeries()).getDisplayName(); intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, extra); BroadcastManager.getInstance(context).addIntent(intent); } @@ -310,9 +309,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis @Override public void onResume() { super.onResume(); - TickSingleton.INSTANCE.registerListener(this); - IntentFilter filter = new IntentFilter(); filter.addAction(AppConstants.INTENT_ACTION_SHOW_PROTEST); LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, filter); @@ -403,8 +400,16 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis ExLog.i(getActivity(), TAG, String.format("Setting up %s with %d races.", this.getClass().getSimpleName(), Util.size(races))); unregisterOnAllRaces(); mManagedRacesById.clear(); + mRacesByGroup.clear(); for (ManagedRace managedRace : races) { mManagedRacesById.put(managedRace.getId(), managedRace); + final RaceGroupSeriesFleet key = new RaceGroupSeriesFleet(managedRace); + List raceList = mRacesByGroup.get(key); + if (raceList == null) { + raceList = new ArrayList<>(); + mRacesByGroup.put(key, raceList); + } + raceList.add(managedRace); } mAllRaces.clear(); Util.addAll(races, mAllRaces); @@ -438,10 +443,11 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis } } - private void showProtestTimeDialog(String raceGroupDisplayName) { + private void showProtestTimeDialog(String raceGroupSeriesDisplayName) { // Find the race group for which the for (RaceGroupSeriesFleet raceGroupSeriesFleet : mRacesByGroup.keySet()) { - Boolean matchingRaceGroup = raceGroupDisplayName.equals(raceGroupSeriesFleet.getDisplayName()); + Boolean matchingRaceGroup = raceGroupSeriesDisplayName.equals( + new RaceGroupSeries(raceGroupSeriesFleet.getRaceGroup(), raceGroupSeriesFleet.getSeries()).getDisplayName()); if (matchingRaceGroup) { List races = mRacesByGroup.get(raceGroupSeriesFleet); if (!isRaceListDirty(races)) { @@ -508,9 +514,9 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis @Override public void onReceive(Context context, Intent intent) { if (AppConstants.INTENT_ACTION_SHOW_PROTEST.equals(intent.getAction())) { - String raceGroupName = intent.getExtras().getString(AppConstants.INTENT_ACTION_EXTRA); - if (raceGroupName != null) { - showProtestTimeDialog(raceGroupName); + String raceGroupSeriesDisplayName = intent.getExtras().getString(AppConstants.INTENT_ACTION_EXTRA); + if (raceGroupSeriesDisplayName != null) { + showProtestTimeDialog(raceGroupSeriesDisplayName); mDrawerLayout.closeDrawers(); } else { ExLog.e(getActivity(), TAG, "INTENT_ACTION_SHOW_PROTEST does not carry an INTENT_ACTION_EXTRA with the race group name!");