mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-14 22:43:57 +00:00
Merge remote-tracking branch 'server/master'
Change-Id: I58698543eeb8ce5f350e5a2f73f0024da893cf32
This commit is contained in:
commit
c745c909fd
11 files changed
+478
-152
No files matched your search
+124
@@ -0,0 +1,124 @@
|
||||
package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.markpassingcalculation.Candidate;
|
||||
import com.sap.sailing.domain.tracking.impl.TimedComparator;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class MostProbableCandidatesInSmallTimeRangeFilterTest {
|
||||
private Candidate c1, c2, c3, c4, c5, c6, c7, c8, c9, c10;
|
||||
private NavigableSet<Candidate> competitorCandidates;
|
||||
private MostProbableCandidatesInSmallTimeRangeFilter filter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
final Candidate start = new CandidateImpl(0, now.minus(Duration.ONE_HOUR), 1.0, null);
|
||||
final Candidate end = new CandidateImpl(6, now.plus(Duration.ONE_HOUR), 1.0, null);
|
||||
final Comparator<Candidate> candidateComparator = (c1, c2)->TimedComparator.INSTANCE.compare(c1, c2);
|
||||
filter = new MostProbableCandidatesInSmallTimeRangeFilter(
|
||||
candidateComparator, start, end);
|
||||
competitorCandidates = new TreeSet<>(candidateComparator);
|
||||
final Duration gapless = MostProbableCandidatesInSmallTimeRangeFilter.CANDIDATE_FILTER_TIME_WINDOW.divide(2);
|
||||
c1 = candidate(now, "c1");
|
||||
c2 = candidate(c1.getTimePoint().plus(gapless), "c2");
|
||||
c3 = candidate(c2.getTimePoint().plus(gapless), "c3");
|
||||
c4 = candidate(c3.getTimePoint().plus(gapless), "c4");
|
||||
c5 = candidate(c4.getTimePoint().plus(gapless), "c5");
|
||||
// add a cap here that is greater than the threshold but not twice as long;
|
||||
// this way, adding one in the middle of this gap is the interesting case
|
||||
c6 = candidate(c5.getTimePoint().plus(MostProbableCandidatesInSmallTimeRangeFilter.CANDIDATE_FILTER_TIME_WINDOW.times(1.5)), "c6");
|
||||
c7 = candidate(c6.getTimePoint().plus(gapless), "c7");
|
||||
c8 = candidate(c7.getTimePoint().plus(gapless), "c8");
|
||||
c9 = candidate(c8.getTimePoint().plus(gapless), "c9");
|
||||
c10= candidate(c9.getTimePoint().plus(gapless), "c10");
|
||||
competitorCandidates.add(c1);
|
||||
competitorCandidates.add(c2);
|
||||
competitorCandidates.add(c3);
|
||||
competitorCandidates.add(c4);
|
||||
competitorCandidates.add(c5);
|
||||
competitorCandidates.add(c6);
|
||||
competitorCandidates.add(c7);
|
||||
competitorCandidates.add(c8);
|
||||
competitorCandidates.add(c9);
|
||||
competitorCandidates.add(c10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidates() {
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c3, /* includeStartFrom */ true);
|
||||
assertContains(result, c1, c2, c3, c4, c5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidatesOnRightBorder() {
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c5, /* includeStartFrom */ true);
|
||||
assertContains(result, c1, c2, c3, c4, c5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidatesOnLeftBorder() {
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c6, /* includeStartFrom */ true);
|
||||
assertContains(result, c6, c7, c8, c9, c10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfGapNotInserted() {
|
||||
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
|
||||
/* includeStartFrom */ true);
|
||||
assertContains(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfGapNotInsertedDontIncludeStartFrom() {
|
||||
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
|
||||
/* includeStartFrom */ false);
|
||||
assertContains(result, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfGapInserted() {
|
||||
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
|
||||
competitorCandidates.add(middleOfGap);
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
|
||||
/* includeStartFrom */ true);
|
||||
assertContains(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfWidenedGap() {
|
||||
competitorCandidates.remove(c5);
|
||||
competitorCandidates.remove(c6);
|
||||
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
|
||||
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
|
||||
/* includeStartFrom */ true);
|
||||
assertContains(result, middleOfGap);
|
||||
}
|
||||
|
||||
private void assertContains(NavigableSet<Candidate> candidates, Candidate... candidatesExpected) {
|
||||
assertEquals(candidatesExpected.length, candidates.size());
|
||||
for (final Candidate candidateExpected : candidatesExpected) {
|
||||
assertTrue(candidates.contains(candidateExpected));
|
||||
}
|
||||
}
|
||||
|
||||
private Candidate candidate(TimePoint timePoint, String name) {
|
||||
return new CandidateImpl(1, timePoint, 1.0, null) {
|
||||
private static final long serialVersionUID = 8547646939054562751L;
|
||||
@Override public String toString() { return name; }
|
||||
};
|
||||
}
|
||||
}
|
||||
+20
-13
@@ -120,11 +120,8 @@ public class MarkPassingCalculator {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
try {
|
||||
executor.invokeAll(tasks);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error trying to compute initial set of mark passings for race "+race.getRace().getName(), e);
|
||||
}
|
||||
ThreadPoolUtil.INSTANCE.invokeAllAndLogExceptions(executor, Level.SEVERE,
|
||||
"Error trying to compute initial set of mark passings for race "+race.getRace().getName()+": %s", tasks);
|
||||
if (listener != null) {
|
||||
synchronized (MarkPassingCalculator.this) {
|
||||
if (listenerThread == null) {
|
||||
@@ -290,9 +287,14 @@ public class MarkPassingCalculator {
|
||||
// haven't processed them yet with the CandidateFinder or CandidateChooser.
|
||||
if (!finished && !suspended) {
|
||||
if (smallestChangedWaypointIndex.value != -1) {
|
||||
logger.finer("Handling course change; smallesChangedWaypointIndex=="+smallestChangedWaypointIndex.value+
|
||||
"; waypoints added: "+addedWaypoints+"; waypoints removed: "+removedWaypoints);
|
||||
// there was a course change; finder and chooser need to be updated in this regard;
|
||||
// do this before the other changes such as new / replaced position fixes are considered:
|
||||
final Course course = race.getRace().getCourse();
|
||||
logger.finer(()->"Handling course change; smallestChangedWaypointIndex=="+smallestChangedWaypointIndex.value+
|
||||
"; waypoints added: "+addedWaypoints+"; waypoints removed: "+removedWaypoints+
|
||||
"; current course: "+course);
|
||||
final Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> candidateDeltas;
|
||||
// obtain the course's read lock so that the finder creates the candidate deltas under the
|
||||
// premise of the same course as the chooser updates its end proxy node's index; they have
|
||||
@@ -311,11 +313,19 @@ public class MarkPassingCalculator {
|
||||
for (Entry<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> entry : candidateDeltas.entrySet()) {
|
||||
tasks.add(()->{
|
||||
Util.Pair<List<Candidate>, List<Candidate>> pair = entry.getValue();
|
||||
chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB());
|
||||
return null;
|
||||
try {
|
||||
chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB());
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception trying to calculate mark passing deltas for competitor "+entry.getKey(), e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
executor.invokeAll(tasks);
|
||||
logger.finer(()->"Calculating mark passing deltas after course change in executor");
|
||||
ThreadPoolUtil.INSTANCE.invokeAllAndLogExceptions(executor, Level.SEVERE,
|
||||
"Error calculating mark passing deltas after course change in executor: %s", tasks);
|
||||
logger.finer(()->"Done calculating mark passing deltas after course change");
|
||||
}
|
||||
updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, unsuppressedMarkPassings);
|
||||
computeMarkPasses(competitorFixes, competitorFixesThatReplacedExistingOnes, markFixes);
|
||||
@@ -412,11 +422,8 @@ public class MarkPassingCalculator {
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
executor.invokeAll(tasks);
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.INFO, "mark passing calculation interrupted", e);
|
||||
}
|
||||
ThreadPoolUtil.INSTANCE.invokeAllAndLogExceptions(executor, Level.INFO,
|
||||
"Error during mark passing calculation: %s", tasks);
|
||||
}
|
||||
|
||||
private class ComputeMarkPassings implements Runnable {
|
||||
|
||||
+22
-14
@@ -370,9 +370,11 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
|
||||
/**
|
||||
* determines the candidates that are new and those that have been removed, keyed by the competitors; the
|
||||
* {@link Pair#getA() first} element of each value pair has the candidates that are new, the {@link Pair#getB()
|
||||
* second} element has those that have been removed.
|
||||
* For all waypoints starting at {@code zeroBasedIndexOfWaypointChanged-1} in the current course removes all
|
||||
* candidates for all competitors and starts from scratch to determine the candidates for the waypoints starting at
|
||||
* {@code zeroBasedIndexOfWaypointChanged-1}. The current and the removed candidates are returned, keyed by the
|
||||
* competitors; the {@link Pair#getA() first} element of each value pair has the candidates that are new, the
|
||||
* {@link Pair#getB() second} element has those that have been removed.
|
||||
*/
|
||||
private Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> invalidateAfterCourseChange(int zeroBasedIndexOfWaypointChanged) {
|
||||
ConcurrentMap<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> result = new ConcurrentHashMap<>();
|
||||
@@ -393,6 +395,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
changedWaypoints.add(w);
|
||||
}
|
||||
}
|
||||
logger.finer(()->"Changed waypoints: "+changedWaypoints);
|
||||
final Set<Callable<Void>> tasks = new HashSet<>();
|
||||
final Thread executingThread = Thread.currentThread(); // most likely the MarkPassingCalculator.Listen thread
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
@@ -419,10 +422,9 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
});
|
||||
}
|
||||
executor.invokeAll(tasks);
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.SEVERE, "Problem trying to update competitor candidate sets after waypoints starting at zero-based index "+
|
||||
zeroBasedIndexOfWaypointChanged+" have changed", e);
|
||||
ThreadPoolUtil.INSTANCE.invokeAllAndLogExceptions(executor, Level.SEVERE,
|
||||
"Problem trying to update competitor candidate sets after waypoints starting at zero-based index "+
|
||||
zeroBasedIndexOfWaypointChanged+" have changed: %s", tasks);
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
@@ -431,9 +433,10 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
|
||||
/**
|
||||
* determines the candidates that are new and those that have been removed, keyed by the competitors; the
|
||||
* {@link Pair#getA() first} element of each value pair has the candidates that are new, the {@link Pair#getB()
|
||||
* second} element has those that have been removed.
|
||||
* After a course change, described by the waypoints added ({@code addedWaypoints}) and the waypoints removed
|
||||
* ({@code removedWaypoints}), determines the candidates that are new and those that have been removed, keyed by the
|
||||
* competitors; the {@link Pair#getA() first} element of each value pair has the candidates that are new, the
|
||||
* {@link Pair#getB() second} element has those that have been removed.
|
||||
*/
|
||||
@Override
|
||||
public Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> updateWaypoints(
|
||||
@@ -449,6 +452,11 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
return newAndUpdatedCandidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* For all {@code waypoints} removed, removes the {@link #passingInstructions} entry and clears all
|
||||
* {@link #xteCandidates} and {@link #distanceCandidates} for all competitors. The candidates cleared this
|
||||
* way are returned, keyed by the {@link Competitor} to which they belonged.
|
||||
*/
|
||||
private Map<Competitor, List<Candidate>> removeWaypoints(Iterable<Waypoint> waypoints) {
|
||||
Map<Competitor, List<Candidate>> result = new HashMap<>();
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
@@ -726,7 +734,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
t, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint, distance);
|
||||
getDistanceCandidates(c, w).put(fix, newCan);
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added distance candidate " + newCan.toString() + "for " + c);
|
||||
logger.finest("Added distance candidate " + newCan.toString() + " for " + c);
|
||||
} else if (wasCan && !isCan) {
|
||||
getDistanceCandidates(c, w).remove(fix);
|
||||
result.getB().add(oldCan);
|
||||
@@ -735,7 +743,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
t, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint, distance);
|
||||
getDistanceCandidates(c, w).put(fix, newCan);
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added distance candidate " + newCan.toString() + "for " + c);
|
||||
logger.finest("Added distance candidate " + newCan.toString() + " for " + c);
|
||||
result.getB().add(oldCan);
|
||||
}
|
||||
}
|
||||
@@ -894,14 +902,14 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
waypointCandidates.remove(canFixes);
|
||||
if (newCan.getProbability() > penaltyForSkipping) {
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added XTE " + newCan.toString() + "for " + c);
|
||||
logger.finest("Added XTE " + newCan.toString() + " for " + c);
|
||||
waypointCandidates.put(canFixes, newCan);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (newCan.getProbability() > penaltyForSkipping) {
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added XTE " + newCan.toString() + "for " + c);
|
||||
logger.finest("Added XTE " + newCan.toString() + " for " + c);
|
||||
waypointCandidates.put(canFixes, newCan);
|
||||
}
|
||||
}
|
||||
|
||||
+33
-30
@@ -41,7 +41,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
* path's ends such that each path step spans a duration less than or equal to the duration specified by this field.
|
||||
* In other words, each cluster is the transitive hull of fixes not further than this duration apart.
|
||||
*/
|
||||
private static final Duration CANDIDATE_FILTER_TIME_WINDOW = Duration.ONE_SECOND.times(5);
|
||||
public static final Duration CANDIDATE_FILTER_TIME_WINDOW = Duration.ONE_SECOND.times(5);
|
||||
|
||||
/**
|
||||
* When looking for the most probable candidates within time windows of length {@link #CANDIDATE_FILTER_TIME_WINDOW},
|
||||
@@ -55,7 +55,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
private final Candidate startProxyCandidate;
|
||||
private final Candidate endProxyCandidate;
|
||||
|
||||
MostProbableCandidatesInSmallTimeRangeFilter(Comparator<Candidate> candidateComparator, Candidate startProxyCandidate,
|
||||
public MostProbableCandidatesInSmallTimeRangeFilter(Comparator<Candidate> candidateComparator, Candidate startProxyCandidate,
|
||||
Candidate endProxyCandidate) {
|
||||
this.candidateComparator = candidateComparator;
|
||||
filteredCandidates = new TreeSet<>(candidateComparator);
|
||||
@@ -66,18 +66,18 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
/**
|
||||
* Finds the contiguous (by definition of {@link #CANDIDATE_FILTER_TIME_WINDOW}) sequences of candidates adjacent to
|
||||
* those candidates added / removed. The {@code competitorCandidates} set is expected to already contain the
|
||||
* {@code newCandidates} and to no longer contain the {@code removedCandidates}. {@link #filteredCandidatesStage1} is
|
||||
* {@code newCandidates} and to no longer contain the {@code removedCandidates}. {@link #filteredCandidates} is
|
||||
* expected to not yet reflect the changes described by {@code newCandidates} and {@code removedCandidates}.
|
||||
* <p>
|
||||
*
|
||||
* The following steps are performed:
|
||||
* <ul>
|
||||
* <li>All candidates from {@code removedCandidates} are removed from {@link #filteredCandidatesStage1}.</li>
|
||||
* <li>All candidates from {@code removedCandidates} are removed from {@link #filteredCandidates}.</li>
|
||||
* <li>The disjoint sequences containing all new candidates and adjacent to all removed candidates are computed.</li>
|
||||
* <li>The most probable candidate(s) from each contiguous sequence is/are determined. Those pass the filter and are
|
||||
* added to {@link #filteredCandidatesStage1}</li>
|
||||
* added to {@link #filteredCandidates}</li>
|
||||
* <li>All other candidates from those sequences do not pass the filter. All of those that are still in
|
||||
* {@link #filteredCandidatesStage1} are removed from {@link #filteredCandidatesStage1}.</li>
|
||||
* {@link #filteredCandidates} are removed from {@link #filteredCandidates}.</li>
|
||||
* </ul>
|
||||
* @return a pair whose first element holds the set of candidates that now pass the filter and didn't before, and
|
||||
* whose second element holds the set of candidates that did pass the filter before but don't anymore
|
||||
@@ -86,13 +86,13 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
Iterable<Candidate> newCandidates, Iterable<Candidate> removedCandidates) {
|
||||
assert Util.containsAll(competitorCandidates, newCandidates); // all new candidates must be in competitorCandidates
|
||||
assert !competitorCandidates.stream().filter(c->Util.contains(removedCandidates, c)).findAny().isPresent(); // no removedCandidate must be in competitorCandidates
|
||||
final Set<Candidate> candidatesAddedToFiltered = new HashSet<>();
|
||||
final Set<Candidate> candidatesRemovedFromFiltered = new HashSet<>();
|
||||
final NavigableSet<Candidate> candidatesAddedToFiltered = new TreeSet<>(candidateComparator);
|
||||
final NavigableSet<Candidate> candidatesRemovedFromFiltered = new TreeSet<>(candidateComparator);
|
||||
// create a copy of newCandidates so we can remove objects from the copy as we add candidates to sequences;
|
||||
// this way we'll obtain disjoint sequences and don't need to consider candidates again which have already
|
||||
// been added to a sequence:
|
||||
final Set<Candidate> newCandidatesModifiableCopy = new HashSet<>();
|
||||
final Set<Candidate> removedCandidatesModifiableCopy = new HashSet<>();
|
||||
final NavigableSet<Candidate> newCandidatesModifiableCopy = new TreeSet<>(candidateComparator);
|
||||
final NavigableSet<Candidate> removedCandidatesModifiableCopy = new TreeSet<>(candidateComparator);
|
||||
Util.addAll(newCandidates, newCandidatesModifiableCopy);
|
||||
Util.addAll(removedCandidates, removedCandidatesModifiableCopy);
|
||||
// If the start/end candidates are part of newCandidates and/or removedCandidates, pass them on as is;
|
||||
@@ -107,11 +107,16 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
// remove the candidate grouped in the sequence from the remaining new candidates to consider
|
||||
// because they have been "scooped up" by this sequence already and need no further consideration:
|
||||
Util.removeAll(contiguousSequenceForNextNewCandidate, newCandidatesModifiableCopy);
|
||||
// Candidates that were removed but fall into the time range of the contiguous sequence just constructed
|
||||
// can be considered processed: the probabilities for the sequence will be recalculated anyway, and
|
||||
// the removal cannot have caused a split.
|
||||
// The candidates removed are added to candidatesRemovedFromFiltered a few lines below.
|
||||
removeAllRemovedCandidatesInOrNearSequence(contiguousSequenceForNextNewCandidate, removedCandidatesModifiableCopy);
|
||||
}
|
||||
// Those candidates that were not yet removed from removedCandidatesModifiableCopy were not within or near
|
||||
// any of the sequences produced by new candidates. Checking their CANDIDATE_FILTER_TIME_WINDOW-neighborhood
|
||||
// and constructing the sequences affected by their removal.
|
||||
// and constructing the sequences affected by their removal. Those sequences' max probabilities need to be
|
||||
// re-assessed, and the removal may cause a split.
|
||||
Util.addAll(removedCandidates, candidatesRemovedFromFiltered);
|
||||
candidatesRemovedFromFiltered.retainAll(filteredCandidates); // those explicitly removed and previously accepted by the filter go away
|
||||
while (!removedCandidatesModifiableCopy.isEmpty()) {
|
||||
@@ -157,7 +162,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
ArrayList<Candidate> sortedByProbabilityFromLowToHigh = new ArrayList<>(contiguousCandidateSequence);
|
||||
Collections.sort(sortedByProbabilityFromLowToHigh, (c1, c2)->Double.compare(c1.getProbability(), c2.getProbability()));
|
||||
double maxProbability = sortedByProbabilityFromLowToHigh.get(sortedByProbabilityFromLowToHigh.size()-1).getProbability();
|
||||
Set<Candidate> candidatesAcceptedFromSequence = new HashSet<>();
|
||||
Set<Candidate> candidatesAcceptedFromSequence = new TreeSet<>(candidateComparator);
|
||||
Candidate currentCandidate;
|
||||
for (int i=sortedByProbabilityFromLowToHigh.size()-1; i>=0 &&
|
||||
(currentCandidate=sortedByProbabilityFromLowToHigh.get(i)).getProbability()+MAX_PROBABILITY_DELTA >= maxProbability; i--) {
|
||||
@@ -222,34 +227,32 @@ public class MostProbableCandidatesInSmallTimeRangeFilter {
|
||||
* @param includeStartFrom
|
||||
* whether or not to include {@code startFrom} in the resulting set
|
||||
*/
|
||||
private NavigableSet<Candidate> getTimeWiseContiguousDistanceCandidates(NavigableSet<Candidate> competitorCandidates, Candidate startFrom, boolean includeStartFrom) {
|
||||
public NavigableSet<Candidate> getTimeWiseContiguousDistanceCandidates(NavigableSet<Candidate> competitorCandidates, Candidate startFrom, boolean includeStartFrom) {
|
||||
final NavigableSet<Candidate> result = new TreeSet<>(candidateComparator);
|
||||
if (includeStartFrom) {
|
||||
result.add(startFrom);
|
||||
}
|
||||
addContiguousCandidates(competitorCandidates.descendingSet().tailSet(startFrom), result);
|
||||
addContiguousCandidates(competitorCandidates.tailSet(startFrom), result);
|
||||
addContiguousCandidates(competitorCandidates.descendingSet().tailSet(startFrom, /* inclusive */ false), startFrom, result);
|
||||
addContiguousCandidates(competitorCandidates.tailSet(startFrom, /* inclusive */ false), startFrom, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The first element in the iteration order of {@code candidates} will not be added to {@code addTo}
|
||||
* but serves as the first element to compute distance to. The {@link #start} and {@link #end} proxy
|
||||
* candidates are ignored by this method and will never be added to {@code addTo}.
|
||||
* The first element in the iteration order of {@code candidates} will also be added to {@code addTo}. The
|
||||
* {@code startFrom} candidate serves as the first element to compute distance to. The {@link #start} and {@link #end}
|
||||
* proxy candidates are ignored by this method and will never be added to {@code addTo}.
|
||||
*/
|
||||
private void addContiguousCandidates(Iterable<Candidate> candidates, Collection<Candidate> addTo) {
|
||||
private void addContiguousCandidates(Iterable<Candidate> candidates, Candidate startFrom, Collection<Candidate> addTo) {
|
||||
Candidate current = startFrom;
|
||||
final Iterator<Candidate> iter = candidates.iterator();
|
||||
if (iter.hasNext()) {
|
||||
Candidate current = iter.next();
|
||||
while (iter.hasNext()) {
|
||||
final Candidate next = iter.next();
|
||||
if (next != startProxyCandidate && next != endProxyCandidate) {
|
||||
if (next.getTimePoint().until(current.getTimePoint()).abs().compareTo(CANDIDATE_FILTER_TIME_WINDOW) <= 0) {
|
||||
addTo.add(next);
|
||||
current = next;
|
||||
} else {
|
||||
break; // too large a gap; end of contiguous sequence in this direction found
|
||||
}
|
||||
while (iter.hasNext()) {
|
||||
final Candidate next = iter.next();
|
||||
if (next != startProxyCandidate && next != endProxyCandidate) {
|
||||
if (next.getTimePoint().until(current.getTimePoint()).abs().compareTo(CANDIDATE_FILTER_TIME_WINDOW) <= 0) {
|
||||
addTo.add(next);
|
||||
current = next;
|
||||
} else {
|
||||
break; // too large a gap; end of contiguous sequence in this direction found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
-25
@@ -1,8 +1,8 @@
|
||||
package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
@@ -213,8 +213,8 @@ public class StationarySequence {
|
||||
|
||||
private SortedSet<Candidate> getCandidatesInTimeRange(final TimeRange timeRange) {
|
||||
final SortedSet<Candidate> candidatesNoLongerPassingFilter = candidates.subSet(
|
||||
createDummyCandidate(timeRange.from()),
|
||||
createDummyCandidate(timeRange.to()));
|
||||
getCandidateMatchingTimePointOrCreateDummy(timeRange.from()),
|
||||
getCandidateMatchingTimePointOrCreateDummy(timeRange.to()));
|
||||
return candidatesNoLongerPassingFilter;
|
||||
}
|
||||
|
||||
@@ -227,8 +227,12 @@ public class StationarySequence {
|
||||
candidate.getTimePoint().until(getLast().getTimePoint()).abs().compareTo(CANDIDATE_FILTER_TIME_WINDOW) < 0;
|
||||
}
|
||||
|
||||
Iterable<Candidate> getAllCandidates() {
|
||||
return candidates;
|
||||
int size() {
|
||||
return candidates.size();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return candidates.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +262,9 @@ public class StationarySequence {
|
||||
* @param stationarySequenceSetToUpdate
|
||||
* when this method causes a change in what {@link #getFirst()} returns before and after the call, this
|
||||
* method maintains the set referenced by this parameter accordingly, assuming that the position in the
|
||||
* set may change, or, if this sequence runs empty, it has to be removed from the set altogether.
|
||||
* set may change, or, if this sequence runs empty, it has to be removed from the set altogether. Furthermore,
|
||||
* should removing the {@code candidate} reduce this sequence's size to {@code 1}, this sequence is
|
||||
* removed from the {@code stationarySequenceSetToUpdate}.
|
||||
*/
|
||||
void remove(Candidate candidate, Set<Candidate> candidatesEffectivelyAdded, Set<Candidate> candidatesEffectivelyRemoved,
|
||||
NavigableSet<StationarySequence> stationarySequenceSetToUpdate) {
|
||||
@@ -266,11 +272,11 @@ public class StationarySequence {
|
||||
final boolean wasValidCandidate = isCloseEnoughToSequenceBorder(candidate);
|
||||
final boolean wasFirst = candidate == getFirst();
|
||||
final boolean wasLast = candidate == getLast();
|
||||
if (wasFirst) {
|
||||
if (wasFirst || size() == 2) { // if size() == 2 then it will shrink to 1 and this sequence shall be removed
|
||||
stationarySequenceSetToUpdate.remove(this);
|
||||
}
|
||||
candidates.remove(candidate);
|
||||
if (wasFirst && !candidates.isEmpty()) {
|
||||
if (wasFirst && candidates.size() > 1) {
|
||||
stationarySequenceSetToUpdate.add(this);
|
||||
}
|
||||
if (wasValidCandidate) {
|
||||
@@ -302,9 +308,13 @@ public class StationarySequence {
|
||||
}
|
||||
|
||||
private void refreshBoundingBox() {
|
||||
boundingBoxOfTrackSpanningCandidates = createNewBounds(getFirst());
|
||||
boundingBoxOfTrackSpanningCandidates = computeExtendedBoundsForFixesBetweenCandidates(getFirst(), getLast());
|
||||
assert boundingBoxOfTrackSpanningCandidates != null;
|
||||
if (isEmpty()) {
|
||||
boundingBoxOfTrackSpanningCandidates = null;
|
||||
} else {
|
||||
boundingBoxOfTrackSpanningCandidates = createNewBounds(getFirst());
|
||||
boundingBoxOfTrackSpanningCandidates = computeExtendedBoundsForFixesBetweenCandidates(getFirst(), getLast());
|
||||
assert boundingBoxOfTrackSpanningCandidates != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,45 +376,55 @@ public class StationarySequence {
|
||||
tailSequence = null;
|
||||
} else {
|
||||
// split:
|
||||
final Set<Candidate> oldValidCandidates = new HashSet<>();
|
||||
final Set<Candidate> oldValidCandidates = new TreeSet<>(candidateComparator);
|
||||
Util.addAll(getValidCandidates(), oldValidCandidates);
|
||||
final Candidate dummyCandidateForFix = createDummyCandidate(newFix.getTimePoint());
|
||||
SortedSet<Candidate> tailSet = candidates.tailSet(dummyCandidateForFix);
|
||||
final Candidate candidateForFixTimePoint = getCandidateMatchingTimePointOrCreateDummy(newFix.getTimePoint());
|
||||
SortedSet<Candidate> tailSet = candidates.tailSet(candidateForFixTimePoint); // will contain a candidate for newFix.getTimePoint() if such a candidate exists
|
||||
boolean tryToAddCandidateAtFixLater = false;
|
||||
if (!tailSet.isEmpty() && tailSet.first().getTimePoint().equals(dummyCandidateForFix.getTimePoint())) {
|
||||
// new fix is exactly on a candidate in this stationary sequence; construct the tailing stationary sequence
|
||||
if (!tailSet.isEmpty() && tailSet.first().getTimePoint().equals(candidateForFixTimePoint.getTimePoint())) {
|
||||
// new fix TimePoint matches exactly with that of a candidate in this stationary sequence;
|
||||
// construct the tailing stationary sequence
|
||||
// without this candidate to start with, then try whether it can be extended to the left:
|
||||
tryToAddCandidateAtFixLater = true;
|
||||
tailSet = candidates.tailSet(dummyCandidateForFix, /* inclusive */ false);
|
||||
tailSet = candidates.tailSet(candidateForFixTimePoint, /* inclusive */ false);
|
||||
assert !tailSet.contains(candidateForFixTimePoint);
|
||||
}
|
||||
tailSequence = tailSet.isEmpty() ? null : createStationarySequence(tailSet);
|
||||
if (tailSequence != null && tryToAddCandidateAtFixLater) {
|
||||
tailSequence.tryToExtendBeforeFirst(candidates.floor(dummyCandidateForFix), new HashSet<>(), new HashSet<>(), stationarySequenceSetToUpdate);
|
||||
tailSequence.tryToExtendBeforeFirst(candidates.floor(candidateForFixTimePoint),
|
||||
new TreeSet<>(candidateComparator), new TreeSet<>(candidateComparator), stationarySequenceSetToUpdate);
|
||||
}
|
||||
// now remove the tail set candidates from this stationary sequence:
|
||||
final ArrayList<Candidate> fullTailSet = new ArrayList<>(candidates.tailSet(dummyCandidateForFix));
|
||||
final ArrayList<Candidate> fullTailSet = new ArrayList<>(candidates.tailSet(candidateForFixTimePoint));
|
||||
if (!fullTailSet.isEmpty() && fullTailSet.get(0) == getFirst()) {
|
||||
// all candidates will be removed from this sequence; the sequence must be removed from its containing set before removing the first candidate...
|
||||
stationarySequenceSetToUpdate.remove(this);
|
||||
}
|
||||
candidates.removeAll(fullTailSet);
|
||||
if (size() == 1) {
|
||||
stationarySequenceSetToUpdate.remove(this);
|
||||
}
|
||||
// ...and it doesn't need adding because if it was removed, it's empty now.
|
||||
refreshBoundingBox();
|
||||
final Set<Candidate> newValidCandidates = new HashSet<>();
|
||||
final Set<Candidate> newValidCandidates = new TreeSet<>(candidateComparator);
|
||||
Util.addAll(getValidCandidates(), newValidCandidates);
|
||||
if (tailSequence != null) { // this includes the possibility of a single candidate being added to the tail set
|
||||
Util.addAll(tailSequence.getValidCandidates(), newValidCandidates);
|
||||
}
|
||||
final Set<Candidate> candidatesAdded = new HashSet<>(newValidCandidates);
|
||||
final Set<Candidate> candidatesAdded = new TreeSet<>(candidateComparator);
|
||||
candidatesAdded.addAll(newValidCandidates);
|
||||
candidatesAdded.removeAll(newValidCandidates);
|
||||
final Set<Candidate> candidatesRemoved = new HashSet<>(oldValidCandidates);
|
||||
final Set<Candidate> candidatesRemoved = new TreeSet<>(candidateComparator);
|
||||
candidatesRemoved.addAll(oldValidCandidates);
|
||||
candidatesRemoved.removeAll(newValidCandidates);
|
||||
candidatesEffectivelyAdded.addAll(candidatesAdded);
|
||||
candidatesEffectivelyRemoved.removeAll(candidatesAdded);
|
||||
candidatesEffectivelyRemoved.addAll(candidatesRemoved);
|
||||
candidatesEffectivelyAdded.removeAll(candidatesRemoved);
|
||||
}
|
||||
return tailSequence != null && Util.size(tailSequence.getAllCandidates()) >= 2 ? tailSequence : null;
|
||||
final StationarySequence result = tailSequence != null && tailSequence.size() >= 2 ? tailSequence : null;
|
||||
assert result == null || !this.getLast().getTimePoint().equals(result.getFirst().getTimePoint());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,8 +436,8 @@ public class StationarySequence {
|
||||
* @return a sequence with the candidate(s)
|
||||
*/
|
||||
private StationarySequence createStationarySequence(SortedSet<Candidate> candidates) {
|
||||
final Set<Candidate> candidatesEffectivelyAdded = new HashSet<>();
|
||||
final Set<Candidate> candidatesEffectivelyRemoved = new HashSet<>();
|
||||
final Set<Candidate> candidatesEffectivelyAdded = new TreeSet<>(candidateComparator);
|
||||
final Set<Candidate> candidatesEffectivelyRemoved = new TreeSet<>(candidateComparator);
|
||||
final Iterator<Candidate> candidateIterator = candidates.iterator();
|
||||
assert candidateIterator.hasNext();
|
||||
final StationarySequence result = new StationarySequence(candidateIterator.next(), candidateComparator, track);
|
||||
@@ -428,6 +448,33 @@ public class StationarySequence {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this sequence contains a {@link Candidate} that matches the {@code timePoint} exactly, that candidate is
|
||||
* returned. Otherwise, a dummy candidate is created that has the {@code timePoint} provided.<p>
|
||||
*
|
||||
* Note that depending on the {@link #candidateComparator} used, two candidates are not necessarily considered
|
||||
* equal only because they have equal time points.
|
||||
*/
|
||||
private Candidate getCandidateMatchingTimePointOrCreateDummy(TimePoint timePoint) {
|
||||
final Candidate dummy = new CandidateImpl(/* one-based index of waypoint */ 1, timePoint, /* probability */ 0, /* waypoint */ null);
|
||||
final Candidate floorCandidate = candidates.floor(dummy);
|
||||
final Candidate ceilingCandidate = candidates.ceiling(dummy);
|
||||
final Candidate result;
|
||||
if (floorCandidate.getTimePoint().equals(timePoint)) {
|
||||
result = floorCandidate;
|
||||
} else if (ceilingCandidate.getTimePoint().equals(timePoint)) {
|
||||
result = ceilingCandidate;
|
||||
} else {
|
||||
result = dummy;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Stationary Sequence "+candidates+", bounds diameter "+boundingBoxOfTrackSpanningCandidates.getDiameter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dummy time point that can be used for searching in {@link #candidates} by time point. It
|
||||
* uses {@code 1} for the one-based waypoint index, {@code null} for the waypoint and {@code 0.0} for its
|
||||
@@ -436,5 +483,17 @@ public class StationarySequence {
|
||||
static Candidate createDummyCandidate(TimePoint timePoint) {
|
||||
return new CandidateImpl(/* one-based index of waypoint */ 1, timePoint, /* probability */ 0, /* waypoint */ null);
|
||||
}
|
||||
|
||||
Iterable<Candidate> getAllCandidates() {
|
||||
return Collections.unmodifiableCollection(candidates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the {@link #candidates} set contains {@code candidate}. Containment is decided based on
|
||||
* the {@link #candidateComparator}'s notion of equality.
|
||||
*/
|
||||
public boolean contains(Candidate candidate) {
|
||||
return candidates.contains(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
+127
-23
@@ -5,6 +5,7 @@ import java.util.HashSet;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
@@ -35,10 +36,13 @@ import com.sap.sse.common.Util.Pair;
|
||||
*
|
||||
* The filter is created empty. It keeps track of all stationary sequences found so far and sorts them by time. The
|
||||
* algorithm guarantees that after a call to {@link #updateCandidates(Iterable, Iterable)} as well as after a call to
|
||||
* {@link #addFix} each stationary sequence has at least two candidates in them which passed the first filter state, and
|
||||
* no stationary sequence can be extended to the next candidate following it or any previous candidate preceding it
|
||||
* because the track leading there would extend the stationary sequence's bounding box beyond limits, and no two
|
||||
* adjacent candidates that are not part of the same {@link StationarySequence} can be joined into a valid such sequence.
|
||||
* {@link #addFix} each stationary sequence has at least two candidates in them which passed the first filter state. No
|
||||
* stationary sequence can be extended to the next candidate following it or any previous candidate preceding it because
|
||||
* the track leading there would extend the stationary sequence's bounding box beyond limits, or the candidate outside
|
||||
* the sequence already belongs to an adjacent sequence. No two adjacent candidates that are not part of any
|
||||
* {@link StationarySequence} can be joined into a valid sequence.
|
||||
* <p>
|
||||
*
|
||||
* Furthermore, all {@link StationarySequence}s maintain their invariant, in particular that the track leading from
|
||||
* their {@link StationarySequence#getFirst() first} to their {@link StationarySequence#getLast() last} candidate
|
||||
* remains within a bounding box whose {@link Bounds#getDiameter() diameter} remains below the threshold. Calling
|
||||
@@ -47,6 +51,11 @@ import com.sap.sse.common.Util.Pair;
|
||||
* {@link #updateCandidates(Iterable, Iterable)} as having been removed and that currently pass this filter.
|
||||
* <p>
|
||||
*
|
||||
* Two adjacent {@link StationarySequence}s must not overlap. The {@link StationarySequence#getLast() last candidate} of
|
||||
* a sequence must be truly less---according to the {@link #candidateComparator}---than the
|
||||
* {@link StationarySequence#getFirst() first candidate} of the next sequence.
|
||||
* <p>
|
||||
*
|
||||
* The sequences are non-overlapping but may touch each other, meaning that a preceding sequence's
|
||||
* {@link StationarySequence#getLast() last} candidate is the same as the following sequence's
|
||||
* {@link StationarySequence#getFirst() first} candidate.
|
||||
@@ -73,6 +82,8 @@ import com.sap.sse.common.Util.Pair;
|
||||
*
|
||||
*/
|
||||
public class StationarySequenceBasedFilter {
|
||||
private static final Logger logger = Logger.getLogger(StationarySequenceBasedFilter.class.getName());
|
||||
|
||||
/**
|
||||
* Set of sequences managed here; ordered by their {@link StationarySequence#getFirst() first} candidate,
|
||||
* based on the candidate comparator passed to the constructor. This implies that {@link StationarySequence} objects
|
||||
@@ -195,6 +206,7 @@ public class StationarySequenceBasedFilter {
|
||||
removeCandidate(removedCandidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
}
|
||||
updateFilteredCandidates(candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
assert isCandidatesConsistent();
|
||||
return new Pair<>(candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
}
|
||||
|
||||
@@ -217,7 +229,7 @@ public class StationarySequenceBasedFilter {
|
||||
* be extended up to the new candidate, try to construct a new {@link StationarySequence} from the new candidate to
|
||||
* the neighbor (new candidate and neighbor will still pass the filter if such a sequence can be validly constructed
|
||||
* as they are the only two candidates in the new sequence for now). (Note: It is not possible that a single
|
||||
* {@link StationarySequence} can be constructed spanning between the two neighbors because it it were possible then
|
||||
* {@link StationarySequence} can be constructed spanning between the two neighbors because if it were possible then
|
||||
* this would have had to have happened before as it does not depend on the appearance of the new candidate.)</li>
|
||||
* <li>The candidate falls into an existing stationary sequence (at or after first and at or before last candidate
|
||||
* in sequence). In this case the set of fixes on the track considered within the sequence hasn't changed. The
|
||||
@@ -227,7 +239,7 @@ public class StationarySequenceBasedFilter {
|
||||
*/
|
||||
private void addCandidate(Candidate newCandidate,
|
||||
Set<Candidate> candidatesEffectivelyAdded, Set<Candidate> candidatesEffectivelyRemoved) {
|
||||
assert !candidates.contains(newCandidate);
|
||||
assert !candidates.contains(newCandidate) && isCandidatesConsistent();
|
||||
if (newCandidate == startProxyCandidate) {
|
||||
candidatesEffectivelyAdded.add(newCandidate);
|
||||
candidatesEffectivelyRemoved.remove(newCandidate);
|
||||
@@ -236,18 +248,68 @@ public class StationarySequenceBasedFilter {
|
||||
candidatesEffectivelyRemoved.remove(newCandidate);
|
||||
} else {
|
||||
candidates.add(newCandidate);
|
||||
lookLeft(newCandidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
lookRight(newCandidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
// First try adding the new candidate to a sequence starting at or before newCandidate.
|
||||
// This could either succeed by adding it to an existing sequence, or by creating a new
|
||||
// sequence with newCandidate and its immediate predecessor candidate that so far was
|
||||
// not part of a sequence. If this was successful, don't even try to extend any later sequence
|
||||
// towards newCandidate. But it newCandidate could not be added to a sequence looking into the past ("left"),
|
||||
// try our luck by looking into the future ("right").
|
||||
if (!lookLeft(newCandidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved)) {
|
||||
lookRight(newCandidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
}
|
||||
}
|
||||
assert isCandidatesConsistent();
|
||||
}
|
||||
|
||||
private void lookLeft(Candidate newCandidate, Set<Candidate> candidatesEffectivelyAdded,
|
||||
/**
|
||||
* Checks whether the union of all candidates of all stationary sequences from {@link #stationarySequences} is
|
||||
* contained in {@link #candidates}.
|
||||
*/
|
||||
private boolean isCandidatesConsistent() {
|
||||
final TreeSet<Candidate> union = new TreeSet<>(candidateComparator);
|
||||
Candidate lastCandidateInPreviousSequence = null;
|
||||
boolean overlappingTimePointFound = false;
|
||||
for (final StationarySequence stationarySequence : stationarySequences) {
|
||||
if (lastCandidateInPreviousSequence != null &&
|
||||
candidateComparator.compare(lastCandidateInPreviousSequence, stationarySequence.getFirst()) >= 0) {
|
||||
overlappingTimePointFound = true;
|
||||
logger.severe("Last candidate "+lastCandidateInPreviousSequence+
|
||||
" in sequence overlaps with first element in next sequence "+stationarySequence);
|
||||
}
|
||||
lastCandidateInPreviousSequence = stationarySequence.getLast();
|
||||
Util.addAll(stationarySequence.getAllCandidates(), union);
|
||||
}
|
||||
final boolean allSequenceCandidatesInCandidates = candidates.containsAll(union);
|
||||
if (!allSequenceCandidatesInCandidates) {
|
||||
union.removeAll(candidates);
|
||||
logger.severe("Candidates "+union+" from sequences missing from full candidates collection");
|
||||
}
|
||||
return allSequenceCandidatesInCandidates && !overlappingTimePointFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the latest {@link StationarySequence} in {@link #stationarySequences} that starts at or before
|
||||
* {@code newCandidate}. If the {@code newCandidate} is within that sequence's time range, it's simply
|
||||
* {@link StationarySequence#addWithin(Candidate, Set, Set) added}. Otherwise, it has to be later than the current
|
||||
* end of that sequence, so {@link StationarySequence#tryToExtendAfterLast(Candidate, Set, Set) extending} the
|
||||
* sequence is tried.
|
||||
* <p>
|
||||
*
|
||||
* If extending the sequence before the {@code newCandidate} does not work because it would exceed the sequence's
|
||||
* bounding box diameter threshold, an attempt is made to join the {@code newCandidate} with its predecessor
|
||||
* candidate from {@link #candidates} into a new {@link StationarySequence}.
|
||||
*
|
||||
* @return {@code true} in case the {@code newCandidate} was added to a {@link StationarySequence}, either a new one
|
||||
* because the preceding sequence could not be extended, or as an extension of the preceding sequence.
|
||||
*/
|
||||
private boolean lookLeft(Candidate newCandidate, Set<Candidate> candidatesEffectivelyAdded,
|
||||
Set<Candidate> candidatesEffectivelyRemoved) {
|
||||
final boolean addedToSequence;
|
||||
final StationarySequence latestStationarySequenceStartingAtOrBeforeNewCandidate =
|
||||
stationarySequences.floor(createStationarySequence(newCandidate));
|
||||
final boolean createNewSequenceFromLowerToNew;
|
||||
if (latestStationarySequenceStartingAtOrBeforeNewCandidate != null) {
|
||||
if (latestStationarySequenceStartingAtOrBeforeNewCandidate.getLast().getTimePoint().before(newCandidate.getTimePoint())) {
|
||||
if (candidateComparator.compare(latestStationarySequenceStartingAtOrBeforeNewCandidate.getLast(), newCandidate) < 0) {
|
||||
// earlier sequence ends before newCandidate; try to extend, and if extending doesn't work,
|
||||
// request a new sequence to be constructed:
|
||||
createNewSequenceFromLowerToNew = !latestStationarySequenceStartingAtOrBeforeNewCandidate.tryToExtendAfterLast(
|
||||
@@ -266,15 +328,27 @@ public class StationarySequenceBasedFilter {
|
||||
candidatesEffectivelyRemoved.remove(newCandidate);
|
||||
// neighbour's stationary sequence can't be extended to newCandidate or there was no earlier stationary sequence,
|
||||
// look for an earlier candidate and try to create a new stationary sequence; no candidate from the new
|
||||
// sequence will be eliminated by this filter for now as they form the sequence's boundaries.
|
||||
// sequence will be eliminated by this filter for now as they form the sequence's boundaries. Construct such
|
||||
// a sequence only if the "lower" candidate is not already part of the previous sequence, because sequences
|
||||
// must not share candidates.
|
||||
final Candidate lower = candidates.lower(newCandidate);
|
||||
if (lower != null) {
|
||||
if (lower != null && (latestStationarySequenceStartingAtOrBeforeNewCandidate == null ||
|
||||
candidateComparator.compare(latestStationarySequenceStartingAtOrBeforeNewCandidate.getLast(), lower) < 0)) {
|
||||
final StationarySequence newSequence = tryToConstructStationarySequence(lower, newCandidate);
|
||||
if (newSequence != null) {
|
||||
addedToSequence = true;
|
||||
stationarySequences.add(newSequence);
|
||||
} else {
|
||||
addedToSequence = false;
|
||||
}
|
||||
} else {
|
||||
addedToSequence = false;
|
||||
}
|
||||
} else {
|
||||
addedToSequence = true;
|
||||
}
|
||||
assert containsNoEmptyOrSingleCandidateStationarySequence() && isCandidatesConsistent();
|
||||
return addedToSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,13 +368,17 @@ public class StationarySequenceBasedFilter {
|
||||
candidatesEffectivelyAdded.add(newCandidate);
|
||||
candidatesEffectivelyRemoved.remove(newCandidate);
|
||||
final Candidate higher = candidates.higher(newCandidate);
|
||||
if (higher != null) {
|
||||
// Construct such a sequence only if the "higher" candidate is not already part of the next sequence, because sequences
|
||||
// must not share candidates.
|
||||
if (higher != null && (earliestStationarySequenceStartingAfterNewCandidate == null ||
|
||||
candidateComparator.compare(higher, earliestStationarySequenceStartingAfterNewCandidate.getFirst()) < 0)) {
|
||||
final StationarySequence newSequence = tryToConstructStationarySequence(newCandidate, higher);
|
||||
if (newSequence != null) {
|
||||
stationarySequences.add(newSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert containsNoEmptyOrSingleCandidateStationarySequence() && isCandidatesConsistent();
|
||||
}
|
||||
|
||||
private StationarySequence tryToConstructStationarySequence(Candidate start, Candidate end) {
|
||||
@@ -343,20 +421,35 @@ public class StationarySequenceBasedFilter {
|
||||
candidatesEffectivelyRemoved.add(removedCandidate);
|
||||
candidatesEffectivelyAdded.remove(removedCandidate);
|
||||
} else {
|
||||
assert candidates.contains(removedCandidate);
|
||||
assert candidates.contains(removedCandidate) && isCandidatesConsistent();
|
||||
candidates.remove(removedCandidate);
|
||||
final StationarySequence searchDummySequence = createStationarySequence(removedCandidate);
|
||||
final StationarySequence latestStationarySequenceStartingAtOrBeforeRemovedCandidate =
|
||||
stationarySequences.floor(createStationarySequence(removedCandidate));
|
||||
stationarySequences.floor(searchDummySequence);
|
||||
final boolean addToEffectivelyRemoved;
|
||||
if (latestStationarySequenceStartingAtOrBeforeRemovedCandidate != null) {
|
||||
if (!latestStationarySequenceStartingAtOrBeforeRemovedCandidate.getLast().getTimePoint()
|
||||
.before(removedCandidate.getTimePoint())) {
|
||||
// If removedCandidate is at the beginning of the sequence (having a time point equal to that of the sequence's
|
||||
// first element) then it may also be at the end of the previous sequence (having a time point equal to the previous
|
||||
// sequence's last element).
|
||||
// Being at the beginning of latestStationarySequenceStartingAtOrBeforeRemovedCandidate will make is be considered
|
||||
// effectively removed. This is consistent in case it is also at the end of the previous sequence where it
|
||||
// will then also be removed and be considered effectively removed.
|
||||
final StationarySequence previousSequence;
|
||||
if (!removedCandidate.getTimePoint().after(latestStationarySequenceStartingAtOrBeforeRemovedCandidate.getFirst().getTimePoint())
|
||||
&& (previousSequence=stationarySequences.lower(searchDummySequence)) != null
|
||||
&& previousSequence.contains(removedCandidate)) {
|
||||
assert removedCandidate.getTimePoint().equals(previousSequence.getLast().getTimePoint());
|
||||
previousSequence.remove(removedCandidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved,
|
||||
/* StationarySequence set to update */ stationarySequences);
|
||||
}
|
||||
if (candidateComparator.compare(latestStationarySequenceStartingAtOrBeforeRemovedCandidate.getLast(), removedCandidate) >= 0) {
|
||||
// within the sequence; remove:
|
||||
latestStationarySequenceStartingAtOrBeforeRemovedCandidate.remove(removedCandidate,
|
||||
candidatesEffectivelyAdded, candidatesEffectivelyRemoved,
|
||||
/* StationarySequence set to update */ stationarySequences);
|
||||
if (Util.size(latestStationarySequenceStartingAtOrBeforeRemovedCandidate.getAllCandidates()) <= 1) {
|
||||
stationarySequences.remove(latestStationarySequenceStartingAtOrBeforeRemovedCandidate);
|
||||
}
|
||||
assert !stationarySequences.contains(latestStationarySequenceStartingAtOrBeforeRemovedCandidate) ||
|
||||
latestStationarySequenceStartingAtOrBeforeRemovedCandidate.size() > 1;
|
||||
assert isCandidatesConsistent();
|
||||
addToEffectivelyRemoved = false; // already taken care of by the remove call above
|
||||
} else {
|
||||
// candidate not in any sequence
|
||||
@@ -371,8 +464,18 @@ public class StationarySequenceBasedFilter {
|
||||
candidatesEffectivelyAdded.remove(removedCandidate);
|
||||
}
|
||||
}
|
||||
assert containsNoEmptyOrSingleCandidateStationarySequence() && isCandidatesConsistent();
|
||||
}
|
||||
|
||||
private boolean containsNoEmptyOrSingleCandidateStationarySequence() {
|
||||
for (final StationarySequence ss : stationarySequences) {
|
||||
if (ss.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs this filter that for its competitor a new position fix was added or replaced. All necessary updates to
|
||||
* the stationary sequences managed by this filter are carried out, and the changes that this has to the result of
|
||||
@@ -412,6 +515,7 @@ public class StationarySequenceBasedFilter {
|
||||
*/
|
||||
Pair<Iterable<Candidate>, Iterable<Candidate>> updateFixes(Iterable<GPSFixMoving> newFixes,
|
||||
Iterable<GPSFixMoving> fixesReplacingExistingOnes) {
|
||||
assert isCandidatesConsistent();
|
||||
final Set<Candidate> candidatesEffectivelyAdded = new HashSet<>();
|
||||
final Set<Candidate> candidatesEffectivelyRemoved = new HashSet<>();
|
||||
if (newFixes != null) {
|
||||
@@ -421,10 +525,9 @@ public class StationarySequenceBasedFilter {
|
||||
if (lastSequenceStartingAtOrBeforeFix != null && !lastSequenceStartingAtOrBeforeFix.getLast().getTimePoint().before(newFix.getTimePoint())) {
|
||||
// fix falls into the existing StationarySequence; update its bounding box:
|
||||
final StationarySequence splitResult = lastSequenceStartingAtOrBeforeFix.tryToAddFix(newFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved, /* StationarySequence set to update */ stationarySequences);
|
||||
if (Util.size(lastSequenceStartingAtOrBeforeFix.getAllCandidates()) <= 1) {
|
||||
stationarySequences.remove(lastSequenceStartingAtOrBeforeFix);
|
||||
}
|
||||
assert !stationarySequences.contains(lastSequenceStartingAtOrBeforeFix) || lastSequenceStartingAtOrBeforeFix.size() > 1;
|
||||
if (splitResult != null) {
|
||||
assert splitResult.size() > 1;
|
||||
stationarySequences.add(splitResult);
|
||||
}
|
||||
}
|
||||
@@ -470,6 +573,7 @@ public class StationarySequenceBasedFilter {
|
||||
}
|
||||
}
|
||||
updateFilteredCandidates(candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
assert containsNoEmptyOrSingleCandidateStationarySequence() && isCandidatesConsistent();
|
||||
return new Pair<>(candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
|
||||
}
|
||||
|
||||
|
||||
+2
-14
@@ -161,7 +161,6 @@ public class SettingsTest extends AbstractSeleniumTest {
|
||||
leaderboardSettingsPanelPO.selectDetailsAndDeselectOther(detailsToSelect);
|
||||
leaderboardSettingsPanelPO.setRefreshInterval(1);
|
||||
leaderboardSettingsDialog.pressOk(false, false);
|
||||
|
||||
mapSettings = raceboard.openMapSettings();
|
||||
// verify default mode settings override custom user settings
|
||||
Assert.assertTrue(mapSettings.isWindUp());
|
||||
@@ -171,12 +170,9 @@ public class SettingsTest extends AbstractSeleniumTest {
|
||||
Assert.assertFalse(mapSettings.isTransparentHoverlines());
|
||||
mapSettings.setWindUp(false);
|
||||
mapSettings.setTransparentHoverlines(false);
|
||||
|
||||
mapSettings.pressOk(false, false);
|
||||
|
||||
raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, BMW_CUP_REGATTA,
|
||||
String.format(BMW_RACE, 1), "WINNING_LANES");
|
||||
|
||||
leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog();
|
||||
leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO();
|
||||
selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails();
|
||||
@@ -185,39 +181,31 @@ public class SettingsTest extends AbstractSeleniumTest {
|
||||
Assert.assertArrayEquals(detailsToSelect, selectedDetails);
|
||||
// verify that document settings are able to override custom user settings by a system default value
|
||||
Assert.assertEquals(1, leaderboardSettingsPanelPO.getRefreshInterval());
|
||||
|
||||
leaderboardSettingsDialog.pressCancel();
|
||||
|
||||
mapSettings = raceboard.openMapSettings();
|
||||
// Verify that mode settings are overridden by document settings
|
||||
Assert.assertFalse(mapSettings.isWindUp());
|
||||
// Verify custom user settings are independent for modes
|
||||
Assert.assertFalse(mapSettings.isTransparentHoverlines());
|
||||
|
||||
//FIXME uncomment when START_ANALYSIS mode can be handled by remote CI server
|
||||
// verify that custom document settings override mode settings of other modes
|
||||
// verify that custom document settings override mode settings of other modes
|
||||
raceboard = RaceBoardPage.goToRaceboardUrl(getWebDriver(), getContextRoot(), BMW_CUP_REGATTA, BMW_CUP_REGATTA,
|
||||
String.format(BMW_RACE, 1), "START_ANALYSIS");
|
||||
leaderboardSettingsDialog = raceboard.openLeaderboardSettingsDialog();
|
||||
leaderboardSettingsPanelPO = leaderboardSettingsDialog.getLeaderboardSettingsPanelPO();
|
||||
selectedDetails = leaderboardSettingsPanelPO.getSelectedDetails();
|
||||
|
||||
detailsToSelect = new DetailCheckboxInfo[] { DetailCheckboxInfo.RACE_GAP_TO_LEADER, // start analysis mode
|
||||
DetailCheckboxInfo.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_START, // start analysis mode
|
||||
DetailCheckboxInfo.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, // start analysis mode
|
||||
DetailCheckboxInfo.DISTANCE_TO_START_AT_RACE_START, // start analysis mode
|
||||
DetailCheckboxInfo.TIME_BETWEEN_RACE_START_AND_COMPETITOR_START, // start analysis mode
|
||||
DetailCheckboxInfo.SPEED_OVER_GROUND_AT_RACE_START, // start analysis mode
|
||||
DetailCheckboxInfo.SPEED_OVER_GROUND_WHEN_STARTING, // start analysis mode
|
||||
DetailCheckboxInfo.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_STARTING, // start analysis mode
|
||||
DetailCheckboxInfo.TIME_BETWEEN_RACE_START_AND_COMPETITOR_START,
|
||||
DetailCheckboxInfo.START_TACK, // start analysis mode
|
||||
|
||||
DetailCheckboxInfo.AVERAGE_SPEED_OVER_GROUND, DetailCheckboxInfo.DISTANCE, DetailCheckboxInfo.RANK_GAIN,
|
||||
|
||||
DetailCheckboxInfo.TACK, DetailCheckboxInfo.JIBE, DetailCheckboxInfo.PENALTY_CIRCLE };
|
||||
Assert.assertArrayEquals(detailsToSelect, selectedDetails);
|
||||
leaderboardSettingsDialog.pressCancel();
|
||||
|
||||
mapSettings = raceboard.openMapSettings();
|
||||
Assert.assertTrue(mapSettings.isWindUp());
|
||||
}
|
||||
|
||||
+1
-32
@@ -2,11 +2,8 @@ package com.sap.sailing.server.trackfiles.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -44,7 +41,6 @@ public class TrackFileExporterImpl implements TrackFileExporter {
|
||||
@Override
|
||||
public void writeAllData(List<TrackFilesDataSource> data, TrackFilesFormat format, List<TrackedRace> races,
|
||||
boolean dataBeforeAfter, boolean rawFixes, final ZipOutputStream out) throws IOException {
|
||||
|
||||
WriteZipCallback callback = new WriteZipCallback() {
|
||||
@Override
|
||||
public synchronized void write(ZipEntry entry, byte[] data) throws IOException {
|
||||
@@ -53,7 +49,6 @@ public class TrackFileExporterImpl implements TrackFileExporter {
|
||||
out.closeEntry();
|
||||
}
|
||||
};
|
||||
|
||||
List<WriteRaceDataCallable> callables = new ArrayList<>();
|
||||
ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
|
||||
List<String> errors = new ArrayList<>();
|
||||
@@ -64,32 +59,7 @@ public class TrackFileExporterImpl implements TrackFileExporter {
|
||||
callables.add(callable);
|
||||
}
|
||||
}
|
||||
|
||||
List<Future<Void>> results = Collections.<Future<Void>> emptyList();
|
||||
|
||||
try {
|
||||
results = executor.invokeAll(callables);
|
||||
} catch (InterruptedException e) {
|
||||
errors.add(e.getMessage());
|
||||
log.log(Level.WARNING, "Error exporting race: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for (Future<Void> result : results) {
|
||||
try {
|
||||
result.get();
|
||||
} catch (ExecutionException e) {
|
||||
Throwable t = e.getCause();
|
||||
errors.add(t.getMessage());
|
||||
log.log(Level.WARNING, "Error exporting race: " + t.getMessage());
|
||||
t.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
errors.add(e.getMessage());
|
||||
log.log(Level.WARNING, "Error exporting race: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
ThreadPoolUtil.INSTANCE.invokeAllAndLogExceptions(executor, Level.WARNING, "Error exporting race: %s", callables);
|
||||
if (errors.size() > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String error : errors) {
|
||||
@@ -100,7 +70,6 @@ public class TrackFileExporterImpl implements TrackFileExporter {
|
||||
callback.write(new ZipEntry("ERRORS"), sb.toString().getBytes());
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Error exporting race: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.sap.sse.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.sap.sse.util.impl.ThreadPoolUtilImpl;
|
||||
|
||||
@@ -79,4 +84,24 @@ public interface ThreadPoolUtil {
|
||||
ScheduledExecutorService createForegroundTaskThreadPoolExecutor(String name);
|
||||
|
||||
ScheduledExecutorService createForegroundTaskThreadPoolExecutor(int size, String name);
|
||||
|
||||
/**
|
||||
* Logs all exceptions that occurred during the execution of the {@code futures} provided.
|
||||
*
|
||||
* @param messageTemplate
|
||||
* a message string that must contain a single {@code %s} parameter placeholder that will be substituted
|
||||
* by the exception's message.
|
||||
*/
|
||||
void logExceptionsFromFutures(Level logLevel, String messageTemplate, Iterable<? extends Future<?>> futures);
|
||||
|
||||
/**
|
||||
* Uses the {@code executor}'s {@link ExecutorService#invokeAll(java.util.Collection) invokeAll} method to execute
|
||||
* all {@code tasks}. All exceptions that occur are logged, using the {@code logLevel} provided, using as the log
|
||||
* message the message template, parameterized with the exception message.
|
||||
*
|
||||
* @param messageTemplate
|
||||
* a message string that must contain a single {@code %s} parameter placeholder that will be substituted
|
||||
* by the exception's message.
|
||||
*/
|
||||
<T> List<Future<T>> invokeAllAndLogExceptions(ExecutorService executor, Level logLevel, String messageTemplate, Iterable<? extends Callable<T>> tasks);
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
package com.sap.sse.util.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.util.ThreadPoolUtil;
|
||||
|
||||
public class ThreadPoolUtilImpl implements ThreadPoolUtil {
|
||||
private static final Logger logger = Logger.getLogger(ThreadPoolUtilImpl.class.getName());
|
||||
private static final int REASONABLE_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()-1, 3);
|
||||
|
||||
private final ScheduledExecutorService defaultBackgroundTaskThreadPoolExecutor;
|
||||
@@ -58,4 +68,33 @@ public class ThreadPoolUtilImpl implements ThreadPoolUtil {
|
||||
public int getReasonableThreadPoolSize() {
|
||||
return REASONABLE_THREAD_POOL_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logExceptionsFromFutures(Level logLevel, String messageTemplate, Iterable<? extends Future<?>> futures) {
|
||||
for (final Future<?> result : futures) {
|
||||
try {
|
||||
result.get();
|
||||
} catch (ExecutionException e) {
|
||||
Throwable t = e.getCause();
|
||||
logger.log(logLevel, String.format(messageTemplate, t.getMessage()), t);
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(logLevel, String.format(messageTemplate, e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAllAndLogExceptions(ExecutorService executor, Level logLevel, String messageTemplate,
|
||||
Iterable<? extends Callable<T>> tasks) {
|
||||
final List<Callable<T>> tasksAsList = new ArrayList<>();
|
||||
Util.addAll(tasks, tasksAsList);
|
||||
List<Future<T>> result = null;
|
||||
try {
|
||||
result = executor.invokeAll(tasksAsList);
|
||||
logExceptionsFromFutures(logLevel, messageTemplate, result);
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(logLevel, String.format(messageTemplate, e.getMessage()), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||
# Overwrite the log leves for specific loggers here
|
||||
com.sap.sailing.level = FINE
|
||||
com.sap.sse.level = FINE
|
||||
com.sap.sailing.domain.markpassingcalculation.level = FINE
|
||||
com.sap.sailing.domain.markpassingcalculation.level = FINEST
|
||||
# log at level FINEST for TransientCompetitorStoreIMpl
|
||||
#com.sap.sailing.domain.base.impl.TransientCompetitorStoreImpl.level = FINEST
|
||||
#com.sap.sailing.domain.base.impl.SharedDomainFactoryImpl.level = FINEST
|
||||
|
||||
Reference in new issue
Block a user