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ₖ <
178 * {@code latestValidityDate} < 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ₖ <
259 * {@code earliestValidityDate} < tₖ₊₁).
260 * </p>
261 * <ul>
262 * <li>if {@code erasesLater} is {@code true}, then all later transitions
263 * from and including tₖ₊₁ are erased, and the {@code entry} will be valid from
264 * {@code earliestValidityDate} to future infinity</li>
265 * <li>if {@code erasesLater} is {@code false}, then all later transitions
266 * are preserved, and the {@code entry} will be valid from {@code earliestValidityDate}
267 * to tₖ₊₁</li>
268 * </ul>
269 * <p>
270 * In both cases, the existing entry eₖ time span will be truncated and will be valid
271 * only from tₖ to {@code earliestValidityDate}.
272 * </p>
273 * @param entry entry to add
274 * @param earliestValidityDate date after which the entry is valid
275 * @param erasesLater if true, the entry erases all existing transitions
276 * that are later than {@code earliestValidityDate}
277 * @return span with added entry
278 * @since 11.1
279 */
280 public synchronized Span<T> addValidAfter(final T entry, final AbsoluteDate earliestValidityDate, final boolean erasesLater) {
281
282 // update current reference to transition date
283 locate(earliestValidityDate);
284
285 if (erasesLater) {
286 // 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 }