1 /* Copyright 2002-2025 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.utils;
18
19 import java.util.function.Consumer;
20
21 import org.orekit.errors.OrekitException;
22 import org.orekit.errors.OrekitMessages;
23 import org.orekit.time.AbsoluteDate;
24 import org.orekit.time.TimeStamped;
25
26 /** Container for objects that apply to spans of time.
27 * <p>
28 * Time span maps can be seen either as an ordered collection of
29 * {@link Span time spans} or as an ordered collection
30 * of {@link Transition transitions}. Both views are dual one to
31 * each other. A time span extends from one transition to the
32 * next one, and a transition separates one time span from the
33 * next one. Each time span contains one entry that is valid during
34 * the time span; this entry may be null if nothing is valid during
35 * this time span.
36 * </p>
37 * <p>
38 * Typical uses of {@link TimeSpanMap} are to hold piecewise data, like for
39 * example an orbit count that changes at ascending nodes (in which case the
40 * entry would be an {@link Integer}), or a visibility status between several
41 * objects (in which case the entry would be a {@link Boolean}), or a drag
42 * coefficient that is expected to be estimated daily or three-hourly.
43 * </p>
44 * <p>
45 * Time span maps are built progressively. At first, they contain one
46 * {@link Span time span} only whose validity extends from past infinity to
47 * future infinity. Then new entries are added one at a time, associated with
48 * transition dates, in order to build up the complete map. The transition dates
49 * can be either the start of validity (when calling {@link #addValidAfter(Object,
50 * AbsoluteDate, boolean)}), or the end of the validity (when calling {@link
51 * #addValidBefore(Object, AbsoluteDate, boolean)}). Entries are often added at one
52 * end only (and mainly in chronological order), but this is not required. It is
53 * possible for example to first set up a map that covers a large range (say one day),
54 * and then to insert intermediate dates using for example propagation and event
55 * detectors to carve out some parts. This is akin to the way Binary Space Partitioning
56 * Trees work.
57 * </p>
58 * <p>
59 * Since 11.1, this class is thread-safe
60 * </p>
61 * @param <T> Type of the data.
62 * @author Luc Maisonobe
63 * @since 7.1
64 */
65 public class TimeSpanMap<T> {
66
67 /** Reference to last accessed data. */
68 private Span<T> current;
69
70 /** First span.
71 * @since 13.1
72 */
73 private Span<T> firstSpan;
74
75 /** Last span.
76 * @since 13.1
77 */
78 private Span<T> lastSpan;
79
80 /** End of early expunged range.
81 * @since 13.1
82 */
83 private AbsoluteDate expungedEarly;
84
85 /** Start of late expunged range.
86 * @since 13.1
87 */
88 private AbsoluteDate expungedLate;
89
90 /** Number of time spans. */
91 private int nbSpans;
92
93 /** Maximum number of time spans.
94 * @since 13.1
95 */
96 private int maxNbSpans;
97
98 /** Maximum time range between the earliest and the latest transitions.
99 * @since 13.1
100 */
101 private double maxRange;
102
103 /** Expunge policy.
104 * @since 13.1
105 */
106 private ExpungePolicy expungePolicy;
107
108 /** Create a map containing a single object, initially valid throughout the timeline.
109 * <p>
110 * The real validity of this first entry will be truncated as other
111 * entries are either {@link #addValidBefore(Object, AbsoluteDate, boolean)
112 * added before} it or {@link #addValidAfter(Object, AbsoluteDate, boolean)
113 * added after} it.
114 * </p>
115 * <p>
116 * The initial {@link #configureExpunge(int, double, ExpungePolicy) expunge policy}
117 * is to never expunge any entries, it can be changed afterward by calling
118 * {@link #configureExpunge(int, double, ExpungePolicy)}
119 * </p>
120 * @param entry entry (initially valid throughout the timeline)
121 */
122 public TimeSpanMap(final T entry) {
123 this.current = new Span<>(entry);
124 this.firstSpan = current;
125 this.lastSpan = current;
126 this.nbSpans = 1;
127 configureExpunge(Integer.MAX_VALUE, Double.POSITIVE_INFINITY, ExpungePolicy.EXPUNGE_FARTHEST);
128 }
129
130 /** Configure (or reconfigure) expunge policy for later additions.
131 * <p>
132 * When an entry is added to the map (using either {@link #addValidBefore(Object, AbsoluteDate, boolean)},
133 * {@link #addValidBetween(Object, AbsoluteDate, AbsoluteDate)}, or
134 * {@link #addValidAfter(Object, AbsoluteDate, boolean)} that exceeds the allowed capacity in terms
135 * of number of time spans or maximum time range between the earliest and the latest transitions,
136 * then exceeding data is expunged according to the {@code expungePolicy}.
137 * </p>
138 * <p>
139 * Note that as the policy depends on the date at which new entries are added, the policy will be enforced
140 * only for the <em>next</em> calls to {@link #addValidBefore(Object, AbsoluteDate, boolean)},
141 * {@link #addValidBetween(Object, AbsoluteDate, AbsoluteDate)}, and {@link #addValidAfter(Object,
142 * AbsoluteDate, boolean)}, it is <em>not</em> enforce immediately.
143 * </p>
144 * @param newMaxNbSpans maximum number of time spans
145 * @param newMaxRange maximum time range between the earliest and the latest transitions
146 * @param newExpungePolicy expunge policy to apply when capacity is exceeded
147 * @since 13.1
148 */
149 public synchronized void configureExpunge(final int newMaxNbSpans, final double newMaxRange, final ExpungePolicy newExpungePolicy) {
150 this.maxNbSpans = newMaxNbSpans;
151 this.maxRange = newMaxRange;
152 this.expungePolicy = newExpungePolicy;
153 this.expungedEarly = AbsoluteDate.PAST_INFINITY;
154 this.expungedLate = AbsoluteDate.FUTURE_INFINITY;
155 }
156
157 /** Get the number of spans.
158 * <p>
159 * The number of spans is always at least 1. The number of transitions
160 * is always 1 lower than the number of spans.
161 * </p>
162 * @return number of spans
163 * @since 11.1
164 */
165 public synchronized int getSpansNumber() {
166 return nbSpans;
167 }
168
169 /** Add an entry valid before a limit date.
170 * <p>
171 * As an entry is valid, it truncates or overrides the validity of the neighboring
172 * entries already present in the map.
173 * </p>
174 * <p>
175 * If the map already contains transitions that occur earlier than {@code latestValidityDate},
176 * the {@code erasesEarlier} parameter controls what to do with them. Let's consider
177 * the time span [tₖ; tₖ₊₁[ associated with entry eₖ that would have been valid at time
178 * {@code latestValidityDate} prior to the call to the method (i.e. tₖ <
179 * {@code latestValidityDate} < tₖ₊₁).
180 * </p>
181 * <ul>
182 * <li>if {@code erasesEarlier} is {@code true}, then all earlier transitions
183 * up to and including tₖ are erased, and the {@code entry} will be valid from past infinity
184 * to {@code latestValidityDate}</li>
185 * <li>if {@code erasesEarlier} is {@code false}, then all earlier transitions
186 * are preserved, and the {@code entry} will be valid from tₖ
187 * to {@code latestValidityDate}</li>
188 * </ul>
189 * <p>
190 * In both cases, the existing entry eₖ time span will be truncated and will be valid
191 * only from {@code latestValidityDate} to tₖ₊₁.
192 * </p>
193 * @param entry entry to add
194 * @param latestValidityDate date before which the entry is valid
195 * @param erasesEarlier if true, the entry erases all existing transitions
196 * that are earlier than {@code latestValidityDate}
197 * @return span with added entry
198 * @since 11.1
199 */
200 public synchronized Span<T> addValidBefore(final T entry, final AbsoluteDate latestValidityDate, final boolean erasesEarlier) {
201
202 // update current reference to transition date
203 locate(latestValidityDate);
204
205 if (erasesEarlier) {
206
207 // drop everything before date
208 current.start = null;
209
210 // update count
211 nbSpans = 0;
212 for (Span<T> span = current; span != null; span = span.next()) {
213 ++nbSpans;
214 }
215
216 }
217
218 final Span<T> span = new Span<>(entry);
219
220 final Transition<T> start = current.getStartTransition();
221 if (start != null && start.getDate().equals(latestValidityDate)) {
222 // the transition at the start of the current span is at the exact same date
223 // we update it, without adding a new transition
224 if (start.previous() != null) {
225 start.previous().setAfter(span);
226 }
227 start.setBefore(span);
228 updateFirstIfNeeded(span);
229 } else {
230
231 if (current.getStartTransition() != null) {
232 current.getStartTransition().setAfter(span);
233 }
234
235 // we need to add a new transition somewhere inside the current span
236 insertTransition(latestValidityDate, span, current);
237
238 }
239
240 // we consider the last added transition as the new current one
241 current = span;
242
243 expungeOldData(latestValidityDate);
244
245 return span;
246
247 }
248
249 /** Add an entry valid after a limit date.
250 * <p>
251 * As an entry is valid, it truncates or overrides the validity of the neighboring
252 * entries already present in the map.
253 * </p>
254 * <p>
255 * If the map already contains transitions that occur later than {@code earliestValidityDate},
256 * the {@code erasesLater} parameter controls what to do with them. Let's consider
257 * the time span [tₖ; tₖ₊₁[ associated with entry eₖ that would have been valid at time
258 * {@code earliestValidityDate} prior to the call to the method (i.e. tₖ <
259 * {@code earliestValidityDate} < tₖ₊₁).
260 * </p>
261 * <ul>
262 * <li>if {@code erasesLater} is {@code true}, then all later transitions
263 * from and including tₖ₊₁ are erased, and the {@code entry} will be valid from
264 * {@code earliestValidityDate} to future infinity</li>
265 * <li>if {@code erasesLater} is {@code false}, then all later transitions
266 * are preserved, and the {@code entry} will be valid from {@code earliestValidityDate}
267 * to tₖ₊₁</li>
268 * </ul>
269 * <p>
270 * In both cases, the existing entry eₖ time span will be truncated and will be valid
271 * only from tₖ to {@code earliestValidityDate}.
272 * </p>
273 * @param entry entry to add
274 * @param earliestValidityDate date after which the entry is valid
275 * @param erasesLater if true, the entry erases all existing transitions
276 * that are later than {@code earliestValidityDate}
277 * @return span with added entry
278 * @since 11.1
279 */
280 public synchronized Span<T> addValidAfter(final T entry, final AbsoluteDate earliestValidityDate, final boolean erasesLater) {
281
282 // update current reference to transition date
283 locate(earliestValidityDate);
284
285 if (erasesLater) {
286
287 // drop everything after date
288 current.end = null;
289
290 // update count
291 nbSpans = 0;
292 for (Span<T> span = current; span != null; span = span.previous()) {
293 ++nbSpans;
294 }
295
296 }
297
298 final Span<T> span = new Span<>(entry);
299 if (current.getEndTransition() != null) {
300 current.getEndTransition().setBefore(span);
301 }
302
303 final Transition<T> start = current.getStartTransition();
304 if (start != null && start.getDate().equals(earliestValidityDate)) {
305 // the transition at the start of the current span is at the exact same date
306 // we update it, without adding a new transition
307 start.setAfter(span);
308 updateLastIfNeeded(span);
309 } else {
310 // we need to add a new transition somewhere inside the current span
311 insertTransition(earliestValidityDate, current, span);
312 }
313
314 // we consider the last added transition as the new current one
315 current = span;
316
317 // update metadata
318 expungeOldData(earliestValidityDate);
319
320 return span;
321
322 }
323
324 /** Add an entry valid between two limit dates.
325 * <p>
326 * As an entry is valid, it truncates or overrides the validity of the neighboring
327 * entries already present in the map.
328 * </p>
329 * @param entry entry to add
330 * @param earliestValidityDate date after which the entry is valid
331 * @param latestValidityDate date before which the entry is valid
332 * @return span with added entry
333 * @since 11.1
334 */
335 public synchronized Span<T> addValidBetween(final T entry, final AbsoluteDate earliestValidityDate, final AbsoluteDate latestValidityDate) {
336
337 // handle special cases
338 if (AbsoluteDate.PAST_INFINITY.equals(earliestValidityDate)) {
339 if (AbsoluteDate.FUTURE_INFINITY.equals(latestValidityDate)) {
340 // we wipe everything in the map
341 current = new Span<>(entry);
342 firstSpan = current;
343 lastSpan = current;
344 return current;
345 } else {
346 // we wipe from past infinity
347 return addValidBefore(entry, latestValidityDate, true);
348 }
349 } else if (AbsoluteDate.FUTURE_INFINITY.equals(latestValidityDate)) {
350 // we wipe up to future infinity
351 return addValidAfter(entry, earliestValidityDate, true);
352 } else {
353
354 // locate spans at earliest and latest dates
355 locate(earliestValidityDate);
356 Span<T> latest = current;
357 while (latest.getEndTransition() != null && latest.getEnd().isBeforeOrEqualTo(latestValidityDate)) {
358 latest = latest.next();
359 --nbSpans;
360 }
361 if (latest == current) {
362 // the interval splits one transition in the middle, we need to duplicate the instance
363 latest = new Span<>(current.data);
364 if (current.getEndTransition() != null) {
365 current.getEndTransition().setBefore(latest);
366 }
367 }
368
369 final Span<T> span = new Span<>(entry);
370
371 // manage earliest transition
372 final Transition<T> start = current.getStartTransition();
373 if (start != null && start.getDate().equals(earliestValidityDate)) {
374 // the transition at the start of the current span is at the exact same date
375 // we update it, without adding a new transition
376 start.setAfter(span);
377 updateLastIfNeeded(span);
378 } else {
379 // we need to add a new transition somewhere inside the current span
380 insertTransition(earliestValidityDate, current, span);
381 }
382
383 // manage latest transition
384 insertTransition(latestValidityDate, span, latest);
385
386 // we consider the last added transition as the new current one
387 current = span;
388
389 // update metadata
390 final AbsoluteDate midDate = earliestValidityDate.shiftedBy(0.5 * latestValidityDate.durationFrom(earliestValidityDate));
391 expungeOldData(midDate);
392
393 return span;
394
395 }
396
397 }
398
399 /** Get the entry valid at a specified date.
400 * <p>
401 * The expected complexity is O(1) for successive calls with
402 * neighboring dates, which is the more frequent use in propagation
403 * or orbit determination applications, and O(n) for random calls.
404 * </p>
405 * @param date date at which the entry must be valid
406 * @return valid entry at specified date
407 * @see #getSpan(AbsoluteDate)
408 */
409 public synchronized T get(final AbsoluteDate date) {
410 return getSpan(date).getData();
411 }
412
413 /** Get the time span containing a specified date.
414 * <p>
415 * The expected complexity is O(1) for successive calls with
416 * neighboring dates, which is the more frequent use in propagation
417 * or orbit determination applications, and O(n) for random calls.
418 * </p>
419 * @param date date belonging to the desired time span
420 * @return time span containing the specified date
421 * @since 9.3
422 */
423 public synchronized Span<T> getSpan(final AbsoluteDate date) {
424
425 // safety check
426 if (date.isBefore(expungedEarly) || date.isAfter(expungedLate)) {
427 throw new OrekitException(OrekitMessages.EXPUNGED_SPAN, date);
428 }
429
430 locate(date);
431 return current;
432 }
433
434 /** Locate the time span containing a specified date.
435 * <p>
436 * The {@code current} field is updated to the located span.
437 * After the method returns, {@code current.getStartTransition()} is either
438 * null or its date is before or equal to date, and {@code
439 * current.getEndTransition()} is either null or its date is after date.
440 * </p>
441 * @param date date belonging to the desired time span
442 */
443 private synchronized void locate(final AbsoluteDate date) {
444
445 while (current.getStart().isAfter(date)) {
446 // the current span is too late
447 current = current.previous();
448 }
449
450 while (current.getEnd().isBeforeOrEqualTo(date)) {
451
452 final Span<T> next = current.next();
453 if (next == null) {
454 // this happens when date is FUTURE_INFINITY
455 return;
456 }
457
458 // the current span is too early
459 current = next;
460
461 }
462
463 }
464
465 /** Insert a transition.
466 * @param date transition date
467 * @param before span before transition
468 * @param after span after transition
469 * @since 11.1
470 */
471 private void insertTransition(final AbsoluteDate date, final Span<T> before, final Span<T> after) {
472 final Transition<T> transition = new Transition<>(this, date);
473 transition.setBefore(before);
474 transition.setAfter(after);
475 updateFirstIfNeeded(before);
476 updateLastIfNeeded(after);
477 ++nbSpans;
478 }
479
480 /** Get the first (earliest) transition.
481 * @return first (earliest) transition, or null if there are no transitions
482 * @since 11.1
483 */
484 public synchronized Transition<T> getFirstTransition() {
485 return getFirstSpan().getEndTransition();
486 }
487
488 /** Get the last (latest) transition.
489 * @return last (latest) transition, or null if there are no transitions
490 * @since 11.1
491 */
492 public synchronized Transition<T> getLastTransition() {
493 return getLastSpan().getStartTransition();
494 }
495
496 /** Get the first (earliest) span.
497 * @return first (earliest) span
498 * @since 11.1
499 */
500 public synchronized Span<T> getFirstSpan() {
501 return firstSpan;
502 }
503
504 /** Get the first (earliest) span with non-null data.
505 * @return first (earliest) span with non-null data
506 * @since 12.1
507 */
508 public synchronized Span<T> getFirstNonNullSpan() {
509 Span<T> span = getFirstSpan();
510 while (span.getData() == null) {
511 if (span.getEndTransition() == null) {
512 throw new OrekitException(OrekitMessages.NO_CACHED_ENTRIES);
513 }
514 span = span.next();
515 }
516 return span;
517 }
518
519 /** Get the last (latest) span.
520 * @return last (latest) span
521 * @since 11.1
522 */
523 public synchronized Span<T> getLastSpan() {
524 return lastSpan;
525 }
526
527 /** Get the last (latest) span with non-null data.
528 * @return last (latest) span with non-null data
529 * @since 12.1
530 */
531 public synchronized Span<T> getLastNonNullSpan() {
532 Span<T> span = getLastSpan();
533 while (span.getData() == null) {
534 if (span.getStartTransition() == null) {
535 throw new OrekitException(OrekitMessages.NO_CACHED_ENTRIES);
536 }
537 span = span.previous();
538 }
539 return span;
540 }
541
542 /** Extract a range of the map.
543 * <p>
544 * The object returned will be a new independent instance that will contain
545 * only the transitions that lie in the specified range.
546 * </p>
547 * <p>
548 * Consider, for example, a map containing objects O₀ valid before t₁, O₁ valid
549 * between t₁ and t₂, O₂ valid between t₂ and t₃, O₃ valid between t₃ and t₄,
550 * and O₄ valid after t₄. then calling this method with a {@code start}
551 * date between t₁ and t₂ and a {@code end} date between t₃ and t₄
552 * will result in a new map containing objects O₁ valid before t₂, O₂
553 * valid between t₂ and t₃, and O₃ valid after t₃. The validity of O₁
554 * is therefore extended in the past, and the validity of O₃ is extended
555 * in the future.
556 * </p>
557 * @param start earliest date at which a transition is included in the range
558 * (may be set to {@link AbsoluteDate#PAST_INFINITY} to keep all early transitions)
559 * @param end latest date at which a transition is included in the r
560 * (may be set to {@link AbsoluteDate#FUTURE_INFINITY} to keep all late transitions)
561 * @return a new instance with all transitions restricted to the specified range
562 * @since 9.2
563 */
564 public synchronized TimeSpanMap<T> extractRange(final AbsoluteDate start, final AbsoluteDate end) {
565
566 Span<T> span = getSpan(start);
567 final TimeSpanMap<T> range = new TimeSpanMap<>(span.getData());
568 while (span.getEndTransition() != null && span.getEndTransition().getDate().isBeforeOrEqualTo(end)) {
569 span = span.next();
570 range.addValidAfter(span.getData(), span.getStartTransition().getDate(), false);
571 }
572
573 return range;
574
575 }
576
577 /**
578 * Performs an action for each non-null element of the map.
579 * <p>
580 * The action is performed chronologically.
581 * </p>
582 * @param action action to perform on the non-null elements
583 * @since 10.3
584 */
585 public synchronized void forEach(final Consumer<T> action) {
586 for (Span<T> span = getFirstSpan(); span != null; span = span.next()) {
587 if (span.getData() != null) {
588 action.accept(span.getData());
589 }
590 }
591 }
592
593 /**
594 * Expunge old data.
595 * @param date date of the latest added data
596 */
597 private synchronized void expungeOldData(final AbsoluteDate date) {
598
599 while (nbSpans > maxNbSpans || lastSpan.getStart().durationFrom(firstSpan.getEnd()) > maxRange) {
600 // capacity exceeded, we need to purge old data
601 if (expungePolicy.expungeEarliest(date, firstSpan.getEnd(), lastSpan.getStart())) {
602 // we need to purge the earliest data
603 if (firstSpan.getEnd().isAfter(expungedEarly)) {
604 expungedEarly = firstSpan.getEnd();
605 }
606 firstSpan = firstSpan.next();
607 firstSpan.start = null;
608 if (current.start == null) {
609 // the current span was the one we just expunged
610 // we need to update it
611 current = firstSpan;
612 }
613 } else {
614 // we need to purge the latest data
615 if (lastSpan.getStart().isBefore(expungedLate)) {
616 expungedLate = lastSpan.getStart();
617 }
618 lastSpan = lastSpan.previous();
619 lastSpan.end = null;
620 if (current.end == null) {
621 // the current span was the one we just expunged
622 // we need to update it
623 current = lastSpan;
624 }
625 }
626 --nbSpans;
627 }
628
629 }
630
631 /** Update first span if needed.
632 * @param candidate candidate first span
633 * @since 13.1
634 */
635 private void updateFirstIfNeeded(final Span<T> candidate) {
636 if (candidate.getStartTransition() == null) {
637 firstSpan = candidate;
638 }
639 }
640
641 /** Update last span if needed.
642 * @param candidate candidate last span
643 * @since 13.1
644 */
645 private void updateLastIfNeeded(final Span<T> candidate) {
646 if (candidate.getEndTransition() == null) {
647 lastSpan = candidate;
648 }
649 }
650
651 /** Class holding transition times.
652 * <p>
653 * This data type is dual to {@link Span}, it is
654 * focused on one transition date, and gives access to
655 * surrounding valid data whereas {@link Span} is focused
656 * on one valid data, and gives access to surrounding
657 * transition dates.
658 * </p>
659 * @param <S> Type of the data.
660 */
661 public static class Transition<S> implements TimeStamped {
662
663 /** Map this transition belongs to.
664 * @since 13.0
665 */
666 private final TimeSpanMap<S> map;
667
668 /** Transition date. */
669 private AbsoluteDate date;
670
671 /** Entry valid before the transition. */
672 private Span<S> before;
673
674 /** Entry valid after the transition. */
675 private Span<S> after;
676
677 /** Simple constructor.
678 * @param map map this transition belongs to
679 * @param date transition date
680 */
681 private Transition(final TimeSpanMap<S> map, final AbsoluteDate date) {
682 this.map = map;
683 this.date = date;
684 }
685
686 /** Set the span valid before transition.
687 * @param before span valid before transition (must be non-null)
688 */
689 void setBefore(final Span<S> before) {
690 this.before = before;
691 before.end = this;
692 }
693
694 /** Set the span valid after transition.
695 * @param after span valid after transition (must be non-null)
696 */
697 void setAfter(final Span<S> after) {
698 this.after = after;
699 after.start = this;
700 }
701
702 /** Get the transition date.
703 * @return transition date
704 */
705 @Override
706 public AbsoluteDate getDate() {
707 return date;
708 }
709
710 /** Move transition.
711 * <p>
712 * When moving a transition to past or future infinity, it will be disconnected
713 * from the time span it initially belonged to as the next or previous time
714 * span validity will be extends to infinity.
715 * </p>
716 * @param newDate new transition date
717 * @param eraseOverridden if true, spans that are entirely between current
718 * and new transition dates will be silently removed, if false and such
719 * spans exist, an exception will be triggered
720 * @since 13.0
721 */
722 public void resetDate(final AbsoluteDate newDate, final boolean eraseOverridden) {
723 if (newDate.isAfter(date)) {
724 // we are moving the transition towards future
725
726 // find span after new date
727 Span<S> newAfter = after;
728 while (newAfter.getEndTransition() != null &&
729 newAfter.getEndTransition().getDate().isBeforeOrEqualTo(newDate)) {
730 if (eraseOverridden) {
731 map.nbSpans--;
732 } else {
733 // forbidden collision detected
734 throw new OrekitException(OrekitMessages.TRANSITION_DATES_COLLISION,
735 date, newDate, newAfter.getEndTransition().getDate());
736 }
737 newAfter = newAfter.next();
738 }
739
740 synchronized (map) {
741 // perform update
742 date = newDate;
743 after = newAfter;
744 after.start = this;
745 map.current = before;
746
747 if (newDate.isInfinite()) {
748 // we have just moved the transition to future infinity, it should really disappear
749 map.nbSpans--;
750 map.lastSpan = before;
751 before.end = null;
752 }
753 }
754
755 } else {
756 // we are moving transition towards the past
757
758 // find span before new date
759 Span<S> newBefore = before;
760 while (newBefore.getStartTransition() != null &&
761 newBefore.getStartTransition().getDate().isAfterOrEqualTo(newDate)) {
762 if (eraseOverridden) {
763 map.nbSpans--;
764 } else {
765 // forbidden collision detected
766 throw new OrekitException(OrekitMessages.TRANSITION_DATES_COLLISION,
767 date, newDate, newBefore.getStartTransition().getDate());
768 }
769 newBefore = newBefore.previous();
770 }
771
772 synchronized (map) {
773 // perform update
774 date = newDate;
775 before = newBefore;
776 before.end = this;
777 map.current = after;
778
779 if (newDate.isInfinite()) {
780 // we have just moved the transition to past infinity, it should really disappear
781 map.nbSpans--;
782 map.firstSpan = after;
783 after.start = null;
784 }
785 }
786
787 }
788 }
789
790 /** Get the previous transition.
791 * @return previous transition, or null if this transition was the first one
792 * @since 11.1
793 */
794 public Transition<S> previous() {
795 return before.getStartTransition();
796 }
797
798 /** Get the next transition.
799 * @return next transition, or null if this transition was the last one
800 * @since 11.1
801 */
802 public Transition<S> next() {
803 return after.getEndTransition();
804 }
805
806 /** Get the entry valid before transition.
807 * @return entry valid before transition
808 * @see #getSpanBefore()
809 */
810 public S getBefore() {
811 return before.getData();
812 }
813
814 /** Get the {@link Span} valid before transition.
815 * @return {@link Span} valid before transition
816 * @since 11.1
817 */
818 public Span<S> getSpanBefore() {
819 return before;
820 }
821
822 /** Get the entry valid after transition.
823 * @return entry valid after transition
824 * @see #getSpanAfter()
825 */
826 public S getAfter() {
827 return after.getData();
828 }
829
830 /** Get the {@link Span} valid after transition.
831 * @return {@link Span} valid after transition
832 * @since 11.1
833 */
834 public Span<S> getSpanAfter() {
835 return after;
836 }
837
838 }
839
840 /** Holder for one time span.
841 * <p>
842 * This data type is dual to {@link Transition}, it
843 * is focused on one valid data, and gives access to
844 * surrounding transition dates whereas {@link Transition}
845 * is focused on one transition date, and gives access to
846 * surrounding valid data.
847 * </p>
848 * @param <S> Type of the data.
849 * @since 9.3
850 */
851 public static class Span<S> {
852
853 /** Valid data. */
854 private final S data;
855
856 /** Start of validity for the data (null if span extends to past infinity). */
857 private Transition<S> start;
858
859 /** End of validity for the data (null if span extends to future infinity). */
860 private Transition<S> end;
861
862 /** Simple constructor.
863 * @param data valid data
864 */
865 private Span(final S data) {
866 this.data = data;
867 }
868
869 /** Get the data valid during this time span.
870 * @return data valid during this time span
871 */
872 public S getData() {
873 return data;
874 }
875
876 /** Get the previous time span.
877 * @return previous time span, or null if this time span was the first one
878 * @since 11.1
879 */
880 public Span<S> previous() {
881 return start == null ? null : start.getSpanBefore();
882 }
883
884 /** Get the next time span.
885 * @return next time span, or null if this time span was the last one
886 * @since 11.1
887 */
888 public Span<S> next() {
889 return end == null ? null : end.getSpanAfter();
890 }
891
892 /** Get the start of this time span.
893 * @return start of this time span (will be {@link AbsoluteDate#PAST_INFINITY}
894 * if {@link #getStartTransition()} returns null)
895 * @see #getStartTransition()
896 */
897 public AbsoluteDate getStart() {
898 return start == null ? AbsoluteDate.PAST_INFINITY : start.getDate();
899 }
900
901 /** Get the transition at the start of this time span.
902 * @return transition at the start of this time span (null if span extends to past infinity)
903 * @see #getStart()
904 * @since 11.1
905 */
906 public Transition<S> getStartTransition() {
907 return start;
908 }
909
910 /** Get the end of this time span.
911 * @return end of this time span (will be {@link AbsoluteDate#FUTURE_INFINITY}
912 * if {@link #getEndTransition()} returns null)
913 * @see #getEndTransition()
914 */
915 public AbsoluteDate getEnd() {
916 return end == null ? AbsoluteDate.FUTURE_INFINITY : end.getDate();
917 }
918
919 /** Get the transition at the end of this time span.
920 * @return transition at the end of this time span (null if span extends to future infinity)
921 * @see #getEnd()
922 * @since 11.1
923 */
924 public Transition<S> getEndTransition() {
925 return end;
926 }
927
928 }
929
930 }