1   /* Copyright 2002-2021 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.NavigableSet;
21  import java.util.TreeSet;
22  import java.util.function.Consumer;
23  
24  import org.orekit.time.AbsoluteDate;
25  import org.orekit.time.ChronologicalComparator;
26  import org.orekit.time.TimeStamped;
27  
28  /** Container for objects that apply to spans of time.
29  
30   * @param <T> Type of the data.
31  
32   * @author Luc Maisonobe
33   * @since 7.1
34   */
35  public class TimeSpanMap<T> {
36  
37      /** Container for the data. */
38      private final NavigableSet<Transition<T>> data;
39  
40      /** Create a map containing a single object, initially valid throughout the timeline.
41       * <p>
42       * The real validity of this first entry will be truncated as other
43       * entries are either {@link #addValidBefore(Object, AbsoluteDate)
44       * added before} it or {@link #addValidAfter(Object, AbsoluteDate)
45       * added after} it.
46       * </p>
47       * @param entry entry (initially valid throughout the timeline)
48       */
49      public TimeSpanMap(final T entry) {
50          data = new TreeSet<Transition<T>>(new ChronologicalComparator());
51          data.add(new Transition<T>(AbsoluteDate.ARBITRARY_EPOCH, entry, entry));
52      }
53  
54      /** Add an entry valid before a limit date.
55       * <p>
56       * As an entry is valid, it truncates the validity of the neighboring
57       * entries already present in the map.
58       * </p>
59       * <p>
60       * The transition dates should be entered only once, either
61       * by a call to this method or by a call to {@link #addValidAfter(Object,
62       * AbsoluteDate)}. Repeating a transition date will lead to unexpected
63       * result and is not supported.
64       * </p>
65       * <p>
66       * Using addValidBefore(entry, t) will make 'entry' valid in ]-∞, t[ (note the open bracket).
67       * </p>
68       * @param entry entry to add
69       * @param latestValidityDate date before which the entry is valid
70       * (must be different from <em>all</em> dates already used for transitions)
71       */
72      public void addValidBefore(final T entry, final AbsoluteDate latestValidityDate) {
73  
74          if (data.size() == 1) {
75              final Transition<T> single = data.first();
76              if (single.getBefore() == single.getAfter()) {
77                  // the single entry was a dummy one, without a real transition
78                  // we replace it entirely
79                  data.clear();
80                  data.add(new Transition<T>(latestValidityDate, entry, single.getAfter()));
81                  return;
82              }
83          }
84  
85          final Transition<T> previous =
86                  data.floor(new Transition<T>(latestValidityDate, entry, null));
87          if (previous == null) {
88              // the new transition will be the first one
89              data.add(new Transition<T>(latestValidityDate, entry, data.first().getBefore()));
90          } else {
91              // the new transition will be after the previous one
92              data.remove(previous);
93              data.add(new Transition<T>(previous.date,      previous.getBefore(), entry));
94              data.add(new Transition<T>(latestValidityDate, entry,                previous.getAfter()));
95          }
96  
97      }
98  
99      /** Add an entry valid after a limit date.
100      * <p>
101      * As an entry is valid, it truncates the validity of the neighboring
102      * entries already present in the map.
103      * </p>
104      * <p>
105      * The transition dates should be entered only once, either
106      * by a call to this method or by a call to {@link #addValidBefore(Object,
107      * AbsoluteDate)}. Repeating a transition date will lead to unexpected
108      * result and is not supported.
109      * </p>
110      * <p>
111      * Using addValidAfter(entry, t) will make 'entry' valid [t, +∞[ (note the closed bracket).
112      * </p>
113      * @param entry entry to add
114      * @param earliestValidityDate date after which the entry is valid
115      * (must be different from <em>all</em> dates already used for transitions)
116      */
117     public void addValidAfter(final T entry, final AbsoluteDate earliestValidityDate) {
118 
119         if (data.size() == 1) {
120             final Transition<T> single = data.first();
121             if (single.getBefore() == single.getAfter()) {
122                 // the single entry was a dummy one, without a real transition
123                 // we replace it entirely
124                 data.clear();
125                 data.add(new Transition<T>(earliestValidityDate, single.getBefore(), entry));
126                 return;
127             }
128         }
129 
130         final Transition<T> next =
131                 data.ceiling(new Transition<T>(earliestValidityDate, entry, null));
132         if (next == null) {
133             // the new transition will be the last one
134             data.add(new Transition<T>(earliestValidityDate, data.last().getAfter(), entry));
135         } else {
136             // the new transition will be before the next one
137             data.remove(next);
138             data.add(new Transition<T>(earliestValidityDate, next.getBefore(), entry));
139             data.add(new Transition<T>(next.date,            entry,            next.getAfter()));
140         }
141 
142     }
143 
144     /** Get the entry valid at a specified date.
145      * @param date date at which the entry must be valid
146      * @return valid entry at specified date
147      */
148     public T get(final AbsoluteDate date) {
149         final Transition<T> previous = data.floor(new Transition<T>(date, null, null));
150         if (previous == null) {
151             // there are no transition before the specified date
152             // return the first valid entry
153             return data.first().getBefore();
154         } else {
155             return previous.getAfter();
156         }
157     }
158 
159     /** Get the time span containing a specified date.
160      * @param date date belonging to the desired time span
161      * @return time span containing the specified date
162      * @since 9.3
163      */
164     public Span<T> getSpan(final AbsoluteDate date) {
165         final Transition<T> previous = data.floor(new Transition<T>(date, null, null));
166         if (previous == null) {
167             // there are no transition before the specified date
168             // return the first valid entry
169             return new Span<>(data.first().getBefore(),
170                               AbsoluteDate.PAST_INFINITY,
171                               data.first().getDate());
172         } else {
173             final Transition<T> next = data.higher(previous);
174             return new Span<>(previous.getAfter(),
175                               previous.getDate(),
176                               next == null ? AbsoluteDate.FUTURE_INFINITY : next.getDate());
177         }
178     }
179 
180     /** Extract a range of the map.
181      * <p>
182      * The object returned will be a new independent instance that will contain
183      * only the transitions that lie in the specified range.
184      * </p>
185      * <p>
186      * Consider for example a map containing objects O₀ valid before t₁, O₁ valid
187      * between t₁ and t₂, O₂ valid between t₂ and t₃, O₃ valid between t₃ and t₄,
188      * and O₄ valid after t₄. then calling this method with a {@code start}
189      * date between t₁ and t₂ and a {@code end} date between t₃ and t₄
190      * will result in a new map containing objects O₁ valid before t₂, O₂
191      * valid between t₂ and t₃, and O₃ valid after t₃. The validity of O₁
192      * is therefore extended in the past, and the validity of O₃ is extended
193      * in the future.
194      * </p>
195      * @param start earliest date at which a transition is included in the range
196      * (may be set to {@link AbsoluteDate#PAST_INFINITY} to keep all early transitions)
197      * @param end latest date at which a transition is included in the r
198      * (may be set to {@link AbsoluteDate#FUTURE_INFINITY} to keep all late transitions)
199      * @return a new instance with all transitions restricted to the specified range
200      * @since 9.2
201      */
202     public TimeSpanMap<T> extractRange(final AbsoluteDate start, final AbsoluteDate end) {
203 
204         final NavigableSet<Transition<T>> inRange = data.subSet(new Transition<T>(start, null, null), true,
205                                                                 new Transition<T>(end,   null, null), true);
206         if (inRange.isEmpty()) {
207             // there are no transitions at all in the range
208             // we need to pick up the only valid object
209             return new TimeSpanMap<>(get(start));
210         }
211 
212         final TimeSpanMap<T> range = new TimeSpanMap<>(inRange.first().before);
213         for (final Transition<T> transition : inRange) {
214             range.addValidAfter(transition.after, transition.getDate());
215         }
216 
217         return range;
218 
219     }
220 
221     /** Get an unmodifiable view of the sorted transitions.
222      * @return unmodifiable view of the sorted transitions
223      */
224     public NavigableSet<Transition<T>> getTransitions() {
225         return Collections.unmodifiableNavigableSet(data);
226     }
227 
228     /**
229      * Performs an action for each element of map.
230      * <p>
231      * The action is performed chronologically.
232      * </p>
233      * @param action action to perform on the elements
234      * @since 10.3
235      */
236     public void forEach(final Consumer<T> action) {
237         boolean first = true;
238         for (Transition<T> transition : data) {
239             if (first) {
240                 if (transition.getBefore() != null) {
241                     action.accept(transition.getBefore());
242                 }
243                 first = false;
244             }
245             if (transition.getAfter() != null) {
246                 action.accept(transition.getAfter());
247             }
248         }
249     }
250 
251     /** Class holding transition times.
252      * <p>
253      * This data type is dual to {@link Span}, it is
254      * focused one transition date, and gives access to
255      * surrounding valid data whereas {@link Span} is focused
256      * on one valid data, and gives access to surrounding
257      * transition dates.
258      * </p>
259      * @param <S> Type of the data.
260      */
261     public static class Transition<S> implements TimeStamped {
262 
263         /** Transition date. */
264         private final AbsoluteDate date;
265 
266         /** Entry valid before the transition. */
267         private final S before;
268 
269         /** Entry valid after the transition. */
270         private final S after;
271 
272         /** Simple constructor.
273          * @param date transition date
274          * @param before entry valid before the transition
275          * @param after entry valid after the transition
276          */
277         private Transition(final AbsoluteDate date, final S before, final S after) {
278             this.date   = date;
279             this.before = before;
280             this.after  = after;
281         }
282 
283         /** Get the transition date.
284          * @return transition date
285          */
286         @Override
287         public AbsoluteDate getDate() {
288             return date;
289         }
290 
291         /** Get the entry valid before transition.
292          * @return entry valid before transition
293          */
294         public S getBefore() {
295             return before;
296         }
297 
298         /** Get the entry valid after transition.
299          * @return entry valid after transition
300          */
301         public S getAfter() {
302             return after;
303         }
304 
305     }
306 
307     /** Holder for one time span.
308      * <p>
309      * This data type is dual to {@link Transition}, it
310      * is focused on one valid data, and gives access to
311      * surrounding transition dates whereas {@link Transition}
312      * is focused on one transition date, and gives access to
313      * surrounding valid data.
314      * </p>
315      * @param <S> Type of the data.
316      * @since 9.3
317      */
318     public static class Span<S> {
319 
320         /** Valid data. */
321         private final S data;
322 
323         /** Start of validity for the data. */
324         private final AbsoluteDate start;
325 
326         /** End of validity for the data. */
327         private final AbsoluteDate end;
328 
329         /** Simple constructor.
330          * @param data valid data
331          * @param start start of validity for the data
332          * @param end end of validity for the data
333          */
334         private Span(final S data, final AbsoluteDate start, final AbsoluteDate end) {
335             this.data  = data;
336             this.start = start;
337             this.end   = end;
338         }
339 
340         /** Get the data valid during this time span.
341          * @return data valid during this time span
342          */
343         public S getData() {
344             return data;
345         }
346 
347         /** Get the start of this time span.
348          * @return start of this time span
349          */
350         public AbsoluteDate getStart() {
351             return start;
352         }
353 
354         /** Get the end of this time span.
355          * @return end of this time span
356          */
357         public AbsoluteDate getEnd() {
358             return end;
359         }
360 
361     }
362 
363 }