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 }