TimeSpanMap.java

  1. /* Copyright 2002-2020 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. import java.util.Collections;
  19. import java.util.NavigableSet;
  20. import java.util.TreeSet;
  21. import java.util.function.Consumer;

  22. import org.orekit.time.AbsoluteDate;
  23. import org.orekit.time.ChronologicalComparator;
  24. import org.orekit.time.TimeStamped;

  25. /** Container for objects that apply to spans of time.

  26.  * @param <T> Type of the data.

  27.  * @author Luc Maisonobe
  28.  * @since 7.1
  29.  */
  30. public class TimeSpanMap<T> {

  31.     /** Container for the data. */
  32.     private final NavigableSet<Transition<T>> data;

  33.     /** Create a map containing a single object, initially valid throughout the timeline.
  34.      * <p>
  35.      * The real validity of this first entry will be truncated as other
  36.      * entries are either {@link #addValidBefore(Object, AbsoluteDate)
  37.      * added before} it or {@link #addValidAfter(Object, AbsoluteDate)
  38.      * added after} it.
  39.      * </p>
  40.      * @param entry entry (initially valid throughout the timeline)
  41.      */
  42.     public TimeSpanMap(final T entry) {
  43.         data = new TreeSet<Transition<T>>(new ChronologicalComparator());
  44.         data.add(new Transition<T>(AbsoluteDate.ARBITRARY_EPOCH, entry, entry));
  45.     }

  46.     /** Add an entry valid before a limit date.
  47.      * <p>
  48.      * As an entry is valid, it truncates the validity of the neighboring
  49.      * entries already present in the map.
  50.      * </p>
  51.      * <p>
  52.      * The transition dates should be entered only once, either
  53.      * by a call to this method or by a call to {@link #addValidAfter(Object,
  54.      * AbsoluteDate)}. Repeating a transition date will lead to unexpected
  55.      * result and is not supported.
  56.      * </p>
  57.      * <p>
  58.      * Using addValidBefore(entry, t) will make 'entry' valid in ]-∞, t[ (note the open bracket).
  59.      * </p>
  60.      * @param entry entry to add
  61.      * @param latestValidityDate date before which the entry is valid
  62.      * (must be different from <em>all</em> dates already used for transitions)
  63.      */
  64.     public void addValidBefore(final T entry, final AbsoluteDate latestValidityDate) {

  65.         if (data.size() == 1) {
  66.             final Transition<T> single = data.first();
  67.             if (single.getBefore() == single.getAfter()) {
  68.                 // the single entry was a dummy one, without a real transition
  69.                 // we replace it entirely
  70.                 data.clear();
  71.                 data.add(new Transition<T>(latestValidityDate, entry, single.getAfter()));
  72.                 return;
  73.             }
  74.         }

  75.         final Transition<T> previous =
  76.                 data.floor(new Transition<T>(latestValidityDate, entry, null));
  77.         if (previous == null) {
  78.             // the new transition will be the first one
  79.             data.add(new Transition<T>(latestValidityDate, entry, data.first().getBefore()));
  80.         } else {
  81.             // the new transition will be after the previous one
  82.             data.remove(previous);
  83.             data.add(new Transition<T>(previous.date,      previous.getBefore(), entry));
  84.             data.add(new Transition<T>(latestValidityDate, entry,                previous.getAfter()));
  85.         }

  86.     }

  87.     /** Add an entry valid after a limit date.
  88.      * <p>
  89.      * As an entry is valid, it truncates the validity of the neighboring
  90.      * entries already present in the map.
  91.      * </p>
  92.      * <p>
  93.      * The transition dates should be entered only once, either
  94.      * by a call to this method or by a call to {@link #addValidBefore(Object,
  95.      * AbsoluteDate)}. Repeating a transition date will lead to unexpected
  96.      * result and is not supported.
  97.      * </p>
  98.      * <p>
  99.      * Using addValidAfter(entry, t) will make 'entry' valid [t, +∞[ (note the closed bracket).
  100.      * </p>
  101.      * @param entry entry to add
  102.      * @param earliestValidityDate date after which the entry is valid
  103.      * (must be different from <em>all</em> dates already used for transitions)
  104.      */
  105.     public void addValidAfter(final T entry, final AbsoluteDate earliestValidityDate) {

  106.         if (data.size() == 1) {
  107.             final Transition<T> single = data.first();
  108.             if (single.getBefore() == single.getAfter()) {
  109.                 // the single entry was a dummy one, without a real transition
  110.                 // we replace it entirely
  111.                 data.clear();
  112.                 data.add(new Transition<T>(earliestValidityDate, single.getBefore(), entry));
  113.                 return;
  114.             }
  115.         }

  116.         final Transition<T> next =
  117.                 data.ceiling(new Transition<T>(earliestValidityDate, entry, null));
  118.         if (next == null) {
  119.             // the new transition will be the last one
  120.             data.add(new Transition<T>(earliestValidityDate, data.last().getAfter(), entry));
  121.         } else {
  122.             // the new transition will be before the next one
  123.             data.remove(next);
  124.             data.add(new Transition<T>(earliestValidityDate, next.getBefore(), entry));
  125.             data.add(new Transition<T>(next.date,            entry,            next.getAfter()));
  126.         }

  127.     }

  128.     /** Get the entry valid at a specified date.
  129.      * @param date date at which the entry must be valid
  130.      * @return valid entry at specified date
  131.      */
  132.     public T get(final AbsoluteDate date) {
  133.         final Transition<T> previous = data.floor(new Transition<T>(date, null, null));
  134.         if (previous == null) {
  135.             // there are no transition before the specified date
  136.             // return the first valid entry
  137.             return data.first().getBefore();
  138.         } else {
  139.             return previous.getAfter();
  140.         }
  141.     }

  142.     /** Get the time span containing a specified date.
  143.      * @param date date belonging to the desired time span
  144.      * @return time span containing the specified date
  145.      * @since 9.3
  146.      */
  147.     public Span<T> getSpan(final AbsoluteDate date) {
  148.         final Transition<T> previous = data.floor(new Transition<T>(date, null, null));
  149.         if (previous == null) {
  150.             // there are no transition before the specified date
  151.             // return the first valid entry
  152.             return new Span<>(data.first().getBefore(),
  153.                               AbsoluteDate.PAST_INFINITY,
  154.                               data.first().getDate());
  155.         } else {
  156.             final Transition<T> next = data.higher(previous);
  157.             return new Span<>(previous.getAfter(),
  158.                               previous.getDate(),
  159.                               next == null ? AbsoluteDate.FUTURE_INFINITY : next.getDate());
  160.         }
  161.     }

  162.     /** Extract a range of the map.
  163.      * <p>
  164.      * The object returned will be a new independent instance that will contain
  165.      * only the transitions that lie in the specified range.
  166.      * </p>
  167.      * <p>
  168.      * Consider for example a map containing objects O₀ valid before t₁, O₁ valid
  169.      * between t₁ and t₂, O₂ valid between t₂ and t₃, O₃ valid between t₃ and t₄,
  170.      * and O₄ valid after t₄. then calling this method with a {@code start}
  171.      * date between t₁ and t₂ and a {@code end} date between t₃ and t₄
  172.      * will result in a new map containing objects O₁ valid before t₂, O₂
  173.      * valid between t₂ and t₃, and O₃ valid after t₃. The validity of O₁
  174.      * is therefore extended in the past, and the validity of O₃ is extended
  175.      * in the future.
  176.      * </p>
  177.      * @param start earliest date at which a transition is included in the range
  178.      * (may be set to {@link AbsoluteDate#PAST_INFINITY} to keep all early transitions)
  179.      * @param end latest date at which a transition is included in the r
  180.      * (may be set to {@link AbsoluteDate#FUTURE_INFINITY} to keep all late transitions)
  181.      * @return a new instance with all transitions restricted to the specified range
  182.      * @since 9.2
  183.      */
  184.     public TimeSpanMap<T> extractRange(final AbsoluteDate start, final AbsoluteDate end) {

  185.         final NavigableSet<Transition<T>> inRange = data.subSet(new Transition<T>(start, null, null), true,
  186.                                                                 new Transition<T>(end,   null, null), true);
  187.         if (inRange.isEmpty()) {
  188.             // there are no transitions at all in the range
  189.             // we need to pick up the only valid object
  190.             return new TimeSpanMap<>(get(start));
  191.         }

  192.         final TimeSpanMap<T> range = new TimeSpanMap<>(inRange.first().before);
  193.         for (final Transition<T> transition : inRange) {
  194.             range.addValidAfter(transition.after, transition.getDate());
  195.         }

  196.         return range;

  197.     }

  198.     /** Get an unmodifiable view of the sorted transitions.
  199.      * @return unmodifiable view of the sorted transitions
  200.      */
  201.     public NavigableSet<Transition<T>> getTransitions() {
  202.         return Collections.unmodifiableNavigableSet(data);
  203.     }

  204.     /**
  205.      * Performs an action for each element of map.
  206.      * <p>
  207.      * The action is performed chronologically.
  208.      * </p>
  209.      * @param action action to perform on the elements
  210.      * @since 10.3
  211.      */
  212.     public void forEach(final Consumer<T> action) {
  213.         boolean first = true;
  214.         for (Transition<T> transition : data) {
  215.             if (first) {
  216.                 if (transition.getBefore() != null) {
  217.                     action.accept(transition.getBefore());
  218.                 }
  219.                 first = false;
  220.             }
  221.             if (transition.getAfter() != null) {
  222.                 action.accept(transition.getAfter());
  223.             }
  224.         }
  225.     }

  226.     /** Class holding transition times.
  227.      * <p>
  228.      * This data type is dual to {@link Span}, it is
  229.      * focused one transition date, and gives access to
  230.      * surrounding valid data whereas {@link Span} is focused
  231.      * on one valid data, and gives access to surrounding
  232.      * transition dates.
  233.      * </p>
  234.      * @param <S> Type of the data.
  235.      */
  236.     public static class Transition<S> implements TimeStamped {

  237.         /** Transition date. */
  238.         private final AbsoluteDate date;

  239.         /** Entry valid before the transition. */
  240.         private final S before;

  241.         /** Entry valid after the transition. */
  242.         private final S after;

  243.         /** Simple constructor.
  244.          * @param date transition date
  245.          * @param before entry valid before the transition
  246.          * @param after entry valid after the transition
  247.          */
  248.         private Transition(final AbsoluteDate date, final S before, final S after) {
  249.             this.date   = date;
  250.             this.before = before;
  251.             this.after  = after;
  252.         }

  253.         /** Get the transition date.
  254.          * @return transition date
  255.          */
  256.         @Override
  257.         public AbsoluteDate getDate() {
  258.             return date;
  259.         }

  260.         /** Get the entry valid before transition.
  261.          * @return entry valid before transition
  262.          */
  263.         public S getBefore() {
  264.             return before;
  265.         }

  266.         /** Get the entry valid after transition.
  267.          * @return entry valid after transition
  268.          */
  269.         public S getAfter() {
  270.             return after;
  271.         }

  272.     }

  273.     /** Holder for one time span.
  274.      * <p>
  275.      * This data type is dual to {@link Transition}, it
  276.      * is focused on one valid data, and gives access to
  277.      * surrounding transition dates whereas {@link Transition}
  278.      * is focused on one transition date, and gives access to
  279.      * surrounding valid data.
  280.      * </p>
  281.      * @param <S> Type of the data.
  282.      * @since 9.3
  283.      */
  284.     public static class Span<S> {

  285.         /** Valid data. */
  286.         private final S data;

  287.         /** Start of validity for the data. */
  288.         private final AbsoluteDate start;

  289.         /** End of validity for the data. */
  290.         private final AbsoluteDate end;

  291.         /** Simple constructor.
  292.          * @param data valid data
  293.          * @param start start of validity for the data
  294.          * @param end end of validity for the data
  295.          */
  296.         private Span(final S data, final AbsoluteDate start, final AbsoluteDate end) {
  297.             this.data  = data;
  298.             this.start = start;
  299.             this.end   = end;
  300.         }

  301.         /** Get the data valid during this time span.
  302.          * @return data valid during this time span
  303.          */
  304.         public S getData() {
  305.             return data;
  306.         }

  307.         /** Get the start of this time span.
  308.          * @return start of this time span
  309.          */
  310.         public AbsoluteDate getStart() {
  311.             return start;
  312.         }

  313.         /** Get the end of this time span.
  314.          * @return end of this time span
  315.          */
  316.         public AbsoluteDate getEnd() {
  317.             return end;
  318.         }

  319.     }

  320. }