1   /* Copyright 2002-2021 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  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.concurrent.ExecutorService;
24  import java.util.concurrent.Executors;
25  import java.util.concurrent.TimeUnit;
26  import java.util.concurrent.atomic.AtomicReference;
27  import java.util.stream.Collectors;
28  
29  import org.hamcrest.CoreMatchers;
30  import org.hamcrest.MatcherAssert;
31  import org.hipparchus.random.RandomGenerator;
32  import org.hipparchus.random.Well1024a;
33  import org.junit.Assert;
34  import org.junit.Before;
35  import org.junit.Test;
36  import org.orekit.Utils;
37  import org.orekit.errors.TimeStampedCacheException;
38  import org.orekit.time.AbsoluteDate;
39  
40  
41  public class GenericTimeStampedCacheTest {
42  
43      @Test
44      public void testSingleCall() throws TimeStampedCacheException {
45          GenericTimeStampedCache<AbsoluteDate> cache = createCache(10, 3600.0, 13);
46          List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
47          list.add(AbsoluteDate.GALILEO_EPOCH);
48          Assert.assertEquals(1, checkDatesSingleThread(list, cache));
49          Assert.assertEquals(1, cache.getGetNeighborsCalls());
50          Assert.assertEquals(4, cache.getGenerateCalls());
51          Assert.assertEquals(0, cache.getSlotsEvictions());
52          Assert.assertEquals(10, cache.getMaxSlots());
53          Assert.assertEquals(Constants.JULIAN_DAY, cache.getNewSlotQuantumGap(), 1.0e-10);
54          Assert.assertEquals(Constants.JULIAN_YEAR, cache.getMaxSpan(), 1.0e-10);
55      }
56  
57      @Test
58      public void testPastInfinityRange() throws TimeStampedCacheException {
59          GenericTimeStampedCache<AbsoluteDate> cache =
60                  new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
61                                                     new Generator(AbsoluteDate.PAST_INFINITY,
62                                                                   AbsoluteDate.J2000_EPOCH,
63                                                                   10.0));
64          List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
65          list.add(AbsoluteDate.GALILEO_EPOCH);
66          list.add(AbsoluteDate.MODIFIED_JULIAN_EPOCH);
67          list.add(AbsoluteDate.JULIAN_EPOCH);
68          Assert.assertEquals(3, checkDatesSingleThread(list, cache));
69          Assert.assertEquals(3, cache.getGetNeighborsCalls());
70          try {
71              cache.getNeighbors(AbsoluteDate.J2000_EPOCH.shiftedBy(100.0));
72              Assert.fail("expected TimeStampedCacheException");
73          } catch (TimeStampedCacheException tce) {
74              // expected behavior
75          } catch (Exception e) {
76              Assert.fail("wrong exception caught");
77          }
78      }
79  
80      @Test
81      public void testFutureInfinityRange() throws TimeStampedCacheException {
82          GenericTimeStampedCache<AbsoluteDate> cache =
83                  new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
84                                                     new Generator(AbsoluteDate.MODIFIED_JULIAN_EPOCH,
85                                                                   AbsoluteDate.FUTURE_INFINITY, 10.0));
86          List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
87          list.add(AbsoluteDate.J2000_EPOCH);
88          list.add(AbsoluteDate.GALILEO_EPOCH);
89          Assert.assertEquals(2, checkDatesSingleThread(list, cache));
90          Assert.assertEquals(2, cache.getGetNeighborsCalls());
91          try {
92              cache.getNeighbors(AbsoluteDate.JULIAN_EPOCH);
93              Assert.fail("expected TimeStampedCacheException");
94          } catch (TimeStampedCacheException tce) {
95              // expected behavior
96          } catch (Exception e) {
97              Assert.fail("wrong exception caught");
98          }
99      }
100 
101     @Test
102     public void testInfinityRange() throws TimeStampedCacheException {
103         GenericTimeStampedCache<AbsoluteDate> cache =
104                 new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
105                                                    new Generator(AbsoluteDate.PAST_INFINITY,
106                                                                  AbsoluteDate.FUTURE_INFINITY,
107                                                                  10.0));
108         List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
109         list.add(AbsoluteDate.J2000_EPOCH.shiftedBy(+4.6e12));
110         list.add(AbsoluteDate.J2000_EPOCH.shiftedBy(-4.6e12));
111         list.add(AbsoluteDate.JULIAN_EPOCH);
112         list.add(AbsoluteDate.J2000_EPOCH);
113         list.add(AbsoluteDate.GALILEO_EPOCH);
114         Assert.assertEquals(5, checkDatesSingleThread(list, cache));
115         Assert.assertEquals(5, cache.getGetNeighborsCalls());
116     }
117 
118     @Test
119     public void testRegularCalls() throws TimeStampedCacheException {
120         GenericTimeStampedCache<AbsoluteDate> cache = createCache(2, 3600, 13);
121         Assert.assertEquals(2000, testMultipleSingleThread(cache, new SequentialMode(), 2));
122         Assert.assertEquals(2000, cache.getGetNeighborsCalls());
123         Assert.assertEquals(56, cache.getGenerateCalls());
124         Assert.assertEquals(0, cache.getSlotsEvictions());
125     }
126 
127     @Test
128     public void testAlternateCallsGoodConfiguration() throws TimeStampedCacheException {
129         GenericTimeStampedCache<AbsoluteDate> cache = createCache(2, 3600, 13);
130         Assert.assertEquals(2000, testMultipleSingleThread(cache, new AlternateMode(), 2));
131         Assert.assertEquals(2000, cache.getGetNeighborsCalls());
132         Assert.assertEquals(56, cache.getGenerateCalls());
133         Assert.assertEquals(0, cache.getSlotsEvictions());
134     }
135 
136     @Test
137     public void testAlternateCallsBadConfiguration() throws TimeStampedCacheException {
138         GenericTimeStampedCache<AbsoluteDate> cache = createCache(1, 3600, 13);
139         Assert.assertEquals(2000, testMultipleSingleThread(cache, new AlternateMode(), 2));
140         Assert.assertEquals(2000, cache.getGetNeighborsCalls());
141         Assert.assertEquals(8000, cache.getGenerateCalls());
142         Assert.assertEquals(1999, cache.getSlotsEvictions());
143     }
144 
145     @Test
146     public void testRandomCallsGoodConfiguration() throws TimeStampedCacheException {
147         GenericTimeStampedCache<AbsoluteDate> cache = createCache(30, 3600, 13);
148         Assert.assertEquals(5000, testMultipleSingleThread(cache, new RandomMode(64394632125212l), 5));
149         Assert.assertEquals(5000, cache.getGetNeighborsCalls());
150         Assert.assertTrue(cache.getGenerateCalls() < 250);
151         Assert.assertEquals(0, cache.getSlotsEvictions());
152     }
153 
154     @Test
155     public void testRandomCallsBadConfiguration() throws TimeStampedCacheException {
156         GenericTimeStampedCache<AbsoluteDate> cache = createCache(3, 3600, 13);
157         Assert.assertEquals(5000, testMultipleSingleThread(cache, new RandomMode(64394632125212l), 5));
158         Assert.assertEquals(5000, cache.getGetNeighborsCalls());
159         Assert.assertTrue(cache.getGenerateCalls()  > 400);
160         Assert.assertTrue(cache.getSlotsEvictions() > 300);
161     }
162 
163     @Test
164     public void testMultithreadedGoodConfiguration() throws TimeStampedCacheException {
165         GenericTimeStampedCache<AbsoluteDate> cache = createCache(50, 3600, 13);
166         int n = testMultipleMultiThread(cache, new AlternateMode(), 50, 30);
167         Assert.assertEquals(n, cache.getGetNeighborsCalls());
168         Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
169                           " (n = " + n + ", calls = " + cache.getGenerateCalls() +
170                           ", ratio = " + (n / cache.getGenerateCalls()) + ")",
171                           cache.getGenerateCalls() < n / 20);
172         Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
173                           " (n = " + n + ", evictions = " + cache.getSlotsEvictions() +
174                           (cache.getSlotsEvictions() == 0 ? "" : (", ratio = " + (n / cache.getSlotsEvictions()))) + ")",
175                           cache.getSlotsEvictions() < n / 1000);
176     }
177 
178     @Test
179     public void testMultithreadedBadConfiguration() throws TimeStampedCacheException {
180         GenericTimeStampedCache<AbsoluteDate> cache = createCache(3, 3600, 13);
181         int n = testMultipleMultiThread(cache, new AlternateMode(), 50, 100);
182         Assert.assertEquals(n, cache.getGetNeighborsCalls());
183         Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
184                           " (n = " + n + ", calls = " + cache.getGenerateCalls() +
185                           ", ratio = " + (n / cache.getGenerateCalls()) + ")",
186                           cache.getGenerateCalls() > n / 15);
187         Assert.assertTrue("this test may fail randomly due to multi-threading non-determinism" +
188                           " (n = " + n + ", evictions = " + cache.getSlotsEvictions() +
189                           ", ratio = " + (n / cache.getSlotsEvictions()) + ")",
190                           cache.getSlotsEvictions() > n / 60);
191     }
192 
193     @Test
194     public void testSmallShift() throws TimeStampedCacheException {
195         double hour = 3600;
196         GenericTimeStampedCache<AbsoluteDate> cache = createCache(10, hour, 13);
197         Assert.assertEquals(0, cache.getSlots());
198         Assert.assertEquals(0, cache.getEntries());
199         final AbsoluteDate start = AbsoluteDate.GALILEO_EPOCH;
200         cache.getNeighbors(start);
201         Assert.assertEquals(1, cache.getGetNeighborsCalls());
202         Assert.assertEquals(1, cache.getSlots());
203         Assert.assertEquals(18, cache.getEntries());
204         Assert.assertEquals(4, cache.getGenerateCalls());
205         Assert.assertEquals(-11 * hour, cache.getEarliest().durationFrom(start), 1.0e-10);
206         Assert.assertEquals( +6 * hour, cache.getLatest().durationFrom(start), 1.0e-10);
207         cache.getNeighbors(start.shiftedBy(-3 * 3600));
208         Assert.assertEquals(2, cache.getGetNeighborsCalls());
209         Assert.assertEquals(1, cache.getSlots());
210         Assert.assertEquals(18, cache.getEntries());
211         Assert.assertEquals(4, cache.getGenerateCalls());
212         Assert.assertEquals(-11 * hour, cache.getEarliest().durationFrom(start), 1.0e-10);
213         Assert.assertEquals( +6 * hour, cache.getLatest().durationFrom(start), 1.0e-10);
214         cache.getNeighbors(start.shiftedBy(7 * 3600));
215         Assert.assertEquals(3, cache.getGetNeighborsCalls());
216         Assert.assertEquals(1, cache.getSlots());
217         Assert.assertEquals(25, cache.getEntries());
218         Assert.assertEquals(5, cache.getGenerateCalls());
219         Assert.assertEquals(-11 * hour, cache.getEarliest().durationFrom(start), 1.0e-10);
220         Assert.assertEquals(+13 * hour, cache.getLatest().durationFrom(start), 1.0e-10);
221     }
222 
223     @Test(expected=IllegalArgumentException.class)
224     public void testNotEnoughSlots() {
225         createCache(0, 3600.0, 13);
226     }
227 
228     @Test(expected=IllegalArgumentException.class)
229     public void testNotEnoughNeighbors() {
230         createCache(10, 3600.0, 1);
231     }
232 
233     @Test(expected=IllegalStateException.class)
234     public void testNoEarliestEntry() {
235         createCache(10, 3600.0, 3).getEarliest();
236     }
237 
238     @Test(expected=IllegalStateException.class)
239     public void testNoLatestEntry() {
240         createCache(10, 3600.0, 3).getLatest();
241     }
242 
243     @Test(expected=TimeStampedCacheException.class)
244     public void testNoGeneratedData() throws TimeStampedCacheException {
245         TimeStampedGenerator<AbsoluteDate> nullGenerator =
246                 new TimeStampedGenerator<AbsoluteDate>() {
247             public List<AbsoluteDate> generate(AbsoluteDate existingDate,
248                                                AbsoluteDate date) {
249                 return new ArrayList<AbsoluteDate>();
250             }
251         };
252         new GenericTimeStampedCache<AbsoluteDate>(2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
253                                            nullGenerator).getNeighbors(AbsoluteDate.J2000_EPOCH);
254     }
255 
256     @Test
257     public void testNoDataBefore() throws TimeStampedCacheException {
258         TimeStampedGenerator<AbsoluteDate> nullGenerator =
259                 new TimeStampedGenerator<AbsoluteDate>() {
260                     public List<AbsoluteDate> generate(AbsoluteDate existingDate,
261                                                        AbsoluteDate date) {
262                         return Collections.singletonList(AbsoluteDate.J2000_EPOCH);
263                     }
264                 };
265         AbsoluteDate central = AbsoluteDate.J2000_EPOCH.shiftedBy(-10);
266         GenericTimeStampedCache<AbsoluteDate> cache = new GenericTimeStampedCache<>(
267                 2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY, nullGenerator);
268         try {
269             cache.getNeighbors(central);
270             Assert.fail("Expected Exception");
271         } catch (TimeStampedCacheException e) {
272             MatcherAssert.assertThat(e.getMessage(),
273                     CoreMatchers.containsString(central.toString()));
274         }
275     }
276 
277     @Test
278     public void testNoDataAfter() throws TimeStampedCacheException {
279         TimeStampedGenerator<AbsoluteDate> nullGenerator =
280                 new TimeStampedGenerator<AbsoluteDate>() {
281             public List<AbsoluteDate> generate(AbsoluteDate existingDate,
282                                                AbsoluteDate date) {
283                 return Collections.singletonList(AbsoluteDate.J2000_EPOCH);
284             }
285         };
286         AbsoluteDate central = AbsoluteDate.J2000_EPOCH.shiftedBy(+10);
287         GenericTimeStampedCache<AbsoluteDate> cache = new GenericTimeStampedCache<>(
288                 2, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY, nullGenerator);
289         try {
290             cache.getNeighbors(central);
291             Assert.fail("Expected Exception");
292         } catch (TimeStampedCacheException e) {
293             MatcherAssert.assertThat(e.getMessage(),
294                     CoreMatchers.containsString(central.toString()));
295         }
296     }
297 
298     @Test(expected=TimeStampedCacheException.class)
299     public void testUnsortedEntries() throws TimeStampedCacheException {
300         TimeStampedGenerator<AbsoluteDate> reversedGenerator =
301                 new TimeStampedGenerator<AbsoluteDate>() {
302             /** {@inheritDoc} */
303             public List<AbsoluteDate> generate(AbsoluteDate existingDate, AbsoluteDate date) {
304                 List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
305                 list.add(date);
306                 list.add(date.shiftedBy(-10.0));
307                 return list;
308             }
309         };
310 
311         new GenericTimeStampedCache<AbsoluteDate>(3, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
312                                            reversedGenerator).getNeighbors(AbsoluteDate.J2000_EPOCH);
313 
314     }
315 
316     @Test
317     public void testDuplicatingGenerator() throws TimeStampedCacheException {
318 
319         final double step = 3600.0;
320 
321         TimeStampedGenerator<AbsoluteDate> duplicatingGenerator =
322                 new TimeStampedGenerator<AbsoluteDate>() {
323 
324             /** {@inheritDoc} */
325             public List<AbsoluteDate> generate(AbsoluteDate existingDate, AbsoluteDate date) {
326                 List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
327                 if (existingDate == null) {
328                     list.add(date);
329                 } else {
330                     if (date.compareTo(existingDate) > 0) {
331                         AbsoluteDate t = existingDate.shiftedBy(-10 * step);
332                         do {
333                             t = t.shiftedBy(step);
334                             list.add(list.size(), t);
335                         } while (t.compareTo(date) <= 0);
336                     } else {
337                         AbsoluteDate t = existingDate.shiftedBy(10 * step);
338                         do {
339                             t = t.shiftedBy(-step);
340                             list.add(0, t);
341                         } while (t.compareTo(date) >= 0);
342                     }
343                 }
344                 return list;
345             }
346 
347         };
348 
349         final GenericTimeStampedCache<AbsoluteDate> cache =
350                 new GenericTimeStampedCache<AbsoluteDate>(5, 10, Constants.JULIAN_YEAR, Constants.JULIAN_DAY,
351                                                    duplicatingGenerator);
352 
353         final AbsoluteDate start = AbsoluteDate.GALILEO_EPOCH;
354         final List<AbsoluteDate> firstSet = cache.getNeighbors(start).collect(Collectors.toList());
355         Assert.assertEquals(5, firstSet.size());
356         Assert.assertEquals(4, cache.getGenerateCalls());
357         Assert.assertEquals(8, cache.getEntries());
358         for (int i = 1; i < firstSet.size(); ++i) {
359             Assert.assertEquals(step, firstSet.get(i).durationFrom(firstSet.get(i - 1)), 1.0e-10);
360         }
361 
362         final List<AbsoluteDate> secondSet = cache.getNeighbors(cache.getLatest().shiftedBy(10 * step)).collect(Collectors.toList());
363         Assert.assertEquals(5, secondSet.size());
364         Assert.assertEquals(7, cache.getGenerateCalls());
365         Assert.assertEquals(20, cache.getEntries());
366         for (int i = 1; i < secondSet.size(); ++i) {
367             Assert.assertEquals(step, firstSet.get(i).durationFrom(firstSet.get(i - 1)), 1.0e-10);
368         }
369 
370     }
371 
372     private int testMultipleSingleThread(GenericTimeStampedCache<AbsoluteDate> cache, Mode mode, int slots)
373         throws TimeStampedCacheException {
374         double step = ((Generator) cache.getGenerator()).getStep();
375         AbsoluteDate[] base = new AbsoluteDate[slots];
376         base[0] = AbsoluteDate.GALILEO_EPOCH;
377         for (int i = 1; i < base.length; ++i) {
378             base[i] = base[i - 1].shiftedBy(10 * Constants.JULIAN_DAY);
379         }
380         return checkDatesSingleThread(mode.generateDates(base, 25 * step, 0.025 * step), cache);
381     }
382 
383     private int testMultipleMultiThread(GenericTimeStampedCache<AbsoluteDate> cache, Mode mode,
384                                         int slots, int threadPoolSize)
385         throws TimeStampedCacheException {
386         double step = ((Generator) cache.getGenerator()).getStep();
387         AbsoluteDate[] base = new AbsoluteDate[slots];
388         base[0] = AbsoluteDate.GALILEO_EPOCH;
389         for (int i = 1; i < base.length; ++i) {
390             base[i] = base[i - 1].shiftedBy(10 * Constants.JULIAN_DAY);
391         }
392         return checkDatesMultiThread(mode.generateDates(base, 25 * step, 0.025 * step), cache, threadPoolSize);
393     }
394 
395     private GenericTimeStampedCache<AbsoluteDate> createCache(int maxSlots, double step, int neighborsSize) {
396         Generator generator =
397                 new Generator(AbsoluteDate.J2000_EPOCH.shiftedBy(-Constants.JULIAN_CENTURY),
398                               AbsoluteDate.J2000_EPOCH.shiftedBy(+Constants.JULIAN_CENTURY),
399                               step);
400         return new GenericTimeStampedCache<AbsoluteDate>(neighborsSize, maxSlots, Constants.JULIAN_YEAR,
401                                                   Constants.JULIAN_DAY, generator);
402     }
403 
404     private int checkDatesSingleThread(final List<AbsoluteDate> centralDates,
405                                        final GenericTimeStampedCache<AbsoluteDate> cache)
406         throws TimeStampedCacheException {
407 
408         final int n = cache.getNeighborsSize();
409         final double step = ((Generator) cache.getGenerator()).getStep();
410 
411         for (final AbsoluteDate central : centralDates) {
412             final List<AbsoluteDate> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
413             Assert.assertEquals(n, neighbors.size());
414             for (final AbsoluteDate date : neighbors) {
415                 Assert.assertTrue(date.durationFrom(central) >= -(n + 1) * step);
416                 Assert.assertTrue(date.durationFrom(central) <= n * step);
417             }
418         }
419 
420         return centralDates.size();
421 
422     }
423 
424     private int checkDatesMultiThread(final List<AbsoluteDate> centralDates,
425                                       final GenericTimeStampedCache<AbsoluteDate> cache,
426                                       final int threadPoolSize)
427         throws TimeStampedCacheException {
428 
429         final int n = cache.getNeighborsSize();
430         final double step = ((Generator) cache.getGenerator()).getStep();
431         final AtomicReference<AbsoluteDate[]> failedDates = new AtomicReference<AbsoluteDate[]>();
432         final AtomicReference<TimeStampedCacheException> caught = new AtomicReference<TimeStampedCacheException>();
433         ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
434 
435         for (final AbsoluteDate central : centralDates) {
436             executorService.execute(new Runnable() {
437                 public void run() {
438                     try {
439                         final List<AbsoluteDate> neighbors = cache.getNeighbors(central).collect(Collectors.toList());
440                         Assert.assertEquals(n, neighbors.size());
441                         for (final AbsoluteDate date : neighbors) {
442                             if (date.durationFrom(central) < -(n + 1) * step ||
443                                 date.durationFrom(central) > n * step) {
444                                 AbsoluteDate[] dates = new AbsoluteDate[n + 1];
445                                 dates[0] = central;
446                                 System.arraycopy(neighbors, 0, dates, 1, n);
447                                 failedDates.set(dates);
448                             }
449                         }
450                     } catch (TimeStampedCacheException tce) {
451                         caught.set(tce);
452                     }
453                 }
454             });
455         }
456 
457         try {
458             executorService.shutdown();
459             Assert.assertTrue(
460                     "Not enough time for all threads to complete, try increasing the timeout",
461                     executorService.awaitTermination(10, TimeUnit.MINUTES));
462         } catch (InterruptedException ie) {
463             Assert.fail(ie.getLocalizedMessage());
464         }
465 
466         if (caught.get() != null) {
467             throw caught.get();
468         }
469 
470         if (failedDates.get() != null) {
471             AbsoluteDate[] dates = failedDates.get();
472             StringBuilder builder = new StringBuilder();
473             String eol = System.getProperty("line.separator");
474             builder.append("central = ").append(dates[0]).append(eol);
475             builder.append("step = ").append(step).append(eol);
476             builder.append("neighbors =").append(eol);
477             for (int i = 1; i < dates.length; ++i) {
478                 builder.append("    ").append(dates[i]).append(eol);
479             }
480             Assert.fail(builder.toString());
481         }
482 
483         return centralDates.size();
484 
485     }
486 
487     private static class Generator implements TimeStampedGenerator<AbsoluteDate> {
488 
489         private final AbsoluteDate earliest;
490         private final AbsoluteDate latest;
491         private final double step;
492 
493         public Generator(final AbsoluteDate earliest, final AbsoluteDate latest, final double step) {
494             this.earliest = earliest;
495             this.latest   = latest;
496             this.step     = step;
497         }
498 
499         public double getStep() {
500             return step;
501         }
502 
503         public List<AbsoluteDate> generate(AbsoluteDate existingDate, AbsoluteDate date) {
504             List<AbsoluteDate> dates = new ArrayList<AbsoluteDate>();
505             if (existingDate == null) {
506                 dates.add(date);
507             } else if (date.compareTo(existingDate) >= 0) {
508                 AbsoluteDate previous = existingDate;
509                 while (date.compareTo(previous) > 0) {
510                     previous = previous.shiftedBy(step);
511                     if (previous.compareTo(earliest) >= 0 && previous.compareTo(latest) <= 0) {
512                         dates.add(dates.size(), previous);
513                     }
514                 }
515             } else {
516                 AbsoluteDate previous = existingDate;
517                 while (date.compareTo(previous) < 0) {
518                     previous = previous.shiftedBy(-step);
519                     if (previous.compareTo(earliest) >= 0 && previous.compareTo(latest) <= 0) {
520                         dates.add(0, previous);
521                     }
522                 }
523             }
524             return dates;
525         }
526 
527     }
528 
529     private interface Mode {
530         List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step);
531     }
532 
533     private class SequentialMode implements Mode {
534 
535         public List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step) {
536             List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
537             for (final AbsoluteDate initial : base) {
538                 for (double dt = 0; dt < duration; dt += step) {
539                     list.add(initial.shiftedBy(dt));
540                 }
541             }
542             return list;
543         }
544 
545     }
546 
547     private class AlternateMode implements Mode {
548 
549         public List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step) {
550             List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
551             for (double dt = 0; dt < duration; dt += step) {
552                 for (final AbsoluteDate initial : base) {
553                     list.add(initial.shiftedBy(dt));
554                 }
555             }
556             return list;
557         }
558 
559     }
560 
561     private class RandomMode implements Mode {
562 
563         private RandomGenerator random;
564 
565         public RandomMode(long seed) {
566             random = new Well1024a(seed);
567         }
568 
569         public List<AbsoluteDate> generateDates(AbsoluteDate[] base, double duration, double step) {
570             List<AbsoluteDate> list = new ArrayList<AbsoluteDate>();
571             for (int i = 0; i < base.length * duration / step; ++i) {
572                 int j     = random.nextInt(base.length);
573                 double dt = random.nextDouble() * duration;
574                     list.add(base[j].shiftedBy(dt));
575             }
576             return list;
577         }
578 
579     }
580 
581     @Before
582     public void setUp() {
583         Utils.setDataRoot("regular-data");
584     }
585 }