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