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