Merge branch 'master' into ubilabs--tracking_app--develop

This commit is contained in:
Bennet Brunsen committed 2015-07-02 14:11:31 +02:00
commit e54b337eec
42 files changed
+830 -579

No files matched your search

+17 -11
View File
@@ -12,6 +12,9 @@ RETVAL=0
JAVA_START_INSTANCES="server"
GIT_REPOSITORY=/home/sailing/code
APACHE_CONFIG_DIR=/etc/httpd/conf.d
rm /var/log/sailing.out
rm /var/log/sailing.err
start_tmux() {
su - sailing -c "/home/sailing/bin/tmuxConsole.sh unattended"
@@ -29,9 +32,14 @@ start_servers() {
(
# execute env.sh in a subshell which now has the same set of variables that the server will see
source /home/sailing/servers/$conf/env.sh
# Append Apache macro invocation for /internal-server-status based on mod_status and INSTANCE_DNS to /etc/httpd/conf.d/001-events.conf
echo "Appending macro usage for /internal-server-status URL for mod_status based Apache monitoring to $APACHE_CONFIG_DIR/001-events.conf"
echo "Use Status $INSTANCE_DNS internal-server-status" >>$APACHE_CONFIG_DIR/001-events.conf
launch_httpd
echo "Starting Java server $conf"
su - sailing -c "cd /home/sailing/servers/$conf && ./start" 2>/var/log/sailing.err >/var/log/sailing.out
su - sailing -c "cd /home/sailing/servers/$conf && ./start" 2>>/var/log/sailing.err >>/var/log/sailing.out
# terminating the subshell will forget all the variable assignments brought in by env.sh, clearing for the next server instance
)
RETVAL=$?
@@ -50,16 +58,14 @@ stop_servers() {
# launch_httpd(server_name, port, event_id)
launch_httpd() {
if [ "$REPLICATE_ON_START" != "" ]; then
echo "REPLICATE_ON_START is $REPLICATE_ON_START. Will try to launch httpd so this replica can work with an ELB easily."
if [ -x /etc/init.d/httpd ]; then
server_name="`echo $SERVER_NAME | tr [A-Z] [a-z]`"
echo "Use Event $server_name.sapsailing.com \"$EVENT_ID\" 127.0.0.1 $SERVER_PORT" >>/etc/httpd/conf.d/001-events.conf
echo "Launching httpd to map ${server_name}.sapsailing.com to event with ID $EVENT_ID with server running on local port $SERVER_PORT..."
service httpd start
else
echo Can't launch httpd; start script doesn't seem to be installed at /etc/init.d/httpd
fi
echo "Will try to launch httpd so this replica can work with an ELB easily."
if [ -x /etc/init.d/httpd ]; then
server_name="`echo $SERVER_NAME | tr [A-Z] [a-z]`"
echo "Use Event $server_name.sapsailing.com \"$EVENT_ID\" 127.0.0.1 $SERVER_PORT" >>$APACHE_CONFIG_DIR/001-events.conf
echo "Launching httpd to map ${server_name}.sapsailing.com to event with ID $EVENT_ID with server running on local port $SERVER_PORT..."
service httpd start
else
echo Can't launch httpd; start script doesn't seem to be installed at /etc/init.d/httpd
fi
}
@@ -391,20 +391,17 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
}
private void onCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent event) {
if (trackedRace == null)
return;
CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog()).analyze();
List<Util.Pair<ControlPoint, PassingInstruction>> update = new ArrayList<>();
for (Waypoint waypoint : base.getWaypoints()) {
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
}
try {
trackedRace.getRace().getCourse().update(update, params.getDomainFactory());
} catch (PatchFailedException e) {
logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName());
if (trackedRace != null) {
CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog()).analyze();
List<Util.Pair<ControlPoint, PassingInstruction>> update = new ArrayList<>();
for (Waypoint waypoint : base.getWaypoints()) {
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
}
try {
trackedRace.getRace().getCourse().update(update, params.getDomainFactory());
} catch (PatchFailedException e) {
logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName());
}
}
}
@@ -30,7 +30,7 @@ public class WeakRaceLogChangedVisitor extends AbstractRaceLogChangedVisitor {
while (true) {
try {
Reference<?> ref = queue.remove();
WeakRaceLogChangedVisitor visitor = referenceToVisitor.get(ref);
WeakRaceLogChangedVisitor visitor = referenceToVisitor.remove(ref);
visitor.removeListener();
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Error trying to clean weak reference", e);
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -18,7 +19,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.IsInFinishingPhase
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.IsIndividualRecallDisplayedAnalyzer;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogChangedVisitor;
import com.sap.sailing.domain.abstractlog.race.impl.WeakRaceLogChangedVisitor;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventScheduler;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
@@ -29,6 +30,7 @@ import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProce
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedureChangedListener;
import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
/**
@@ -40,8 +42,11 @@ import com.sap.sse.common.TimePoint;
*/
public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener implements RacingProcedure,
RaceLogChangedListener {
private final static long individualRecallRemovalTimeout = 4 * 60 * 1000; // minutes * seconds * milliseconds
private static final Logger logger = Logger.getLogger(BaseRacingProcedure.class.getName());
/**
* The time since race start after which the X-ray flag has to go down
*/
private final static Duration individualRecallRemovalTimeout = Duration.ONE_MINUTE.times(4);
private RaceStateEventScheduler scheduler;
@@ -57,9 +62,11 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
private final FinishingTimeFinder finishingTimeFinder;
private final FinishedTimeFinder finishedTimeFinder;
private final RaceLogEventVisitor raceLogListener;
private final StartTimeFinder startTimeFinder;
private boolean cachedIsIndividualRecallDisplayed;
/**
* When calling me, call {@link BaseRacingProcedure#update()} afterwards!
*
@@ -72,24 +79,18 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
if (configuration == null) {
throw new IllegalArgumentException("configuration must not be null");
}
this.raceLog = raceLog;
this.author = author;
this.factory = factory;
this.configuration = configuration;
this.changedListeners = createChangedListenerContainer();
this.isRecallDisplayedAnalyzer = new IsIndividualRecallDisplayedAnalyzer(raceLog);
this.recallDisplayedFinder = new IndividualRecallDisplayedFinder(raceLog);
this.recallRemovedFinder = new IndividualRecallRemovedFinder(raceLog);
this.finishingTimeFinder = new FinishingTimeFinder(raceLog);
this.finishedTimeFinder = new FinishedTimeFinder(raceLog);
// Use the following in order to initialize a start time finder once it's needed for start time-dependent individual recall timeouts
// this.startTimeFinder = new StartTimeFinder(raceLogResolver, raceLog);
this.raceLogListener = new RaceLogChangedVisitor(this);
this.raceLog.addListener(raceLogListener);
this.startTimeFinder = new StartTimeFinder(raceLogResolver, raceLog);
this.raceLogListener = new WeakRaceLogChangedVisitor(this.raceLog, this);
this.cachedIsIndividualRecallDisplayed = false;
}
@@ -133,7 +134,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
break;
case RUNNING:
if (isIndividualRecallDisplayed()) {
rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze());
rescheduleIndividualRecallTimeout(getIndividualRecallRemovalTime());
}
break;
default:
@@ -172,7 +173,16 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
}
}
if (displayed != null) {
return displayed.plus(individualRecallRemovalTimeout);
final TimePoint raceStartTime = startTimeFinder.analyze().getStartTime();
final TimePoint referenceTimePoint;
if (raceStartTime != null) {
referenceTimePoint = raceStartTime;
} else {
logger.warning("An individual recall was set but we don't have a start time set for the race; using individual recall set time "+
displayed+" + timeout ("+individualRecallRemovalTimeout+") for implicit termination of individual recall");
referenceTimePoint = displayed;
}
return referenceTimePoint.plus(individualRecallRemovalTimeout);
}
return null;
}
@@ -218,7 +228,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
cachedIsIndividualRecallDisplayed = isRecallDisplayed;
if (cachedIsIndividualRecallDisplayed) {
changedListeners.onIndividualRecallDisplayed(this);
rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze());
rescheduleIndividualRecallTimeout(getIndividualRecallRemovalTime());
} else {
changedListeners.onIndividualRecallRemoved(this);
unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT);
@@ -231,9 +241,9 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
changedListeners.onActiveFlagsChanged(this);
}
private void rescheduleIndividualRecallTimeout(TimePoint displayTime) {
private void rescheduleIndividualRecallTimeout(TimePoint removalTime) {
unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT);
scheduleStateEvents(new RaceStateEventImpl(displayTime.plus(individualRecallRemovalTimeout), RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT));
scheduleStateEvents(new RaceStateEventImpl(removalTime, RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT));
}
protected void scheduleStateEvents(RaceStateEvent stateEvent) {
@@ -23,7 +23,6 @@ public class CourseDataImpl extends NamedImpl implements CourseBase {
@Override
public List<Leg> getLegs() {
// TODO Auto-generated method stub
return null;
}
@@ -72,7 +71,6 @@ public class CourseDataImpl extends NamedImpl implements CourseBase {
@Override
public Leg getFirstLeg() {
// TODO Auto-generated method stub
return null;
}
@@ -477,11 +477,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass) {
return null;
}
@Override
public void addTrackedRace(TrackedRace trackedRace) {
}
@@ -536,6 +531,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public void unlockTrackedRacesAfterWrite() {
}
@Override
public void removeRaceListener(RaceListener listener) {
}
};
}
@@ -54,11 +54,11 @@ public interface Leaderboard extends LeaderboardBase {
*/
public interface Entry {
int getTrackedRank();
Double getNetPoints() throws NoWindException;
Double getNetPointsUncorrected() throws NoWindException;
Double getTotalPoints() throws NoWindException;
Double getNetPoints();
Double getNetPointsUncorrected();
Double getTotalPoints();
MaxPointsReason getMaxPointsReason();
boolean isDiscarded() throws NoWindException;
boolean isDiscarded();
/**
* Tells if the net points have been corrected by a {@link ScoreCorrection}
*/
@@ -219,7 +219,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
return fleet;
}
@Override
public Double getNetPointsUncorrected() throws NoWindException {
public Double getNetPointsUncorrected() {
try {
return netPointsUncorrectedProvider.call();
} catch (Exception e) {
@@ -1068,7 +1068,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) {
Distance result = null;
for (TrackedRace trackedRace : getTrackedRaces()) {
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
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) {
@@ -1277,21 +1280,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
for (final RaceColumn raceColumn : this.getRaceColumns()) {
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
try {
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);
} catch (NoWindException e) {
logger.info("Exception trying to compute leaderboard entry for competitor "
+ competitor.getName() + " in race column " + raceColumn.getName() + ": "
+ e.getMessage());
logger.throwing(AbstractSimpleLeaderboardImpl.class.getName(),
"computeLeaderboardByName.future.call()", e);
throw new NoWindError(e);
}
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);
@@ -1362,7 +1356,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory,
boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) throws NoWindException {
boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) {
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
@@ -1538,7 +1532,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
*/
private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws NoWindException, InterruptedException, ExecutionException {
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);
@@ -1550,7 +1544,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws NoWindException, InterruptedException, ExecutionException {
RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor);
RunnableFuture<RaceDetails> raceDetails;
synchronized (raceDetailsAtEndOfTrackingCache) {
@@ -1582,7 +1576,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) throws NoWindException {
WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) {
final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>();
final Course course = trackedRace.getRace().getCourse();
course.lockForRead(); // hold back any course re-configurations while looping over the legs
@@ -1630,7 +1624,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws NoWindException {
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
LegEntryDTO result;
final Duration time = trackedLeg.getTime(timePoint);
if (trackedLeg == null || time == null) {
@@ -77,9 +77,10 @@ public class MarkPassingCalculator {
}
/**
* Waits until an object is in the queue, then drains it entirely. After that, the fixes are sorted by the object
* they are tracking in <code>competitorFixes</code> and <code>markFixes</code>. Finally, if <code>suspended</code>
* is false, new passing are computed.
* Waits until an object is in the queue, then drains it entirely. After that the information is sorted depending on
* whether it is a fix, an updated waypoint or an updated fixed markpassing. Finally, if <code>suspended</code> is
* false, new passing are computed. First new wayponts are evaluated, than new fixed markpassings. Than the
* markpassings are computed using mark and competitor fixes.
*
* @author Nicolas Klose
*
@@ -139,6 +140,7 @@ public class MarkPassingCalculator {
markFixes.clear();
addedWaypoints.clear();
removedWaypoints.clear();
smallestChangedWaypointIndex = null;
fixedMarkPassings.clear();
removedFixedMarkPassings.clear();
suppressedMarkPassings.clear();
@@ -171,10 +173,10 @@ public class MarkPassingCalculator {
/**
* The calculation has two steps. For every mark with new fixes those competitor fixes are calculated that may
* have changed their status as {@link Candidate} (see {@code FixesAffectedByNewMarkFixes}). Then, the
* {@link CandidateFinder} uses those fixes along with any new competitor fixes to calculate any new or wrong
* Candidates. These are passed to the {@link CandidateChooser} to calculate any new {@link MarkPassing}s (see
* {@link ComputeMarkPassings}).
* have changed their status as a {@link Candidate} (see {@code FixesAffectedByNewMarkFixes}). These fixes,
* along with any new competitor fixes, are passed into the executor, one task per competitor. The
* {@link CandidateFinder} uses the fixes to calculate any new or wrong Candidates. These are passed to the
* {@link CandidateChooser} to calculate any new {@link MarkPassing}s (see {@link ComputeMarkPassings}).
*
*/
private void computeMarkPasses(Map<Competitor, List<GPSFix>> newCompetitorFixes,
@@ -19,9 +19,10 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
/**
* Listens for changes that might affect the MarkPassingCalculator: new Fixes of a Competitor or a Mark and when the
* race status changes to finished. New Fixes are put in queue to be evaluated by the {@link MarkPassingCalculator} and
* the <code>end</code> object is passed through to signal the end of the race.
* Listens for changes that might affect the MarkPassingCalculator: new Fixes of a Competitor or a Mark, updated
* Markpassings or Waypoints and the end of the race. New information is put in to a queue to be evaluated by the
* {@link MarkPassingCalculator}. To have only one queue each fix is stored in a StorePositionUpdateStrategy, which than
* sorts itself in the MPC.
*
* @author Nicolas Klose
*
@@ -31,9 +32,10 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
private LinkedBlockingQueue<StorePositionUpdateStrategy> queue;
private final StorePositionUpdateStrategy endMarker = new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex, List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
}
};
@@ -55,9 +57,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void competitorPositionChanged(final GPSFixMoving fix, final Competitor competitor) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
List<GPSFix> list = competitorFixes.get(competitor);
if (list == null) {
@@ -73,9 +77,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void markPositionChanged(final GPSFix fix, final Mark mark, boolean firstInTrack) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
List<GPSFix> list = markFixes.get(mark);
if (list == null) {
@@ -92,7 +98,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
}
public void stop() {
logger.info("Stopping "+this);
logger.info("Stopping " + this);
queue.add(endMarker);
}
@@ -100,9 +106,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void waypointAdded(final int zeroBasedIndex, final Waypoint waypointThatGotAdded) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
addedWaypoints.add(waypointThatGotAdded);
if (smallestChangedWaypointIndex == null || smallestChangedWaypointIndex > zeroBasedIndex) {
@@ -116,9 +124,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void waypointRemoved(final int zeroBasedIndex, final Waypoint waypointThatGotRemoved) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
removedWaypoints.add(waypointThatGotRemoved);
if (smallestChangedWaypointIndex == null || smallestChangedWaypointIndex > zeroBasedIndex) {
@@ -128,14 +138,18 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
});
}
public void addFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint, final TimePoint timePointOfFixedPassing) {
public void addFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint,
final TimePoint timePointOfFixedPassing) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
fixedMarkPassings.add(new Triple<Competitor, Integer, TimePoint>(c, zeroBasedIndexOfWaypoint, timePointOfFixedPassing));
fixedMarkPassings.add(new Triple<Competitor, Integer, TimePoint>(c, zeroBasedIndexOfWaypoint,
timePointOfFixedPassing));
}
});
}
@@ -143,9 +157,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void removeFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
removedMarkPassings.add(new Pair<Competitor, Integer>(c, zeroBasedIndexOfWaypoint));
}
@@ -156,12 +172,14 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void addSuppressedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
suppressedMarkPassings.add(new Pair<Competitor, Integer>(c, zeroBasedIndexOfWaypoint));
}
});
}
@@ -169,9 +187,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
public void removeSuppressedPassing(final Competitor c) {
queue.add(new StorePositionUpdateStrategy() {
@Override
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
Integer smallestChangedWaypointIndex,
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
List<Pair<Competitor, Integer>> removedMarkPassings,
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
unSuppressedMarkPassings.add(c);
}
@@ -40,17 +40,19 @@ import com.sap.sse.common.Util;
* The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all,
* every time a competitor passes the crossing-bearing of a waypoint, a candidate is created using linear interpolation
* to estimate the exact time the bearing was crossed. Secondly, all local distance minima to a waypoint are candidates.
* The probability of a candidate depends on its distance , whether it is on the right side and if it passes in the
* right direction of its waypoint.
* The probability of a candidate depends on its distance, whether it is on the right side and if it passes in the right
* direction of its waypoint.
*
* @author Nicolas Klose
*
*/
public class CandidateFinderImpl implements CandidateFinder {
// TODO Parallelization?
// The higher this is, the closer the fixes have to be to waypoint to become a Candidate
private final int strictnessOfDistanceBasedProbability = 8;
// All of the penalties are multiplied onto the propability of a Candidate. A value of 0 exludes Candidates that do
// not fit, a value of 1 imposes no penalty on each criteria
private final double penaltyForWrongSide = 0.7;
private final double penaltyForWrongDirection = 0.7;
private final double penaltyForDistanceCandidates = 0.7;
@@ -120,8 +122,8 @@ public class CandidateFinderImpl implements CandidateFinder {
TimePoint end = null;
for (Entry<Mark, List<GPSFix>> fixes : markFixes.entrySet()) {
for (GPSFix fix : fixes.getValue()) {
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey())
.getEstimatedPositionTimePeriodAffectedBy(fix);
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(
fix);
TimePoint newStart = timePoints.from();
TimePoint newEnd = timePoints.to();
start = start == null || start.after(newStart) ? newStart : start;
@@ -157,17 +159,23 @@ public class CandidateFinderImpl implements CandidateFinder {
List<Candidate> newCans = new ArrayList<>();
List<Candidate> wrongCans = new ArrayList<>();
Util.Pair<List<Candidate>, List<Candidate>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes,
race.getRace().getCourse().getWaypoints());
Util.Pair<List<Candidate>, List<Candidate>> xteCandidates = checkForXTECandidatesChanges(c, fixes, race
.getRace().getCourse().getWaypoints());
logger.finest(distanceCandidates.getA().size() + " new Distance Candidates, " + xteCandidates.getA().size()
+ " new XTE Candidates, " + distanceCandidates.getB().size() + " removed distance Candidates and "
+ xteCandidates.getB().size() + " removed XTE Candidates.");
newCans.addAll(xteCandidates.getA());
newCans.addAll(distanceCandidates.getA());
wrongCans.addAll(xteCandidates.getB());
wrongCans.addAll(distanceCandidates.getB());
Course course = race.getRace().getCourse();
course.lockForRead();
try {
Util.Pair<List<Candidate>, List<Candidate>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes,
race.getRace().getCourse().getWaypoints());
Util.Pair<List<Candidate>, List<Candidate>> xteCandidates = checkForXTECandidatesChanges(c, fixes, race
.getRace().getCourse().getWaypoints());
logger.finest(distanceCandidates.getA().size() + " new Distance Candidates, " + xteCandidates.getA().size()
+ " new XTE Candidates, " + distanceCandidates.getB().size() + " removed distance Candidates and "
+ xteCandidates.getB().size() + " removed XTE Candidates.");
newCans.addAll(xteCandidates.getA());
newCans.addAll(distanceCandidates.getA());
wrongCans.addAll(xteCandidates.getB());
wrongCans.addAll(distanceCandidates.getB());
} finally {
course.unlockAfterRead();
}
return new Util.Pair<Iterable<Candidate>, Iterable<Candidate>>(newCans, wrongCans);
}
@@ -179,27 +187,33 @@ public class CandidateFinderImpl implements CandidateFinder {
xteCache.get(c).clear();
}
List<Waypoint> changedWaypoints = new ArrayList<>();
for (Waypoint w : course.getWaypoints()) {
if (course.getIndexOfWaypoint(w) > indexOfChange - 2) {
changedWaypoints.add(w);
course.lockForRead();
try {
for (Waypoint w : course.getWaypoints()) {
if (course.getIndexOfWaypoint(w) > indexOfChange - 2) {
changedWaypoints.add(w);
}
}
}
for (Competitor c : race.getRace().getCompetitors()) {
List<Candidate> badCans = new ArrayList<>();
List<Candidate> newCans = new ArrayList<>();
for (Waypoint w : changedWaypoints) {
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
badCans.addAll(xteCans.values());
xteCans.clear();
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
badCans.addAll(distanceCans.values());
distanceCans.clear();
for (Competitor c : race.getRace().getCompetitors()) {
List<Candidate> badCans = new ArrayList<>();
List<Candidate> newCans = new ArrayList<>();
for (Waypoint w : changedWaypoints) {
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
badCans.addAll(xteCans.values());
xteCans.clear();
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
badCans.addAll(distanceCans.values());
distanceCans.clear();
}
Set<GPSFix> allFixes = getAllFixes(c);
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
}
Set<GPSFix> allFixes = getAllFixes(c);
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
} finally {
course.unlockAfterRead();
}
return result;
}
@@ -215,7 +229,6 @@ public class CandidateFinderImpl implements CandidateFinder {
}
private Map<Competitor, List<Candidate>> removeWaypoints(Iterable<Waypoint> waypoints) {
// TODO Clear caches?
Map<Competitor, List<Candidate>> result = new HashMap<>();
for (Competitor c : race.getRace().getCompetitors()) {
result.put(c, new ArrayList<Candidate>());
@@ -237,7 +250,7 @@ public class CandidateFinderImpl implements CandidateFinder {
}
return result;
}
private Map<GPSFix, Candidate> getDistanceCandidates(Competitor c, Waypoint w) {
synchronized (distanceCandidates) {
Map<GPSFix, Candidate> result = distanceCandidates.get(c).get(w);
@@ -295,10 +308,10 @@ public class CandidateFinderImpl implements CandidateFinder {
try {
track.lockForRead();
for (GPSFix fix : track.getFixes(
race.getStartOfTracking()==null?TimePoint.BeginningOfTime:race.getStartOfTracking(),
/* fromInclusive */ true,
race.getEndOfTracking()==null?TimePoint.EndOfTime:race.getEndOfTracking(),
/* toInclusive */ true)) {
race.getStartOfTracking() == null ? TimePoint.BeginningOfTime : race.getStartOfTracking(),
/* fromInclusive */true,
race.getEndOfTracking() == null ? TimePoint.EndOfTime : race.getEndOfTracking(),
/* toInclusive */true)) {
fixes.add(fix);
}
} finally {
@@ -360,8 +373,10 @@ public class CandidateFinderImpl implements CandidateFinder {
List<Distance> waypointDistances = fixDistances.get(w);
List<Distance> waypointDistancesBefore = fixDistancesBefore.get(w);
List<Distance> waypointDistancesAfter = fixDistancesAfter.get(w);
// due to course changes, waypoints that exist in the waypoints collection may not have a corresponding
// key in passingInstructions' key set which is the basis for the waypoints for which getDistances(...)
// due to course changes, waypoints that exist in the waypoints collection may not have a
// corresponding
// key in passingInstructions' key set which is the basis for the waypoints for which
// getDistances(...)
// computes results; so we have to check for null here:
if (waypointDistances != null && waypointDistancesBefore != null && waypointDistancesAfter != null) {
Iterator<Distance> disIter = waypointDistances.iterator();
@@ -396,8 +411,8 @@ public class CandidateFinderImpl implements CandidateFinder {
wasCan = true;
}
if (!wasCan && isCan) {
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t,
probability, w);
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
t, probability, w);
getDistanceCandidates(c, w).put(fix, newCan);
result.getA().add(newCan);
logger.finest("Added distance" + newCan.toString() + "for " + c);
@@ -405,8 +420,8 @@ public class CandidateFinderImpl implements CandidateFinder {
getDistanceCandidates(c, w).remove(fix);
result.getB().add(oldCan);
} else if (wasCan && isCan && oldCan.getProbability() != probability) {
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t,
probability, w);
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
t, probability, w);
getDistanceCandidates(c, w).put(fix, newCan);
result.getA().add(newCan);
logger.finest("Added distance" + newCan.toString() + "for " + c);
@@ -425,9 +440,15 @@ public class CandidateFinderImpl implements CandidateFinder {
if (result == null) {
// Else calculate distances and put them into the cache
result = new LinkedHashMap<>();
for (Waypoint w : getWaypoints()) {
List<Distance> distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint());
result.put(w, distances);
Course course = race.getRace().getCourse();
course.lockForRead();
try {
for (Waypoint w : course.getWaypoints()) {
List<Distance> distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint());
result.put(w, distances);
}
} finally {
course.unlockAfterRead();
}
distanceCache.get(c).put(fix, result);
}
@@ -560,24 +581,26 @@ public class CandidateFinderImpl implements CandidateFinder {
result = new HashMap<>();
Position p = fix.getPosition();
TimePoint t = fix.getTimePoint();
for (Waypoint w : getWaypoints()) {
List<Distance> distances = new ArrayList<>();
result.put(w, distances);
for (Util.Pair<Position, Bearing> crossingInfo : getCrossingInformation(w, t)) {
if (crossingInfo.getA() != null && crossingInfo.getB() != null) {
distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB()));
Course course = race.getRace().getCourse();
course.lockForRead();
try {
for (Waypoint w : course.getWaypoints()) {
List<Distance> distances = new ArrayList<>();
result.put(w, distances);
for (Util.Pair<Position, Bearing> crossingInfo : getCrossingInformation(w, t)) {
if (crossingInfo.getA() != null && crossingInfo.getB() != null) {
distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB()));
}
}
}
} finally {
course.unlockAfterRead();
}
xteCache.get(c).put(fix, result);
}
return result;
}
private Iterable<Waypoint> getWaypoints() {
return race.getRace().getCourse().getWaypoints();
}
/**
* Calculates the cross-track error of each fix to the position and crossing bearing of each waypoint. Gates have
* two of these and lines always go from the port mark to the starboard mark.
@@ -833,7 +856,8 @@ public class CandidateFinderImpl implements CandidateFinder {
private Util.Pair<Mark, Mark> getPortAndStarboardMarks(TimePoint t, Waypoint w) {
List<Position> markPositions = new ArrayList<Position>();
for (Mark mark : w.getMarks()) {
final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */ false);
final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */
false);
if (estimatedMarkPosition == null) {
return new Util.Pair<Mark, Mark>(null, null);
}
@@ -195,7 +195,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
* <code>timePoint</code>, returns the VMG value for this time point. If the competitor has already finished the
* leg, the VMG at the time when the competitor finished the leg is returned.
*/
Speed getVelocityMadeGood(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException;
Speed getVelocityMadeGood(TimePoint timePoint, WindPositionMode windPositionMode);
/**
* Same as {@link #getVelocityMadeGood(TimePoint, WindPositionMode)}, only that a cache for wind data and leg type and bearing
@@ -212,7 +212,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
/**
* Same as {@link #getEstimatedTimeToNextMark(TimePoint, WindPositionMode)}, only that a cache for leg type calculation is passed.
*/
Duration getEstimatedTimeToNextMark(TimePoint timePoint, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException;
Duration getEstimatedTimeToNextMark(TimePoint timePoint, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache);
/**
* Returns <code>null</code> in case this leg's competitor hasn't started the leg yet. If in the leg at
@@ -3,7 +3,6 @@ package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
@@ -55,8 +54,6 @@ public interface TrackedRegatta extends Serializable {
void unlockTrackedRacesAfterWrite();
Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass);
/**
* Creates a {@link TrackedRace} based on the parameter specified and {@link #addTrackedRace(TrackedRace) adds} it
* to this tracked regatta. Afterwards, calling {@link #getTrackedRace(RaceDefinition) getTrackedRace(raceDefinition)}
@@ -93,6 +90,8 @@ public interface TrackedRegatta extends Serializable {
*/
void addRaceListener(RaceListener listener);
void removeRaceListener(RaceListener listener);
int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
void removeTrackedRace(RaceDefinition raceDefinition);
@@ -284,11 +284,13 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
// as fallback in the absence of wind information, project to leg bearing
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
}
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
if (cos < 0) {
projectToBearing = projectToBearing.reverse();
if (speed.getBearing() != null && projectToBearing != null) {
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
if (cos < 0) {
projectToBearing = projectToBearing.reverse();
}
result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing);
}
result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing);
}
return result;
}
@@ -338,10 +340,12 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
final Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */false);
if (endPos != null) {
Distance d = getTrackedLeg().getAbsoluteWindwardDistance(
getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false),
endPos, to, WindPositionMode.EXACT, cache);
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
final Position startPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false);
if (startPos != null) {
Distance d = getTrackedLeg().getAbsoluteWindwardDistance(startPos, endPos, to,
WindPositionMode.EXACT, cache);
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
}
}
}
return result;
@@ -613,7 +617,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
@Override
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode) {
return getVelocityMadeGood(at, windPositionMode, new LeaderboardDTOCalculationReuseCache(at));
}
@@ -1024,7 +1024,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// competitor has finished race at or before the requested time point; use time point of crossing the finish line
end = markPassings.last().getTimePoint();
} else if ((trackedLegOfCompetitor=getTrackedLeg(competitor, timePoint)) == null ||
(!trackedLegOfCompetitor.hasFinishedLeg(timePoint)
(!trackedLegOfCompetitor.hasFinishedLeg(getEndOfTracking())
&& ((getEndOfTracking() != null && timePoint.after(getEndOfTracking()))
|| getStatus().getStatus() == TrackedRaceStatusEnum.FINISHED))) {
// If the race is no longer tracking and hence no more data can be expected, and the competitor
@@ -5,17 +5,14 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
@@ -51,21 +48,19 @@ public class TrackedRegattaImpl implements TrackedRegatta {
*/
private final Map<RaceDefinition, TrackedRace> trackedRaces;
private final Map<BoatClass, Collection<TrackedRace>> trackedRacesByBoatClass;
private transient Set<RaceListener> raceListeners;
private transient ConcurrentHashMap<RaceListener, RaceListener> raceListeners;
public TrackedRegattaImpl(Regatta regatta) {
super();
trackedRacesLock = new NamedReentrantReadWriteLock("trackeRaces lock for tracked regatta "+regatta.getName(), /* fair */ false);
this.regatta = regatta;
this.trackedRaces = new HashMap<RaceDefinition, TrackedRace>();
this.trackedRacesByBoatClass = new HashMap<BoatClass, Collection<TrackedRace>>();
raceListeners = new HashSet<RaceListener>();
raceListeners = new ConcurrentHashMap<RaceListener, RaceListener>();
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
this.raceListeners = new HashSet<RaceListener>();
this.raceListeners = new ConcurrentHashMap<RaceListener, RaceListener>();
}
@Override
@@ -104,7 +99,6 @@ public class TrackedRegattaImpl implements TrackedRegatta {
private Object readResolve() throws ObjectStreamException {
TrackedRegattaImpl result = new TrackedRegattaImpl(this.regatta);
result.trackedRaces.putAll(this.trackedRaces);
result.trackedRacesByBoatClass.putAll(this.trackedRacesByBoatClass);
return result;
}
@@ -116,19 +110,11 @@ public class TrackedRegattaImpl implements TrackedRegatta {
logger.info("adding tracked race for "+trackedRace.getRace()+" to tracked regatta "+getRegatta().getName()+
" with regatta hash code "+getRegatta().hashCode());
oldTrackedRace = trackedRaces.put(trackedRace.getRace(), trackedRace);
if (oldTrackedRace != trackedRace) {
Collection<TrackedRace> coll = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass());
if (coll == null) {
coll = new ArrayList<TrackedRace>();
trackedRacesByBoatClass.put(trackedRace.getRace().getBoatClass(), coll);
}
coll.add(trackedRace);
}
} finally {
LockUtil.unlockAfterWrite(trackedRacesLock);
}
if (oldTrackedRace != trackedRace) {
for (RaceListener listener : raceListeners) {
for (RaceListener listener : raceListeners.keySet()) {
listener.raceAdded(trackedRace);
}
}
@@ -149,14 +135,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
LockUtil.lockForWrite(trackedRacesLock);
try {
trackedRaces.remove(trackedRace.getRace());
Collection<TrackedRace> trbbc = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass());
if (trbbc != null) {
trbbc.remove(trackedRace);
if (trbbc.isEmpty()) {
trackedRacesByBoatClass.remove(trackedRace.getRace().getBoatClass());
}
}
for (RaceListener listener : raceListeners) {
for (RaceListener listener : raceListeners.keySet()) {
listener.raceRemoved(trackedRace);
}
} finally {
@@ -177,23 +156,40 @@ public class TrackedRegattaImpl implements TrackedRegatta {
return trackedRaces.values();
}
@Override
public Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass) {
return trackedRacesByBoatClass.get(boatClass);
}
@Override
public TrackedRace getTrackedRace(RaceDefinition race) {
boolean interrupted = false;
lockTrackedRacesForRead();
try {
TrackedRace result = trackedRaces.get(race);
while (!interrupted && result == null) {
if (!interrupted && result == null) {
final Object mutex = new Object();
final RaceListener listener = new RaceListener() {
@Override
public void raceRemoved(TrackedRace trackedRace) {}
@Override
public void raceAdded(TrackedRace trackedRace) {
synchronized (mutex) {
mutex.notifyAll();
}
}
};
addRaceListener(listener);
try {
trackedRaces.wait();
result = trackedRaces.get(race);
} catch (InterruptedException e) {
interrupted = true;
synchronized (mutex) {
result = trackedRaces.get(race);
while (!interrupted && result == null) {
try {
mutex.wait();
result = trackedRaces.get(race);
} catch (InterruptedException e) {
interrupted = true;
}
}
}
} finally {
removeRaceListener(listener);
}
}
return result;
@@ -217,7 +213,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
final List<TrackedRace> trackedRacesCopy = new ArrayList<>();
lockTrackedRacesForRead();
try {
raceListeners.add(listener);
raceListeners.put(listener, listener);
Util.addAll(getTrackedRaces(), trackedRacesCopy);
} finally {
unlockTrackedRacesAfterRead();
@@ -227,6 +223,16 @@ public class TrackedRegattaImpl implements TrackedRegatta {
}
}
@Override
public void removeRaceListener(RaceListener listener) {
lockTrackedRacesForRead();
try {
raceListeners.remove(listener);
} finally {
unlockTrackedRacesAfterRead();
}
}
@Override
public int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException {
int result = 0;
@@ -47,8 +47,8 @@
<plugin id="com.sap.sailing.gwt.ui" autoStart="true" startLevel="5" />
<plugin id="com.sap.sailing.kiworesultimport" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.manage2sail.resultimport" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.news" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.monitoring" autoStart="true" startLevel="6" />
<plugin id="com.sap.sailing.news" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.polars" autoStart="true" startLevel="4" />
<plugin id="com.sap.sailing.resultimport" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.sailwave.resultimport" autoStart="true" startLevel="3" />
@@ -63,7 +63,7 @@
<plugin id="com.sap.sailing.xrr.resultimport" autoStart="true" startLevel="3" />
<plugin id="com.sap.sse.filestorage" autoStart="true" startLevel="3" />
<plugin id="com.sap.sse.mail" autoStart="true" startLevel="4" />
<plugin id="com.sap.sse.replication" autoStart="true" startLevel="4" />
<plugin id="com.sap.sse.replication" autoStart="true" startLevel="5" />
<plugin id="com.sap.sse.security.ui" autoStart="true" startLevel="5" />
<plugin id="com.sap.sse.security.userstore.mongodb" autoStart="true" startLevel="3" />
<plugin id="org.apache.felix.shell" autoStart="true" startLevel="0" />
@@ -11,7 +11,6 @@ import java.util.Set;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.Leg;
@@ -274,11 +273,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass) {
return null;
}
@Override
public void addTrackedRace(TrackedRace trackedRace) {
}
@@ -333,6 +327,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public void unlockTrackedRacesAfterWrite() {
}
@Override
public void removeRaceListener(RaceListener listener) {
}
};
}
@@ -25,7 +25,7 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/470.png")
ImageResource _470Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png")
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.jpg")
ImageResource _49erFXIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png")
@@ -74,7 +74,9 @@ public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog
}
regattaDefinesDiscardsLabel.setVisible(true);
} else {
discardThresholdBoxes.getWidget().setVisible(true);
if (discardThresholdBoxes != null) {
discardThresholdBoxes.getWidget().setVisible(true);
}
regattaDefinesDiscardsLabel.setVisible(false);
}
}
@@ -23,7 +23,7 @@ public class RegattaLeaderboardEditDialog extends RegattaLeaderboardDialog {
regattaListBox = createSortedRegattaListBox(existingRegattas, leaderboardDescriptor.getRegattaName());
regattaListBox.setEnabled(false);
if (leaderboardDescriptor.getDiscardThresholds() != null) {
if (!getSelectedRegatta().definesSeriesDiscardThresholds()) {
discardThresholdBoxes = new DiscardThresholdBoxes(this, leaderboardDescriptor.getDiscardThresholds(), stringMessages);
} // else, the regatta leaderboard obtains its result discarding rule implicitly from the underlying regatta
adjustVisibilityOfResultDiscardingRuleComponent();
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

@@ -17,6 +17,7 @@ public class RegattaJsonSerializer implements JsonSerializer<Regatta> {
public static final String FIELD_SERIES = "series";
public static final String FIELD_COMPETITORS = "competitors";
public static final String FIELD_TRACKED_RACES = "trackedRaces";
public static final String FIELD_COURSE_AREA_ID = "courseAreaId";
private final JsonSerializer<Series> seriesSerializer;
private final JsonSerializer<Competitor> competitorSerializer;
@@ -38,6 +39,7 @@ public class RegattaJsonSerializer implements JsonSerializer<Regatta> {
result.put(FIELD_END_DATE, regatta.getEndDate() != null ? regatta.getEndDate().asMillis() : null);
result.put(FIELD_SCORINGSYSTEM, regatta.getScoringScheme().getType().name());
result.put(FIELD_BOATCLASS, regatta.getBoatClass() != null ? regatta.getBoatClass().getName(): null);
result.put(FIELD_COURSE_AREA_ID, regatta.getDefaultCourseArea() != null ? regatta.getDefaultCourseArea().getId().toString(): null);
if(seriesSerializer != null) {
JSONArray seriesJson = new JSONArray();
@@ -287,14 +287,10 @@ public class RegattaAndRaceDataJsonGetServlet extends AbstractJsonHttpServlet {
jsonCompetitorInLeg.put("distanceTraveledOverGroundInMeters",
distanceTraveled.getMeters());
}
try {
Speed velocityMadeGood = trackedLegOfCompetitor.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
if (velocityMadeGood != null) {
jsonCompetitorInLeg.put("velocityMadeGoodInKnots", velocityMadeGood.getKnots());
jsonCompetitorInLeg.put("velocityMadeGoodInMetersPerSecond", velocityMadeGood.getMetersPerSecond());
}
} catch (NoWindException e) {
// well, we don't know the wind direction... then no VMG will be shown...
Speed velocityMadeGood = trackedLegOfCompetitor.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
if (velocityMadeGood != null) {
jsonCompetitorInLeg.put("velocityMadeGoodInKnots", velocityMadeGood.getKnots());
jsonCompetitorInLeg.put("velocityMadeGoodInMetersPerSecond", velocityMadeGood.getMetersPerSecond());
}
Speed averageVelocityMadeGood = trackedLegOfCompetitor
.getAverageVelocityMadeGood(timePoint);
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.pde.ui.EquinoxLauncher">
<booleanAttribute key="append.args" value="true"/>
<booleanAttribute key="automaticAdd" value="false"/>
<booleanAttribute key="automaticValidate" value="false"/>
<stringAttribute key="bootstrap" value=""/>
<stringAttribute key="checked" value="[NONE]"/>
<booleanAttribute key="clearConfig" value="false"/>
<stringAttribute key="configLocation" value="${workspace_loc}/target"/>
<booleanAttribute key="default" value="true"/>
<booleanAttribute key="default_auto_start" value="true"/>
<intAttribute key="default_start_level" value="4"/>
<booleanAttribute key="includeOptional" value="true"/>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djetty.port=8889 -Dreplication.exchangeName=sapsailinganalytics-replica -Declipse.ignoreApp=true -Dosgi.noShutdown=true&#13;&#10;-Dexpedition.udp.port=2011 -Xmx6000m -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -XX:+UseMembar -Dmongo.dbName=replica -Dreplicate.on.start=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl,com.sap.sse.mail.impl.MailServiceImpl -Dreplicate.master.queue.host=127.0.0.1 -Dreplicate.master.queue.port=5672 -Dreplicate.master.servlet.port=8888 -Dreplicate.master.servlet.host=127.0.0.1"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
<stringAttribute key="pde.version" value="3.3"/>
<booleanAttribute key="show_selected_only" value="false"/>
<stringAttribute key="target_bundles" value="com.fasterxml.jackson.core.jackson-annotations@default:default,com.fasterxml.jackson.core.jackson-core@default:default,com.fasterxml.jackson.core.jackson-databind@default:default,com.fasterxml.jackson.jaxrs.jackson-jaxrs-base@default:default,com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider@default:default,com.rabbitmq.client@default:default,com.sun.jersey.contribs.jersey-multipart@default:default,com.sun.jersey@default:default,com.sun.mail.javax.mail@default:default,jackson-core-lgpl@default:default,jackson-jaxrs@default:default,jackson-mapper-lgpl@default:default,javax.servlet@default:default,javax.validation@default:default,javax.ws.rs@default:default,jcl.over.slf4j@default:default,org.apache.commons.beanutils@default:default,org.apache.commons.codec@default:default,org.apache.commons.collections@default:default,org.apache.commons.fileupload@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.commons.math@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.gogo.shell@default:default,org.apache.geronimo.specs.geronimo-annotation_1.0_spec@default:default,org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec@default:default,org.apache.geronimo.specs.geronimo-jms_1.1_spec@default:default,org.apache.httpcomponents.httpclient@default:default,org.apache.httpcomponents.httpcore@default:default,org.apache.servicemix.bundles.aws-java-sdk@default:default,org.apache.servicemix.bundles.ehcache@default:default,org.apache.servicemix.bundles.scribe@default:default,org.apache.servicemix.bundles.zxing@default:default,org.apache.shiro.core@default:default,org.apache.shiro.ehcache@default:default,org.apache.shiro.web@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.console@default:default,org.eclipse.jetty.continuation@default:default,org.eclipse.jetty.deploy@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.jmx@default:default,org.eclipse.jetty.osgi.boot@3:default,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.jetty.webapp@default:default,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.websocket.server@default:default,org.eclipse.jetty.websocket.servlet@default:default,org.eclipse.jetty.xml@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.jdom@default:default,org.jvnet.mimepull@default:default,org.mongodb.mongo-java-driver@default:default,org.objectweb.asm@default:default,rome@default:default,routeconverter@default:default,slf4j.api@default:default,slf4j.jdk14@default:false"/>
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfigArea" value="false"/>
<stringAttribute key="workspace_bundles" value="com.google.gwt.osgi@default:default,com.googlecode.java-diff-utils@default:default,com.googlecode.mgwt@default:default,com.sap.sailing.barbados.resultimport@default:default,com.sap.sailing.dashboards.gwt@5:true,com.sap.sailing.datamining.shared@default:default,com.sap.sailing.datamining@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.deckmanadapter@4:true,com.sap.sailing.domain.igtimiadapter.persistence@default:default,com.sap.sailing.domain.igtimiadapter@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.racelogtrackingadapter@3:default,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@4:true,com.sap.sailing.domain.swisstimingreplayadapter@4:true,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@4:true,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.gwt.ui@5:true,com.sap.sailing.kiworesultimport@default:default,com.sap.sailing.manage2sail.resultimport@default:default,com.sap.sailing.manage2sail@default:default,com.sap.sailing.news@default:default,com.sap.sailing.nmeaconnector@default:default,com.sap.sailing.polars@4:true,com.sap.sailing.resultimport@default:default,com.sap.sailing.sailwave.resultimport@default:default,com.sap.sailing.server.gateway.ess40@4:true,com.sap.sailing.server.gateway.serialization.shared.android@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.server.gateway@5:true,com.sap.sailing.server.trackfiles@default:default,com.sap.sailing.server@default:default,com.sap.sailing.simulator@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@default:default,com.sap.sailing.www.events@4:true,com.sap.sailing.www@4:true,com.sap.sailing.xrr.resultimport@default:default,com.sap.sailing.xrr.schema@default:default,com.sap.sailing.xrr.structureimport@default:default,com.sap.sse.common@default:default,com.sap.sse.datamining.shared@default:default,com.sap.sse.datamining@default:default,com.sap.sse.filestorage@default:default,com.sap.sse.gwt.adminconsole@default:default,com.sap.sse.gwt.theme@default:default,com.sap.sse.gwt@default:default,com.sap.sse.mail@4:true,com.sap.sse.mongodb@default:default,com.sap.sse.operationaltransformation@default:default,com.sap.sse.replication@4:true,com.sap.sse.security.common@default:default,com.sap.sse.security.ui@6:true,com.sap.sse.security.userstore.mongodb@4:true,com.sap.sse.security@5:true,com.sap.sse.shared.android@default:default,com.sap.sse@default:default,com.tractrac.clientmodule@default:default,net.sf.marineapi@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
<stringAttribute key="yk-options" value="&#13;&#10;additional-options2=onexit\=snapshot&#13;&#10;"/>
</launchConfiguration>
@@ -30,14 +30,24 @@ public interface ReplicationService {
*/
ReplicationMasterDescriptor getReplicatingFromMaster();
/**
* Same as {@link #startToReplicateFrom(ReplicationMasterDescriptor, Iterable)}, where the list of replicables
* is determined by asking this service's {@link ReplicablesProvider} which replicables are currently available.
* Note that this is not a good idea during the OSGi start-up phase, particularly if there is a bundle that
* starts after this bundle because then that {@link Replicable} will not yet be registered and won't become part
* of replication.
*/
void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException;
/**
* Performs a servlet request to the remote master server's {@link ReplicationServlet}, first registering this
* replica, ensuring the message queue for replicating operations is created, then subscribing to the master's
* replication message queue and asking the servlet for the stream containing the initial load which will then be
* used to {@link Replicable#initiallyFillFrom(java.io.ObjectInputStream) replace} the current {@link Replicable}'s
* used to {@link Replicable#initiallyFillFrom(java.io.ObjectInputStream) replace} the {@link Replicable replicables}'
* state.
*/
void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException;
void startToReplicateFrom(ReplicationMasterDescriptor master, Iterable<Replicable<?, ?>> replicables) throws IOException,
ClassNotFoundException, InterruptedException;
/**
* Registers a replica with this master instance. The <code>replica</code> will be considered in the result of
@@ -1,12 +1,15 @@
package com.sap.sse.replication.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sse.replication.Replicable;
import com.sap.sse.replication.ReplicablesProvider;
import com.sap.sse.replication.ReplicationService;
@@ -129,12 +132,16 @@ public class Activator implements BundleActivator {
new Thread("ServiceTracker waiting for Replicables "+Arrays.toString(replicableIdsAsStrings)) {
@Override
public void run() {
logger.info("Waiting for Replicables " + replicableIdsAsStrings + " before firing up replication automatically...");
logger.info("Waiting for Replicables " + Arrays.toString(replicableIdsAsStrings) +
" before firing up replication automatically...");
final List<Replicable<?, ?>> replicables = new ArrayList<>();
for (String replicableIdAsString : replicableIdsAsStrings) {
replicablesProvider.getReplicable(replicableIdAsString, /* wait */true);
Replicable<?, ?> replicable = replicablesProvider.getReplicable(replicableIdAsString, /* wait */true);
logger.info("Obtained Replicable " + replicableIdAsString);
replicables.add(replicable);
}
logger.info("Configuration requested automatic replication. Starting it up...");
logger.info("Configuration requested automatic replication for replicables "+
Arrays.toString(replicableIdsAsStrings)+". Starting it up...");
String replicateFromExchangeName = System.getProperty(PROPERTY_NAME_REPLICATE_MASTER_EXCHANGE_NAME);
if (replicateFromExchangeName == null) {
replicateFromExchangeName = masterExchangeName;
@@ -147,8 +154,8 @@ public class Activator implements BundleActivator {
System.getProperty(PROPERTY_NAME_REPLICATE_MASTER_SERVLET_HOST),
Integer.valueOf(System.getProperty(PROPERTY_NAME_REPLICATE_MASTER_SERVLET_PORT).trim()));
try {
// TODO pass on the replicables to wait for
serverReplicationMasterService.startToReplicateFrom(master);
// TODO pass on the replicables to wait for; failure to do so can cause the symptoms of bug 3015
serverReplicationMasterService.startToReplicateFrom(master, replicables);
logger.info("Automatic replication has been started.");
} catch (ClassNotFoundException | IOException | InterruptedException e) {
e.printStackTrace();
@@ -476,7 +476,14 @@ public class ReplicationServiceImpl implements ReplicationService {
*/
@Override
public void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException {
logger.info("Starting to replicate from "+master);
final Iterable<Replicable<?, ?>> replicables = getReplicables();
startToReplicateFrom(master, replicables);
}
@Override
public void startToReplicateFrom(ReplicationMasterDescriptor master, Iterable<Replicable<?, ?>> replicables)
throws IOException, ClassNotFoundException, InterruptedException {
logger.info("Starting to replicate from " + master);
try {
registerReplicaWithMaster(master);
} catch (Exception ex) {
@@ -497,14 +504,13 @@ public class ReplicationServiceImpl implements ReplicationService {
throw ex;
}
logger.info("Connection to exchange successful.");
final Iterable<Replicable<?, ?>> replicables = replicablesProvider.getReplicables();
URL initialLoadURL = master.getInitialLoadURL(replicables);
logger.info("Initial load URL is "+initialLoadURL);
// start receiving messages already now, but start in suspended mode
replicator = new ReplicationReceiver(master, replicablesProvider, /* startSuspended */true, consumer);
// clear Replicable state here, before starting to receive and de-serialized operations which builds up
// new state, e.g., in competitor store
for (Replicable<?, ?> r : getReplicables()) {
for (Replicable<?, ?> r : replicables) {
r.clearReplicaState();
r.startedReplicatingFrom(master);
}
@@ -517,7 +523,7 @@ public class ReplicationServiceImpl implements ReplicationService {
RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName);
try {
final GZIPInputStream gzipInputStream = new GZIPInputStream(rabbitInputStreamProvider.getInputStream());
for (Replicable<?, ?> replicable : getReplicables()) {
for (Replicable<?, ?> replicable : replicables) { // absolutely make sure to use the same sequence of replicables as for URL (bug 3015)
logger.info("Starting to receive initial load for "+replicable.getId());
try {
replicable.initiallyFillFrom(gzipInputStream);
@@ -527,9 +533,9 @@ public class ReplicationServiceImpl implements ReplicationService {
}
logger.info("Done receiving initial load for "+replicable.getId());
}
} finally {
logger.info("Resuming replicator to apply queues");
replicator.setSuspended(false); // apply queued operations
} finally {
// delete initial load queue
DeleteOk deleteOk = consumer.getChannel().queueDelete(queueName);
logger.info("Deleted queue " + queueName + " used for initial load: " + deleteOk.toString());
+6 -2
View File
@@ -246,12 +246,16 @@ if [[ $OPERATION == "auto-install" ]]; then
activate_user_data
# then download and install environment and append to env.sh
install_environment
# finally, append user data to env.sh as it shall take precedence over the installed environment's defaults
append_user_data_to_envsh
# finally, append user data to env.sh as it shall take precedence over the installed environment's defaults
append_user_data_to_envsh
# make sure to reload data
source `pwd`/env.sh
# Append Apache macro invocation for /internal-server-status based on mod_status and INSTANCE_DNS to /etc/httpd/conf.d/001-events.conf
APACHE_CONFIG_DIR=/etc/httpd/conf.d
echo "Appending macro usage for /internal-server-status URL for mod_status based Apache monitoring to $APACHE_CONFIG_DIR/001-events.conf"
echo "Use Status $INSTANCE_DNS internal-server-status" >>$APACHE_CONFIG_DIR/001-events.conf
if [[ $INSTALL_FROM_RELEASE == "" ]] && [[ $BUILD_BEFORE_START != "True" ]]; then
echo "I could not find any option telling me to download a release or to build! Possible cause: Your environment contains empty values for these variables!"
@@ -40,13 +40,12 @@
<Button
android:id="@+id/set_date_time"
style="?attr/buttonSolidXSmall"
style="?attr/buttonSolidSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/picker"
android:layout_margin="@dimen/default_margin_twice"
android:maxWidth="200dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/default_margin_twice"
android:text="@string/race_set_time"/>
<TextView
@@ -331,7 +331,7 @@
<string name="race_begin_finish_custom">Angegebene Zeit verwenden</string>
<string name="wind_sensor"><![CDATA[um #AT# / von #FROM#° / #SPEED# kn]]></string>
<string name="wind_no_data">Keine Daten</string>
<string name="wind_panel"><![CDATA[von #FROM#°\n#SPEED# kn]]></string>
<string name="wind_panel"><![CDATA[von %1$.0f°\n%2$.1f kn]]></string>
<string name="settings_category_developer">Entwickler Optionen</string>
<string name="preference_offline_header">Offline Modus</string>
<string name="preference_offline_summary">Deaktiviert die Verbindung zum Server und benutzt statische Daten</string>
@@ -405,7 +405,7 @@
<string name="theme_changed_message">Die Änderung am Theme wird nach einem Neustart der App wirksam.</string>
<string name="url_refresh_title">Die Server URL wurde geändert</string>
<string name="url_refresh_message">Bitte melden Sie sich vom aktuellen Event ab, um die Daten des neuen Servers zu laden.</string>
<string name="until_xray_removed">XRAY wird eingeholt in #TIME#</string>
<string name="until_xray_removed">XRAY wird eingeholt in %s</string>
<string name="send_photos">Fotos senden</string>
<string name="error_wrong_degree_value_title">Fehler</string>
<string name="error_wrong_degree_value_message">Bitte geben Sie einen Wert zwischen 0 und 360 an.</string>
@@ -323,7 +323,7 @@
<string name="wind_kn">kn</string>
<string name="wind_address_hint">Enter Address</string>
<string name="wind_sensor"><![CDATA[at #AT# / from #FROM#° / #SPEED# kn]]></string>
<string name="wind_panel"><![CDATA[from #FROM#°\n#SPEED# kn]]></string>
<string name="wind_panel"><![CDATA[from %1$.0f°\n%2$.1f kn]]></string>
<string name="wind_no_data">No Data</string>
<string name="time_up">Up in #TIME#</string>
@@ -426,7 +426,7 @@
<string name="theme_changed_message">Changes to the theme will take action after an app restart.</string>
<string name="url_refresh_title">Server URL Changed</string>
<string name="url_refresh_message">Please logout from the current event to load the data from the new server.</string>
<string name="until_xray_removed">XRAY will be removed in #TIME#</string>
<string name="until_xray_removed">XRAY will be removed in %s</string>
<string name="send_photos">Send Photos</string>
<string name="error_wrong_degree_value_title">Error</string>
<string name="error_wrong_degree_value_message">Please provide a value between 0 and 360</string>
@@ -1,5 +1,6 @@
package com.sap.sailing.racecommittee.app.services;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -8,6 +9,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Pair;
@@ -259,11 +261,16 @@ public class RaceStateService extends Service {
intent.putExtra(AppConstants.EXTRAS_RACE_STATE_EVENT, event);
return PendingIntent.getService(this, alarmManagerRequestCode++, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public void setAlarm(ManagedRace race, RaceStateEvent event) {
PendingIntent intent = createAlarmPendingIntent(race, event);
managedIntents.get(race.getId()).add(Pair.create(intent, event.getEventName()));
alarmManager.set(AlarmManager.RTC_WAKEUP, event.getTimePoint().asMillis(), intent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, event.getTimePoint().asMillis(), intent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, event.getTimePoint().asMillis(), intent);
}
ExLog.i(this, TAG, "The alarm " + event.getEventName() + " will be fired at " + event.getTimePoint());
}
@@ -1,15 +1,10 @@
package com.sap.sailing.racecommittee.app.ui.adapters.racelist;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -19,10 +14,11 @@ import android.widget.ArrayAdapter;
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.ViewHolder;
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;
@@ -32,11 +28,17 @@ import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceFilter.FilterS
import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources;
import com.sap.sailing.racecommittee.app.utils.BitmapHelper;
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.impl.MillisecondsTimePoint;
import java.text.SimpleDateFormat;
import java.util.List;
public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> implements FilterSubscriber {
private final static String TAG = ManagedRaceListAdapter.class.getName();
private final static int FLAG_SIZE = 48;
private final Object mLockObject = new Object();
private List<RaceListDataType> mAllViewItems;
private RaceFilter mFilter;
@@ -71,15 +73,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
dateFormat = new SimpleDateFormat("HH:mm", getContext().getResources().getConfiguration().locale);
}
public String fill2(int value) {
String erg = String.valueOf(value);
if (erg.length() < 2) {
erg = "0" + erg;
}
return erg;
}
@Override
public int getCount() {
synchronized (mLockObject) {
@@ -87,46 +80,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
}
}
public String getDuration(Date date1, Date date2) {
TimeUnit timeUnit = TimeUnit.SECONDS;
long diffInMilli = date2.getTime() - date1.getTime();
long s = timeUnit.convert(diffInMilli, TimeUnit.MILLISECONDS);
long days = s / (24 * 60 * 60);
long rest = s - (days * 24 * 60 * 60);
long hrs = rest / (60 * 60);
long rest1 = rest - (hrs * 60 * 60);
long min = rest1 / 60;
long sec = s % 60;
String dates = "";
if (days < 0 || hrs < 0 || min < 0 || sec < 0) {
dates += "-";
if (days < 0) {
days *= -1;
}
if (hrs < 0) {
hrs *= -1;
}
if (min < 0) {
min *= -1;
}
if (sec < 0) {
sec *= -1;
}
}
if (days != 0) {
dates = days + ":";
}
dates += (days != 0 || hrs != 0) ? fill2((int) hrs) + ":" : "";
dates += fill2((int) min) + ":";
dates += fill2((int) sec);
return dates;
}
@Override
public RaceFilter getFilter() {
if (mFilter == null) {
@@ -157,6 +110,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
raceListElement = getItem(position);
int type = getItemViewType(position);
TimePoint now = MillisecondsTimePoint.now();
if (convertView == null) {
if (type == ViewType.HEADER.index) {
@@ -186,7 +140,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
fleetSeries += header.getSeries().getName();
}
fleet_series.setText(fleetSeries);
protest_image.setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), 48));
protest_image.setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), FLAG_SIZE));
protest_image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -220,7 +174,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
String startTime = mResources.getString(startRes, dateFormat.format(state.getStartTime().asDate()));
race_started.setText(startTime);
if (state.getFinishedTime() == null) {
String duration = getDuration(state.getStartTime().asDate(), Calendar.getInstance().getTime());
String duration = TimeUtils.formatDuration(now, state.getStartTime());
time.setText(duration);
float textSize = getContext().getResources().getDimension(R.dimen.textSize_40);
if (!TextUtils.isEmpty(duration) && duration.length() >= 6) {
@@ -248,7 +202,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
update_badge.setVisibility(View.VISIBLE);
}
updateFlag(race.getRace());
updateFlag(race.getRace(), now);
}
return convertView;
}
@@ -323,32 +277,86 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
}
}
private void updateFlag(ManagedRace race) {
private void updateFlag(ManagedRace race, TimePoint now) {
RaceState state = race.getState();
if (state == null || state.getStartTime() == null) {
return;
}
FlagPoleState flagPoleState = state.getTypedRacingProcedure()
.getActiveFlags(state.getStartTime(), MillisecondsTimePoint.now());
List<FlagPole> flagChanges = flagPoleState.computeUpcomingChanges();
if (!flagChanges.isEmpty()) {
TimePoint changeAt = flagPoleState.getNextStateValidFrom();
FlagPole changePole = FlagPoleState.getMostInterestingFlagPole(flagChanges);
if (changeAt != null) {
current_flag.setImageDrawable(
FlagsResources.getFlagDrawable(getContext(), changePole.getUpperFlag().name(), 48));
String text = getDuration(changeAt.asDate(), Calendar.getInstance().getTime());
flag_timer.setText(text.replace("-", ""));
Drawable arrow;
if (changePole.isDisplayed()) {
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up);
} else {
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down);
RacingProcedure procedure = state.getTypedRacingProcedure();
LayerDrawable flag = null;
Drawable arrow = null;
String timer = null;
if (!procedure.isIndividualRecallDisplayed()) {
FlagPoleState poleState = state.getRacingProcedure().getActiveFlags(state.getStartTime(), now);
List<FlagPole> currentState = poleState.getCurrentState();
List<FlagPole> upcoming = poleState.computeUpcomingChanges();
FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming);
TimePoint change = poleState.getNextStateValidFrom();
Flags currentFlag;
if (change != null) {
for (FlagPole pole : currentState) {
int isNext = 0;
currentFlag = pole.getUpperFlag();
if (isNextFlag(currentFlag, nextPole)) {
isNext = 1;
} else {
currentFlag = pole.getLowerFlag();
if (!Flags.NONE.equals(currentFlag)) {
if (isNextFlag(currentFlag, nextPole)) {
isNext = 2;
}
}
}
if (isNext != 0) {
flag = FlagsResources.getFlagDrawable(getContext(), currentFlag.name(), FLAG_SIZE);
switch (isNext) {
case 1:
if (nextPole.isDisplayed()) {
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up);
} else {
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down);
}
break;
case 2:
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up);
break;
default:
ExLog.i(getContext(), TAG, "unknown flag");
}
timer = TimeUtils.formatDuration(now, poleState.getNextStateValidFrom());
}
}
arrow_direction.setImageDrawable(arrow);
race_flag.setVisibility(View.VISIBLE);
}
} else {
TimePoint flagDown = procedure.getIndividualRecallRemovalTime();
if (now.before(flagDown)) {
flag = FlagsResources.getFlagDrawable(getContext(), Flags.XRAY.name(), FLAG_SIZE);
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down);
timer = TimeUtils.formatDuration(now, flagDown);
}
}
if (timer != null) {
timer = timer.replace("-", "");
}
showFlag(flag, arrow, timer);
}
private boolean isNextFlag(Flags flag, FlagPole pole) {
return pole != null && flag.equals(pole.getUpperFlag());
}
private void showFlag(LayerDrawable flag, Drawable arrow, String timer) {
if (flag != null && arrow != null && timer != null) {
current_flag.setImageDrawable(flag);
arrow_direction.setImageDrawable(arrow);
flag_timer.setText(timer);
race_flag.setVisibility(View.VISIBLE);
}
}
@@ -1,5 +1,13 @@
package com.sap.sailing.racecommittee.app.ui.fragments;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -20,10 +28,16 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.*;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.sap.sailing.android.shared.logging.ExLog;
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener;
@@ -41,16 +55,16 @@ 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.RaceListDataTypeHeader;
import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace;
import com.sap.sailing.racecommittee.app.ui.comparators.NaturalNamedComparator;
import com.sap.sailing.racecommittee.app.ui.comparators.RaceListDataTypeComparator;
import com.sap.sailing.racecommittee.app.ui.comparators.RegattaSeriesFleetComparator;
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.ProtestTimeDialogFragment;
import com.sap.sailing.racecommittee.app.utils.*;
import com.sap.sailing.racecommittee.app.utils.BitmapHelper;
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 java.io.Serializable;
import java.util.*;
public class RaceListFragment
extends LoggableFragment
implements OnItemClickListener, OnItemSelectedListener, TickListener, OnScrollListener {
@@ -67,7 +81,7 @@ public class RaceListFragment
private ActionBarDrawerToggle mDrawerToggle;
private FilterMode mFilterMode;
private ListView mListView;
private HashMap<Serializable, ManagedRace> mManagedRacesById;
private LinkedHashMap<String, ManagedRace> mManagedRacesById;
private TreeMap<RaceGroupSeriesFleet, List<ManagedRace>> mRacesByGroup;
private ManagedRace mSelectedRace;
private IntentReceiver mReceiver;
@@ -93,7 +107,7 @@ public class RaceListFragment
public RaceListFragment() {
mFilterMode = FilterMode.ACTIVE;
mSelectedRace = null;
mManagedRacesById = new HashMap<>();
mManagedRacesById = new LinkedHashMap<>();
mRacesByGroup = new TreeMap<>(new RegattaSeriesFleetComparator());
mViewItems = new ArrayList<>();
}
@@ -420,7 +434,6 @@ public class RaceListFragment
mViewItems.add(new RaceListDataTypeHeader(key));
List<ManagedRace> races = mRacesByGroup.get(key);
Collections.sort(races, new NaturalNamedComparator());
for (ManagedRace race : races) {
// ... and add the race view!
mViewItems.add(new RaceListDataTypeRace(race));
@@ -87,9 +87,7 @@ public class ByNameCourseDesignDialog extends RaceDialogFragment {
protected void onCourseSelectionButtonClicked(View v) {
Button selectedButton = (Button) v;
String internalCourseName = (String) selectedButton.getText();
CourseBase courseLayout = new CourseDataImpl(internalCourseName);
getRaceState().setCourseDesign(MillisecondsTimePoint.now(), courseLayout);
dismiss();
}
@@ -154,9 +154,7 @@ public class SetupPanelFragment extends BasePanelFragment {
Wind wind = getRaceState().getWindFix();
if (mWindValue != null && wind != null) {
String sensorData = getString(R.string.wind_panel);
sensorData = sensorData.replace("#FROM#", String.format("%.0f", wind.getFrom().getDegrees()));
sensorData = sensorData.replace("#SPEED#", String.format("%.1f", wind.getKnots()));
String sensorData = getString(R.string.wind_panel, wind.getFrom().getDegrees(), wind.getKnots());
mWindValue.setText(sensorData);
}
}
@@ -9,6 +9,8 @@ import android.widget.*;
import com.sap.sailing.android.shared.util.ViewHolder;
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.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure;
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.BaseRacingProcedureChangedListener;
import com.sap.sailing.domain.common.racelog.FlagPole;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.racecommittee.app.R;
@@ -32,8 +34,12 @@ public class RaceFlagViewerFragment extends BaseFragment {
private TextView mXrayCountdown;
private Button mXrayButton;
public RaceFlagViewerFragment() {
private ProcedureChangedListener mProcedureListener;
private FlagsCache mFlagCache;
public RaceFlagViewerFragment() {
mProcedureListener = new ProcedureChangedListener();
mFlagCache = null;
}
public static RaceFlagViewerFragment newInstance() {
@@ -69,6 +75,20 @@ public class RaceFlagViewerFragment extends BaseFragment {
return layout;
}
@Override
public void onStart() {
super.onStart();
getRaceState().getRacingProcedure().addChangedListener(mProcedureListener);
}
@Override
public void onStop() {
super.onStop();
getRaceState().getRacingProcedure().removeChangedListener(mProcedureListener);
}
@Override
public void notifyTick(TimePoint now) {
super.notifyTick(now);
@@ -76,7 +96,7 @@ public class RaceFlagViewerFragment extends BaseFragment {
updateFlags(now);
}
private void updateFlags(TimePoint timePoint) {
private void updateFlags(TimePoint now) {
if (getRace() == null || getRaceState() == null || getRaceState().getStartTime() == null) {
return;
}
@@ -87,65 +107,84 @@ public class RaceFlagViewerFragment extends BaseFragment {
if (mRecall != null) {
mRecall.setVisibility(View.GONE);
}
RacingProcedure procedure = getRaceState().getTypedRacingProcedure();
if (!procedure.isIndividualRecallDisplayed()) {
if (mLayout != null) {
mLayout.setVisibility(View.VISIBLE);
}
FlagPoleState poleState = getRaceState().getRacingProcedure().getActiveFlags(getRaceState().getStartTime(), timePoint);
List<FlagPole> currentState = poleState.getCurrentState();
List<FlagPole> upcoming = poleState.computeUpcomingChanges();
FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming);
mLayout.removeAllViews();
int size = 0;
Flags flag;
for (FlagPole flagPole : currentState) {
size++;
flag = flagPole.getUpperFlag();
mLayout.addView(renderFlag(timePoint, poleState, flag, isNextFlag(flag, nextPole), currentState.size() == size, flagPole.isDisplayed(), UPPER_FLAG));
if (!flagPole.getLowerFlag().equals(Flags.NONE)) {
flag = flagPole.getLowerFlag();
mLayout.addView(renderFlag(timePoint, poleState, flag, isNextFlag(flag, nextPole), currentState.size() == size, false, LOWER_FLAG));
if (mFlagCache == null) {
mLayout.removeAllViews();
FlagPoleState poleState = getRaceState().getRacingProcedure().getActiveFlags(getRaceState().getStartTime(), now);
List<FlagPole> currentState = poleState.getCurrentState();
List<FlagPole> upcoming = poleState.computeUpcomingChanges();
FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming);
mFlagCache = new FlagsCache(poleState.getNextStateValidFrom(), nextPole);
int size = 0;
Flags flag;
for (FlagPole flagPole : currentState) {
size++;
flag = flagPole.getUpperFlag();
mLayout.addView(createFlagView(now, poleState, flag, isNextFlag(nextPole, flag), currentState.size() == size, flagPole.isDisplayed(), UPPER_FLAG));
if (!flagPole.getLowerFlag().equals(Flags.NONE)) {
flag = flagPole.getLowerFlag();
mLayout.addView(createFlagView(now, poleState, flag, isNextFlag(nextPole, flag), currentState.size() == size, false, LOWER_FLAG));
}
}
} else {
if (mLayout != null && mFlagCache.nextPole != null) {
for (int i = 0; i < mLayout.getChildCount(); i++) {
ImageView flagImage = ViewHolder.get(mLayout.getChildAt(i), R.id.flag);
Flags flags = (Flags) flagImage.getTag();
if (isNextFlag(mFlagCache.nextPole, flags)) {
updateFlagView(now, mLayout.getChildAt(i));
}
}
}
}
}
} else {
String time = getString(R.string.until_xray_removed);
if (mXrayCountdown != null) {
if (mXrayCountdown != null && mRecall != null) {
TimePoint flagDown = procedure.getIndividualRecallRemovalTime();
if (timePoint.before(flagDown)) {
time = time.replace("#TIME#", TimeUtils.formatDurationUntil(flagDown.minus(timePoint.asMillis()).asMillis()).replace("-", ""));
mXrayCountdown.setText(time);
} else {
procedure.removeIndividualRecall(timePoint);
if (now.before(flagDown)) {
mRecall.setVisibility(View.VISIBLE);
mXrayCountdown.setText(getString(R.string.until_xray_removed, TimeUtils.formatDuration(now, flagDown, false)));
}
}
if (mRecall != null) {
mRecall.setVisibility(View.VISIBLE);
}
}
}
private boolean isNextFlag(Flags flag, FlagPole pole) {
return pole != null && flag.equals(pole.getUpperFlag());
private boolean isNextFlag(FlagPole flagPole, Flags flags) {
return flagPole != null && flags.equals(flagPole.getUpperFlag());
}
private RelativeLayout renderFlag(TimePoint timePoint, FlagPoleState poleState, final Flags flag, boolean isNext, boolean lastEntry, boolean isDisplayed, int flagType) {
private View updateFlagView(TimePoint now, View flagView) {
TimePoint change = mFlagCache.mChange;
if (change != null) {
TextView flag_text = ViewHolder.get(flagView, R.id.flag_text);
String timer = TimeUtils.formatDuration(now, change, false);
flag_text.setText(timer);
}
return flagView;
}
private RelativeLayout createFlagView(TimePoint now, FlagPoleState poleState, final Flags flag, boolean isNext, boolean lastEntry,
boolean isDisplayed, int flagType) {
RelativeLayout layout = (RelativeLayout) getActivity().getLayoutInflater().inflate(R.layout.race_flag, mLayout, false);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
layout.setLayoutParams(layoutParams);
TimePoint changeAt = poleState.getNextStateValidFrom();
ImageView flagView = (ImageView) layout.findViewById(R.id.flag);
TextView textView = (TextView) layout.findViewById(R.id.flag_text);
View downView = layout.findViewById(R.id.arrow_down);
View upView = layout.findViewById(R.id.arrow_up);
View line = layout.findViewById(R.id.line);
ImageView flagView = ViewHolder.get(layout, R.id.flag);
TextView textView = ViewHolder.get(layout, R.id.flag_text);
View downView = ViewHolder.get(layout, R.id.arrow_down);
View upView = ViewHolder.get(layout, R.id.arrow_up);
View line = ViewHolder.get(layout, R.id.line);
if (lastEntry) {
line.setVisibility(View.GONE);
}
flagView.setImageDrawable(FlagsResources.getFlagDrawable(getActivity(), flag.name(), 96));
flagView.setTag(flag);
if (flag == Flags.CLASS && getRace().getFleet().getColor() != null) {
flagView.setBackgroundColor(getFleetColorId());
}
@@ -159,12 +198,7 @@ public class RaceFlagViewerFragment extends BaseFragment {
textView.setText("");
if (changeAt != null && isNext) {
textView.setVisibility(View.VISIBLE);
String timer;
if (changeAt.after(timePoint)) {
timer = TimeUtils.formatDurationUntil(changeAt.minus(timePoint.asMillis()).asMillis());
} else {
timer = TimeUtils.formatDurationSince(timePoint.minus(changeAt.asMillis()).asMillis());
}
String timer = TimeUtils.formatDuration(now, changeAt);
textView.setText(timer.replace("-", ""));
if (flagType == UPPER_FLAG) {
if (isDisplayed) {
@@ -192,7 +226,28 @@ public class RaceFlagViewerFragment extends BaseFragment {
}
private int getFleetColorId() {
Util.Triple<Integer, Integer, Integer> rgb = getRace().getFleet().getColor() == null ? new Util.Triple<>(0, 0, 0) : getRace().getFleet().getColor().getAsRGB();
Util.Triple<Integer, Integer, Integer> rgb =
getRace().getFleet().getColor() == null ? new Util.Triple<>(0, 0, 0) : getRace().getFleet().getColor().getAsRGB();
return Color.rgb(rgb.getA(), rgb.getB(), rgb.getC());
}
private class ProcedureChangedListener extends BaseRacingProcedureChangedListener {
@Override
public void onActiveFlagsChanged(ReadonlyRacingProcedure racingProcedure) {
super.onActiveFlagsChanged(racingProcedure);
mFlagCache = null;
}
}
private class FlagsCache {
private TimePoint mChange;
public FlagPole nextPole;
public FlagsCache(TimePoint change, FlagPole pole) {
mChange = change;
nextPole = pole;
}
}
}
@@ -26,13 +26,16 @@ public class RaceTimeChangeFragment extends BaseFragment implements View.OnClick
public final static int FINISHING_TIME_MODE = 1;
public final static int FINISHED_TIME_MODE = 2;
private final static String START_MODE = "startMode";
private static final int FUTURE_DAYS = 3;
private static final int PAST_DAYS = -3;
private static final int FUTURE_DAYS_DEFAULT = 3;
private static final int PAST_DAYS_DEFAULT = -3;
private NumberPicker mDatePicker;
private TimePicker mTimePicker;
private NumberPicker mSecondPicker;
private int mFutureDays = FUTURE_DAYS_DEFAULT;
private int mPastDays = PAST_DAYS_DEFAULT;
public static RaceTimeChangeFragment newInstance(int mode) {
RaceTimeChangeFragment fragment = new RaceTimeChangeFragment();
Bundle args = new Bundle();
@@ -81,10 +84,37 @@ public class RaceTimeChangeFragment extends BaseFragment implements View.OnClick
mDatePicker = (NumberPicker) layout.findViewById(R.id.date_picker);
if (mDatePicker != null) {
Calendar start = (Calendar) calendar.clone();
Calendar finishing = (Calendar) calendar.clone();
Calendar finished = (Calendar) calendar.clone();
switch (getArguments().getInt(START_MODE, 0)) {
case START_TIME_MODE:
finishing.setTime(getRaceState().getFinishingTime().asDate());
mFutureDays = TimeUtils.daysBetween(finishing, calendar);
break;
case FINISHING_TIME_MODE:
start.setTime(getRaceState().getStartTime().asDate());
mPastDays = TimeUtils.daysBetween(calendar, start);
finished.setTime(getRaceState().getFinishedTime().asDate());
mFutureDays = TimeUtils.daysBetween(finished, calendar);
break;
case FINISHED_TIME_MODE:
finishing.setTime(getRaceState().getFinishingTime().asDate());
mPastDays = TimeUtils.daysBetween(finishing, calendar);
break;
default:
// use default values
}
ThemeHelper.setPickerTextColor(getActivity(), mDatePicker, ThemeHelper.getColor(getActivity(), R.attr.white));
TimeUtils.initDatePicker(getActivity(), mDatePicker, calendar, PAST_DAYS, FUTURE_DAYS, false);
mDatePicker.setValue(Math.abs(PAST_DAYS));
TimeUtils.initDatePicker(getActivity(), mDatePicker, calendar, mPastDays, mFutureDays, false);
mDatePicker.setValue(Math.abs(mPastDays));
mDatePicker.setTag(calendar);
if (mDatePicker.getMinValue() == mDatePicker.getMaxValue()) {
mDatePicker.setVisibility(View.GONE);
}
}
mTimePicker = (TimePicker) layout.findViewById(R.id.time_picker);
@@ -174,7 +204,7 @@ public class RaceTimeChangeFragment extends BaseFragment implements View.OnClick
private TimePoint getPickerTime() {
Calendar calendar = (Calendar) mDatePicker.getTag();
calendar.add(Calendar.DAY_OF_MONTH, mDatePicker.getValue() + PAST_DAYS);
calendar.add(Calendar.DAY_OF_MONTH, mDatePicker.getValue() + mPastDays);
calendar.set(Calendar.HOUR_OF_DAY, mTimePicker.getCurrentHour());
calendar.set(Calendar.MINUTE, mTimePicker.getCurrentMinute());
calendar.set(Calendar.SECOND, mSecondPicker.getValue());
@@ -86,6 +86,9 @@ public class CompassView extends RelativeLayout {
return false;
}
});
setFocusable(true);
setFocusableInTouchMode(true);
}
private void generateAndShowAlert() {
@@ -166,7 +169,7 @@ public class CompassView extends RelativeLayout {
if (Math.abs(degree) == 360) {
degree = 0;
}
degreeView.clearFocus();
requestFocus();
changeListener.onDirectionChanged(degree);
}
}
@@ -1,166 +1,190 @@
package com.sap.sailing.racecommittee.app.utils;
import android.content.Context;
import android.os.Build;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.TextView;
import com.sap.sailing.racecommittee.app.R;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import java.util.ArrayList;
import java.util.Calendar;
public class TimeUtils {
private TimeUtils() {
// only static methods
}
public static long timeUntil(TimePoint targetTime) {
return targetTime.asMillis() - MillisecondsTimePoint.now().asMillis();
}
/**
* Formats your time to 'kk:mm:ss'.
*
* @param timePoint timestamp to format
*/
public static String formatTime(TimePoint timePoint) {
return formatTime(timePoint, "kk:mm:ss");
}
/**
* Formats your time with the help of {@link DateFormat}.
*
* @param timePoint timestamp to format
* @param format format as defined by {@link DateFormat}
* @return timestamp formatted as {@link String}
*/
public static String formatTime(TimePoint timePoint, String format) {
return DateFormat.format(format, timePoint.asDate()).toString();
}
public static String formatDurationSince(long milliseconds) {
int secondsTillStart = (int) Math.floor(milliseconds / 1000f);
return formatDuration(secondsTillStart);
}
public static String formatDurationUntil(long milliseconds) {
int secondsTillStart = (int) Math.ceil(milliseconds / 1000f);
return formatDuration(secondsTillStart);
}
public static String calcDuration(Calendar from, Calendar to) {
String retValue;
long millis = to.getTimeInMillis() - from.getTimeInMillis();
long min = millis / (1000 * 60);
long sec = (millis - (min * 60 * 1000)) / 1000;
retValue = String.valueOf(sec) + "\"";
if (retValue.length() == 2) {
retValue = "0" + retValue;
}
if (min > 0) {
retValue = String.valueOf(min) + "' " + retValue;
}
return retValue;
}
public static Calendar floorTime(Calendar calendar) {
if (calendar == null) {
calendar = Calendar.getInstance();
}
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
public static int daysBetween(Calendar day1, Calendar day2) {
Calendar dayOne = (Calendar) day1.clone();
Calendar dayTwo = (Calendar) day2.clone();
if (dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR)) {
return dayOne.get(Calendar.DAY_OF_YEAR) - dayTwo.get(Calendar.DAY_OF_YEAR);
} else {
if (dayTwo.get(Calendar.YEAR) > dayOne.get(Calendar.YEAR)) {
//swap them
Calendar temp = dayOne;
dayOne = dayTwo;
dayTwo = temp;
}
int extraDays = 0;
while (dayOne.get(Calendar.YEAR) > dayTwo.get(Calendar.YEAR)) {
dayOne.add(Calendar.YEAR, -1);
// getActualMaximum() important for leap years
extraDays += dayOne.getActualMaximum(Calendar.DAY_OF_YEAR);
}
return extraDays - dayTwo.get(Calendar.DAY_OF_YEAR) + dayOne.get(Calendar.DAY_OF_YEAR);
}
}
private static String formatDuration(int secondsTillStart) {
int hours = secondsTillStart / 3600;
int minutes = (secondsTillStart % 3600) / 60;
int seconds = (secondsTillStart % 60);
boolean negative = (hours < 0 || minutes < 0 || seconds < 0);
String timePattern = ((negative) ? "-" : "") + "%s:%s:%s";
String secondsString = seconds < 10 ? "0" + Math.abs(seconds) : "" + Math.abs(seconds);
String minutesString = minutes < 10 ? "0" + Math.abs(minutes) : "" + Math.abs(minutes);
String hoursString = hours < 10 ? "0" + Math.abs(hours) : "" + Math.abs(hours);
return String.format(timePattern, hoursString, minutesString, secondsString);
}
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays) {
initDatePicker(context, datePicker, time, pastDays, futureDays, true);
}
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays, boolean useWords) {
java.text.DateFormat dateFormat = java.text.DateFormat.getDateInstance();
ArrayList<String> dates = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(time.getTime());
// Past
calendar.add(Calendar.DAY_OF_MONTH, pastDays);
for (int i = pastDays; i < -1; i++) {
dates.add(dateFormat.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
if (useWords) {
dates.add(context.getString(R.string.yesterday));
dates.add(context.getString(R.string.today));
dates.add(context.getString(R.string.tomorrow));
calendar.add(Calendar.DAY_OF_MONTH, 3);
} else {
for (int i = 0; i < 3; i++) {
dates.add(dateFormat.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
}
// Future
for (int i = 3; i <= futureDays + 1; i++) {
dates.add(dateFormat.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
datePicker.setDisplayedValues(dates.toArray(new String[dates.size()]));
datePicker.setMinValue(0);
datePicker.setMaxValue(dates.size() - 1);
datePicker.setWrapSelectorWheel(false);
datePicker.setValue(TimeUtils.daysBetween(calendar, Calendar.getInstance()) + Math.abs(pastDays));
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
TextView date = new TextView(context);
date.setText(dateFormat.format(calendar.getTime()));
int padding = context.getResources().getDimensionPixelSize(R.dimen.default_padding_half);
date.setPadding(padding, padding, padding, padding);
date.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
datePicker.getLayoutParams().width = date.getMeasuredWidth();
}
}
package com.sap.sailing.racecommittee.app.utils;
import android.content.Context;
import android.os.Build;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.TextView;
import com.sap.sailing.racecommittee.app.R;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import java.util.ArrayList;
import java.util.Calendar;
public class TimeUtils {
private TimeUtils() {
// only static methods
}
public static long timeUntil(TimePoint targetTime) {
return targetTime.asMillis() - MillisecondsTimePoint.now().asMillis();
}
/**
* Formats your time to 'kk:mm:ss'.
*
* @param timePoint timestamp to format
*/
public static String formatTime(TimePoint timePoint) {
return formatTime(timePoint, "kk:mm:ss");
}
/**
* Formats your time with the help of {@link DateFormat}.
*
* @param timePoint timestamp to format
* @param format format as defined by {@link DateFormat}
* @return timestamp formatted as {@link String}
*/
public static String formatTime(TimePoint timePoint, String format) {
return DateFormat.format(format, timePoint.asDate()).toString();
}
public static String formatDuration(TimePoint now, TimePoint timePoint) {
return formatDuration(now, timePoint, true);
}
public static String formatDuration(TimePoint now, TimePoint timePoint, boolean inclSign) {
String duration;
if (timePoint.after(now)) {
String sign = (inclSign) ? "-" : "";
duration = sign + TimeUtils.formatDurationUntil(timePoint.minus(now.asMillis()).asMillis(), false);
} else {
duration = TimeUtils.formatDurationSince(now.minus(timePoint.asMillis()).asMillis(), false);
}
return duration;
}
public static String formatDurationSince(long milliseconds) {
return formatDurationSince(milliseconds, true);
}
public static String formatDurationSince(long milliseconds, boolean emptyHours) {
int secondsTillStart = (int) Math.floor(milliseconds / 1000f);
return formatDuration(secondsTillStart, emptyHours);
}
public static String formatDurationUntil(long milliseconds) {
return formatDurationUntil(milliseconds, true);
}
public static String formatDurationUntil(long milliseconds, boolean emptyHours) {
int secondsTillStart = (int) Math.ceil(milliseconds / 1000f);
return formatDuration(secondsTillStart, emptyHours);
}
public static String calcDuration(Calendar from, Calendar to) {
String retValue;
long millis = to.getTimeInMillis() - from.getTimeInMillis();
long min = millis / (1000 * 60);
long sec = (millis - (min * 60 * 1000)) / 1000;
retValue = String.valueOf(sec) + "\"";
if (retValue.length() == 2) {
retValue = "0" + retValue;
}
if (min > 0) {
retValue = String.valueOf(min) + "' " + retValue;
}
return retValue;
}
public static Calendar floorTime(Calendar calendar) {
if (calendar == null) {
calendar = Calendar.getInstance();
}
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
public static int daysBetween(Calendar day1, Calendar day2) {
Calendar dayOne = (Calendar) day1.clone();
Calendar dayTwo = (Calendar) day2.clone();
if (dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR)) {
return dayOne.get(Calendar.DAY_OF_YEAR) - dayTwo.get(Calendar.DAY_OF_YEAR);
} else {
if (dayTwo.get(Calendar.YEAR) > dayOne.get(Calendar.YEAR)) {
//swap them
Calendar temp = dayOne;
dayOne = dayTwo;
dayTwo = temp;
}
int extraDays = 0;
while (dayOne.get(Calendar.YEAR) > dayTwo.get(Calendar.YEAR)) {
dayOne.add(Calendar.YEAR, -1);
// getActualMaximum() important for leap years
extraDays += dayOne.getActualMaximum(Calendar.DAY_OF_YEAR);
}
return extraDays - dayTwo.get(Calendar.DAY_OF_YEAR) + dayOne.get(Calendar.DAY_OF_YEAR);
}
}
private static String formatDuration(int secondsTillStart, boolean emptyHours) {
int hours = secondsTillStart / 3600;
int minutes = (secondsTillStart % 3600) / 60;
int seconds = (secondsTillStart % 60);
boolean negative = (hours < 0 || minutes < 0 || seconds < 0);
String timePattern = ((negative) ? "-" : "") + "%s%s:%s";
String secondsString = seconds < 10 ? "0" + Math.abs(seconds) : "" + Math.abs(seconds);
String minutesString = minutes < 10 ? "0" + Math.abs(minutes) : "" + Math.abs(minutes);
String hoursString = hours < 10 ? "0" + Math.abs(hours) : "" + Math.abs(hours);
hoursString = !emptyHours && hoursString.equals("00") ? "" : hoursString + ":";
return String.format(timePattern, hoursString, minutesString, secondsString);
}
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays) {
initDatePicker(context, datePicker, time, pastDays, futureDays, true);
}
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays, boolean useWords) {
java.text.DateFormat dateFormat = java.text.DateFormat.getDateInstance();
ArrayList<String> dates = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(time.getTime());
// Past
calendar.add(Calendar.DAY_OF_MONTH, pastDays);
for (int i = pastDays; i < -1; i++) {
dates.add(dateFormat.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
if (useWords) {
dates.add(context.getString(R.string.yesterday));
dates.add(context.getString(R.string.today));
dates.add(context.getString(R.string.tomorrow));
calendar.add(Calendar.DAY_OF_MONTH, 3);
} else {
for (int i = (pastDays == 0 ? pastDays : -1); i < (futureDays < 2 ? futureDays + 1 : 2); i++) {
dates.add(dateFormat.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
}
// Future
for (int i = 2; i <= futureDays; i++) {
dates.add(dateFormat.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
datePicker.setDisplayedValues(dates.toArray(new String[dates.size()]));
datePicker.setMinValue(0);
datePicker.setMaxValue(dates.size() - 1);
datePicker.setWrapSelectorWheel(false);
datePicker.setValue(TimeUtils.daysBetween(calendar, Calendar.getInstance()) + Math.abs(pastDays));
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
TextView date = new TextView(context);
date.setText(dateFormat.format(calendar.getTime()));
int padding = context.getResources().getDimensionPixelSize(R.dimen.default_padding_half);
date.setPadding(padding, padding, padding, padding);
date.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
datePicker.getLayoutParams().width = date.getMeasuredWidth();
}
}
}