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.function.Function;
24  import java.util.stream.Stream;
25  
26  import org.hipparchus.CalculusFieldElement;
27  import org.hipparchus.exception.LocalizedCoreFormats;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitIllegalArgumentException;
30  import org.orekit.errors.OrekitIllegalStateException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.errors.TimeStampedCacheException;
33  import org.orekit.time.AbsoluteDate;
34  import org.orekit.time.ChronologicalComparator;
35  import org.orekit.time.FieldTimeStamped;
36  import org.orekit.time.TimeStamped;
37  
38  /**
39   * A cache of {@link TimeStamped} data that provides concurrency through
40   * immutability. This strategy is suitable when all of the cached data is stored
41   * in memory. (For example, {@link org.orekit.time.UTCScale UTCScale}) This
42   * class then provides convenient methods for accessing the data.
43   *
44   * @author Evan Ward
45   * @param <T>  the type of data
46   */
47  public class ImmutableTimeStampedCache<T extends TimeStamped>
48      implements TimeStampedCache<T> {
49  
50      /**
51       * An empty immutable cache that always throws an exception on attempted
52       * access.
53       */
54      @SuppressWarnings("rawtypes")
55      private static final ImmutableTimeStampedCache EMPTY_CACHE =
56          new EmptyTimeStampedCache<>();
57  
58      /**
59       * the cached data. Be careful not to modify it after the constructor, or
60       * return a reference that allows mutating this list.
61       */
62      private final List<T> data;
63  
64      /**
65       * the maximum size list to return from {@link #getNeighbors(AbsoluteDate, int)}.
66       * @since 12.0
67       */
68      private final int maxNeighborsSize;
69  
70      /**
71       * Create a new cache with the given neighbors size and data.
72       *
73       * @param maxNeighborsSize the maximum size of the list returned from
74       *        {@link #getNeighbors(AbsoluteDate, int)}. Must be less than or equal to
75       *        {@code data.size()}.
76       * @param data the backing data for this cache. The list will be copied to
77       *        ensure immutability. To guarantee immutability the entries in
78       *        {@code data} must be immutable themselves. There must be more data
79       *        than {@code maxNeighborsSize}.
80       * @throws IllegalArgumentException if {@code neightborsSize > data.size()}
81       *         or if {@code neighborsSize} is negative
82       */
83      public ImmutableTimeStampedCache(final int maxNeighborsSize,
84                                       final Collection<? extends T> data) {
85          // parameter check
86          if (maxNeighborsSize > data.size()) {
87              throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_CACHED_NEIGHBORS,
88                                                       data.size(), maxNeighborsSize);
89          }
90          if (maxNeighborsSize < 1) {
91              throw new OrekitIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
92                                                       maxNeighborsSize, 1);
93          }
94  
95          // assign instance variables
96          this.maxNeighborsSize = maxNeighborsSize;
97          // sort and copy data first
98          this.data = new ArrayList<>(data);
99          this.data.sort(new ChronologicalComparator());
100 
101     }
102 
103     /**
104      * private constructor for {@link #EMPTY_CACHE}.
105      */
106     private ImmutableTimeStampedCache() {
107         this.data             = null;
108         this.maxNeighborsSize = 0;
109     }
110 
111     /** {@inheritDoc} */
112     public Stream<T> getNeighbors(final AbsoluteDate central, final int n) {
113         if (n > maxNeighborsSize) {
114             throw new OrekitException(OrekitMessages.NOT_ENOUGH_DATA, maxNeighborsSize);
115         }
116         return new SortedListTrimmer(n).getNeighborsSubList(central, data).stream();
117     }
118 
119     /** {@inheritDoc} */
120     public int getMaxNeighborsSize() {
121         return maxNeighborsSize;
122     }
123 
124     /** {@inheritDoc} */
125     public T getEarliest() {
126         return this.data.getFirst();
127     }
128 
129     /** {@inheritDoc} */
130     public T getLatest() {
131         return this.data.get(this.data.size() - 1);
132     }
133 
134     /**
135      * Get all of the data in this cache.
136      *
137      * @return a sorted collection of all data passed in the
138      *         {@link #ImmutableTimeStampedCache(int, Collection) constructor}.
139      */
140     public List<T> getAll() {
141         return Collections.unmodifiableList(this.data);
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     public String toString() {
147         return "Immutable cache with " + this.data.size() + " entries";
148     }
149 
150     /** Get a field version of the instance.
151      * @param <C> cache elements
152      * @param <F> field elements
153      * @param converter converter to field elements
154      * @return field version
155      * @since 14.0
156      */
157     public <C extends FieldTimeStamped<F>, F extends CalculusFieldElement<F>>
158         ImmutableFieldTimeStampedCache<C, F> toField(final Function<T, C> converter) {
159         final List<C> fieldData = new ArrayList<>(data.size());
160         for (int i = 0; i < data.size(); i++) {
161             fieldData.add(converter.apply(data.get(i)));
162         }
163         return new ImmutableFieldTimeStampedCache<>(maxNeighborsSize, fieldData);
164     }
165 
166     /**
167      * An empty immutable cache that always throws an exception on attempted
168      * access.
169      */
170     private static class EmptyTimeStampedCache<T extends TimeStamped> extends ImmutableTimeStampedCache<T> {
171 
172         /** {@inheritDoc} */
173         @Override
174         public Stream<T> getNeighbors(final AbsoluteDate central) {
175             throw new TimeStampedCacheException(OrekitMessages.NO_CACHED_ENTRIES);
176         }
177 
178         /** {@inheritDoc} */
179         @Override
180         public int getMaxNeighborsSize() {
181             return 0;
182         }
183 
184         /** {@inheritDoc} */
185         @Override
186         public T getEarliest() {
187             throw new OrekitIllegalStateException(OrekitMessages.NO_CACHED_ENTRIES);
188         }
189 
190         /** {@inheritDoc} */
191         @Override
192         public T getLatest() {
193             throw new OrekitIllegalStateException(OrekitMessages.NO_CACHED_ENTRIES);
194         }
195 
196         /** {@inheritDoc} */
197         @Override
198         public List<T> getAll() {
199             return Collections.emptyList();
200         }
201 
202         /** {@inheritDoc} */
203         @Override
204         public String toString() {
205             return "Empty immutable cache";
206         }
207 
208     }
209 
210     /**
211      * Get an empty immutable cache, cast to the correct type.
212      * @param <TS>  the type of data
213      * @return an empty {@link ImmutableTimeStampedCache}.
214      */
215     @SuppressWarnings("unchecked")
216     public static <TS extends TimeStamped> ImmutableTimeStampedCache<TS> emptyCache() {
217         return (ImmutableTimeStampedCache<TS>) EMPTY_CACHE;
218     }
219 
220 }