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