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ₖ &lt;
179      * {@code latestValidityDate} &lt; 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ₖ &lt;
259      * {@code earliestValidityDate} &lt; 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 }