1   /* Contributed in the public domain.
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.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.stream.Stream;
24  
25  import org.hipparchus.exception.LocalizedCoreFormats;
26  import org.hipparchus.util.FastMath;
27  import org.orekit.errors.OrekitIllegalArgumentException;
28  import org.orekit.errors.OrekitIllegalStateException;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.errors.TimeStampedCacheException;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.ChronologicalComparator;
33  import org.orekit.time.TimeStamped;
34  
35  /**
36   * A cache of {@link TimeStamped} data that provides concurrency through
37   * immutability. This strategy is suitable when all of the cached data is stored
38   * in memory. (For example, {@link org.orekit.time.UTCScale UTCScale}) This
39   * class then provides convenient methods for accessing the data.
40   *
41   * @author Evan Ward
42   * @param <T>  the type of data
43   */
44  public class ImmutableTimeStampedCache<T extends TimeStamped>
45      implements TimeStampedCache<T> {
46  
47      /**
48       * A single chronological comparator since instances are thread safe.
49       */
50      private static final ChronologicalComparator CMP = new ChronologicalComparator();
51  
52      /**
53       * An empty immutable cache that always throws an exception on attempted
54       * access.
55       */
56      @SuppressWarnings("rawtypes")
57      private static final ImmutableTimeStampedCache EMPTY_CACHE =
58          new EmptyTimeStampedCache<TimeStamped>();
59  
60      /**
61       * the cached data. Be careful not to modify it after the constructor, or
62       * return a reference that allows mutating this list.
63       */
64      private final List<T> data;
65  
66      /**
67       * the size list to return from {@link #getNeighbors(AbsoluteDate)}.
68       */
69      private final int neighborsSize;
70  
71      /**
72       * Create a new cache with the given neighbors size and data.
73       *
74       * @param neighborsSize the size of the list returned from
75       *        {@link #getNeighbors(AbsoluteDate)}. Must be less than or equal to
76       *        {@code data.size()}.
77       * @param data the backing data for this cache. The list will be copied to
78       *        ensure immutability. To guarantee immutability the entries in
79       *        {@code data} must be immutable themselves. There must be more data
80       *        than {@code neighborsSize}.
81       * @throws IllegalArgumentException if {@code neightborsSize > data.size()}
82       *         or if {@code neighborsSize} is negative
83       */
84      public ImmutableTimeStampedCache(final int neighborsSize,
85                                       final Collection<? extends T> data) {
86          // parameter check
87          if (neighborsSize > data.size()) {
88              throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_CACHED_NEIGHBORS,
89                                                       data.size(), neighborsSize);
90          }
91          if (neighborsSize < 1) {
92              throw new OrekitIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
93                                                       neighborsSize, 0);
94          }
95  
96          // assign instance variables
97          this.neighborsSize = neighborsSize;
98          // sort and copy data first
99          this.data = new ArrayList<T>(data);
100         Collections.sort(this.data, CMP);
101     }
102 
103     /**
104      * private constructor for {@link #EMPTY_CACHE}.
105      */
106     private ImmutableTimeStampedCache() {
107         this.data = null;
108         this.neighborsSize = 0;
109     }
110 
111     /** {@inheritDoc} */
112     public Stream<T> getNeighbors(final AbsoluteDate central) {
113 
114         // find central index
115         final int i = findIndex(central);
116 
117         // check index in in the range of the data
118         if (i < 0) {
119             final AbsoluteDate earliest = this.getEarliest().getDate();
120             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE,
121                     earliest, central, earliest.durationFrom(central));
122         } else if (i >= this.data.size()) {
123             final AbsoluteDate latest = this.getLatest().getDate();
124             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER,
125                     latest, central, central.durationFrom(latest));
126         }
127 
128         // force unbalanced range if necessary
129         int start = FastMath.max(0, i - (this.neighborsSize - 1) / 2);
130         final int end = FastMath.min(this.data.size(), start +
131                                                        this.neighborsSize);
132         start = end - this.neighborsSize;
133 
134         // return list without copying
135         return this.data.subList(start, end).stream();
136     }
137 
138     /**
139      * Find the index, i, to {@link #data} such that {@code data[i] <= t} and
140      * {@code data[i+1] > t} if {@code data[i+1]} exists.
141      *
142      * @param t the time
143      * @return the index of the data at or just before {@code t}, {@code -1} if
144      *         {@code t} is before the first entry, or {@code data.size()} if
145      *         {@code t} is after the last entry.
146      */
147     private int findIndex(final AbsoluteDate t) {
148         // Guaranteed log(n) time
149         int i = Collections.binarySearch(this.data, t, CMP);
150         if (i == -this.data.size() - 1) {
151             // beyond last entry
152             i = this.data.size();
153         } else if (i < 0) {
154             // did not find exact match, but contained in data interval
155             i = -i - 2;
156         }
157         return i;
158     }
159 
160     /** {@inheritDoc} */
161     public int getNeighborsSize() {
162         return this.neighborsSize;
163     }
164 
165     /** {@inheritDoc} */
166     public T getEarliest() {
167         return this.data.get(0);
168     }
169 
170     /** {@inheritDoc} */
171     public T getLatest() {
172         return this.data.get(this.data.size() - 1);
173     }
174 
175     /**
176      * Get all of the data in this cache.
177      *
178      * @return a sorted collection of all data passed in the
179      *         {@link #ImmutableTimeStampedCache(int, Collection) constructor}.
180      */
181     public List<T> getAll() {
182         return Collections.unmodifiableList(this.data);
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public String toString() {
188         return "Immutable cache with " + this.data.size() + " entries";
189     }
190 
191     /**
192      * An empty immutable cache that always throws an exception on attempted
193      * access.
194      */
195     private static class EmptyTimeStampedCache<T extends TimeStamped> extends ImmutableTimeStampedCache<T> {
196 
197         /** {@inheritDoc} */
198         @Override
199         public Stream<T> getNeighbors(final AbsoluteDate central) {
200             throw new TimeStampedCacheException(OrekitMessages.NO_CACHED_ENTRIES);
201         }
202 
203         /** {@inheritDoc} */
204         @Override
205         public int getNeighborsSize() {
206             return 0;
207         }
208 
209         /** {@inheritDoc} */
210         @Override
211         public T getEarliest() {
212             throw new OrekitIllegalStateException(OrekitMessages.NO_CACHED_ENTRIES);
213         }
214 
215         /** {@inheritDoc} */
216         @Override
217         public T getLatest() {
218             throw new OrekitIllegalStateException(OrekitMessages.NO_CACHED_ENTRIES);
219         }
220 
221         /** {@inheritDoc} */
222         @Override
223         public List<T> getAll() {
224             return Collections.emptyList();
225         }
226 
227         /** {@inheritDoc} */
228         @Override
229         public String toString() {
230             return "Empty immutable cache";
231         }
232 
233     }
234 
235     /**
236      * Get an empty immutable cache, cast to the correct type.
237      * @param <TS>  the type of data
238      * @return an empty {@link ImmutableTimeStampedCache}.
239      */
240     @SuppressWarnings("unchecked")
241     public static final <TS extends TimeStamped> ImmutableTimeStampedCache<TS> emptyCache() {
242         return (ImmutableTimeStampedCache<TS>) EMPTY_CACHE;
243     }
244 
245 }