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;
18  
19  import org.hamcrest.Description;
20  import org.hamcrest.Matcher;
21  import org.hamcrest.Matchers;
22  import org.hamcrest.SelfDescribing;
23  import org.hamcrest.TypeSafeDiagnosingMatcher;
24  import org.hamcrest.TypeSafeMatcher;
25  import org.hipparchus.geometry.euclidean.threed.Rotation;
26  import org.hipparchus.geometry.euclidean.threed.Vector3D;
27  import org.hipparchus.linear.RealMatrix;
28  import org.hipparchus.util.FastMath;
29  import org.hipparchus.util.Precision;
30  import org.orekit.attitudes.Attitude;
31  import org.orekit.bodies.GeodeticPoint;
32  import org.orekit.time.AbsoluteDate;
33  import org.orekit.utils.Constants;
34  import org.orekit.utils.PVCoordinates;
35  
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collection;
39  
40  import static org.hamcrest.CoreMatchers.is;
41  import static org.hamcrest.Matchers.array;
42  
43  /**
44   * A set of matchers specific to the Orekit value classes.
45   *
46   * @author Evan Ward
47   */
48  public class OrekitMatchers {
49  
50      /**
51       * Match a geodetic point
52       *
53       * @param lat latitude matcher, in radians
54       * @param lon longitude matcher, in radians
55       * @param alt altitude matcher, in meters
56       * @return a {@link GeodeticPoint} matcher
57       */
58      public static Matcher<GeodeticPoint> geodeticPoint(
59              final Matcher<Double> lat,
60              final Matcher<Double> lon,
61              final Matcher<Double> alt) {
62          return new TypeSafeDiagnosingMatcher<GeodeticPoint>() {
63              @Override
64              public void describeTo(Description description) {
65                  description.appendList("GeodeticPoint[", ", ", "]",
66                          Arrays.<SelfDescribing>asList(lat, lon, alt));
67              }
68  
69              @Override
70              protected boolean matchesSafely(GeodeticPoint item,
71                                              Description mismatchDescription) {
72                  if (!lat.matches(item.getLatitude())) {
73                      mismatchDescription.appendText("the latitude ");
74                      lat.describeMismatch(item.getLatitude(),
75                              mismatchDescription);
76                      return false;
77                  }
78                  if (!lon.matches(item.getLongitude())) {
79                      mismatchDescription.appendText("the longitude ");
80                      lon.describeMismatch(item.getLongitude(),
81                              mismatchDescription);
82                      return false;
83                  }
84                  if (!alt.matches(item.getAltitude())) {
85                      mismatchDescription.appendText("the altitude ");
86                      alt.describeMismatch(item.getAltitude(),
87                              mismatchDescription);
88                      return false;
89                  }
90                  return true;
91              }
92          };
93      }
94  
95      /**
96       * Match a geodetic point
97       *
98       * @param lat latitude, in radians
99       * @param lon longitude, in radians
100      * @param alt altitude, in meters
101      * @return matcher of a {@link GeodeticPoint}
102      */
103     public static Matcher<GeodeticPoint> geodeticPoint(double lat,
104                                                        double lon,
105                                                        double alt) {
106         return geodeticPoint(is(lat), is(lon), is(alt));
107     }
108 
109     /**
110      * Match a geodetic point by comparing it with another one.
111      *
112      * @param expected the expected value
113      * @param absTol   the absolute tolerance on the comparison, in meters.
114      *                 Differences less than this value will be ignored.
115      * @return a {@link GeodeticPoint} matcher
116      */
117     public static Matcher<GeodeticPoint> geodeticPointCloseTo(
118             GeodeticPoint expected, double absTol) {
119         double angularAbsTol = absTol / Constants.WGS84_EARTH_EQUATORIAL_RADIUS;
120         return geodeticPoint(closeTo(expected.getLatitude(), angularAbsTol),
121                 closeTo(expected.getLongitude(), angularAbsTol),
122                 closeTo(expected.getAltitude(), absTol));
123     }
124 
125     /**
126      * Match a geodetic point by comparing it with another one.
127      *
128      * @param expected the expected value
129      * @param ulps     the ulps difference allowed
130      * @return a {@link GeodeticPoint} matcher
131      * @see #relativelyCloseTo
132      */
133     public static Matcher<GeodeticPoint> geodeticPointCloseTo(
134             GeodeticPoint expected, int ulps) {
135         return geodeticPoint(relativelyCloseTo(expected.getLatitude(), ulps),
136                 relativelyCloseTo(expected.getLongitude(), ulps),
137                 relativelyCloseTo(expected.getAltitude(), ulps));
138     }
139 
140     /**
141      * Matches a {@link Vector3D} based on its three coordinates.
142      *
143      * @param x matcher for the x coordinate
144      * @param y matcher for the y coordinate
145      * @param z matcher for the z coordinate
146      * @return a vector matcher
147      */
148     public static Matcher<Vector3D> vector(final Matcher<Double> x,
149                                            final Matcher<Double> y,
150                                            final Matcher<Double> z) {
151         return new TypeSafeDiagnosingMatcher<Vector3D>() {
152             @Override
153             public void describeTo(Description description) {
154                 description.appendList("Vector3D[", ", ", "]",
155                         Arrays.<SelfDescribing>asList(x, y, z));
156             }
157 
158             @Override
159             protected boolean matchesSafely(Vector3D item,
160                                             Description mismatchDescription) {
161                 if (!x.matches(item.getX())) {
162                     mismatchDescription.appendText("the x coordinate ");
163                     x.describeMismatch(item.getX(), mismatchDescription);
164                     return false;
165                 }
166                 if (!y.matches(item.getY())) {
167                     mismatchDescription.appendText("the y coordinate ");
168                     y.describeMismatch(item.getY(), mismatchDescription);
169                     return false;
170                 }
171                 if (!z.matches(item.getZ())) {
172                     mismatchDescription.appendText("the z coordinate ");
173                     z.describeMismatch(item.getZ(), mismatchDescription);
174                     return false;
175                 }
176                 return true;
177             }
178         };
179     }
180 
181     /**
182      * Matches a {@link Vector3D} close to another one.
183      *
184      * @param vector the reference vector
185      * @param absTol absolute tolerance of comparison, in each dimension
186      * @return a vector matcher.
187      */
188     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, double absTol) {
189         return vector(closeTo(vector.getX(), absTol),
190                 closeTo(vector.getY(), absTol), closeTo(vector.getZ(), absTol));
191     }
192 
193     /**
194      * Matches a {@link Vector3D} close to another one.
195      *
196      * @param vector the reference vector
197      * @param ulps   the relative tolerance, in units in last place, of the
198      *               Comparison of each dimension.
199      * @return a vector matcher.
200      */
201     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, int ulps) {
202         return vector(relativelyCloseTo(vector.getX(), ulps),
203                 relativelyCloseTo(vector.getY(), ulps),
204                 relativelyCloseTo(vector.getZ(), ulps));
205     }
206 
207     /**
208      * Alias for {@link #vectorCloseTo(Vector3D, int)}
209      *
210      * @param x    the x component
211      * @param y    the y component
212      * @param z    the z component
213      * @param ulps the relative tolerance, in ulps
214      * @return a vector matcher
215      */
216     public static Matcher<Vector3D> vectorCloseTo(double x, double y, double z, int ulps) {
217         return vectorCloseTo(new Vector3D(x, y, z), ulps);
218     }
219 
220     /**
221      * Matches a {@link Vector3D} to another one.
222      *
223      * @param vector the reference vector
224      * @param absTol the absolute tolerance of comparison, in each dimension.
225      * @param ulps   the relative tolerance of comparison in each dimension, in
226      *               units in last place.
227      * @return a matcher that matches if either the absolute or relative
228      * comparison matches in each dimension.
229      */
230     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector,
231                                                   double absTol, int ulps) {
232         return vector(numberCloseTo(vector.getX(), absTol, ulps),
233                 numberCloseTo(vector.getY(), absTol, ulps),
234                 numberCloseTo(vector.getZ(), absTol, ulps));
235     }
236 
237     /**
238      * Match a {@link PVCoordinates}
239      *
240      * @param position matcher for the position
241      * @param velocity matcher for the velocity
242      * @return a matcher of {@link PVCoordinates}
243      */
244     public static Matcher<PVCoordinates> pvIs(
245             final Matcher<? super Vector3D> position,
246             final Matcher<? super Vector3D> velocity) {
247         return new TypeSafeDiagnosingMatcher<PVCoordinates>() {
248 
249             @Override
250             public void describeTo(Description description) {
251                 description.appendText("position ");
252                 description.appendDescriptionOf(position);
253                 description.appendText(" and velocity ");
254                 description.appendDescriptionOf(velocity);
255             }
256 
257             @Override
258             protected boolean matchesSafely(PVCoordinates item,
259                     Description mismatchDescription) {
260                 if (!position.matches(item.getPosition())) {
261                     // position doesn't match
262                     mismatchDescription.appendText("position ");
263                     position.describeMismatch(item.getPosition(), mismatchDescription);
264                     return false;
265                 } else if (!velocity.matches(item.getVelocity())) {
266                     // velocity doesn't match
267                     mismatchDescription.appendText("velocity ");
268                     velocity.describeMismatch(item.getVelocity(), mismatchDescription);
269                     return false;
270                 } else {
271                     // both p and v matched
272                     return true;
273                 }
274             }
275         };
276     }
277 
278     /**
279      * Check that a {@link PVCoordinates} is the same as another one.
280      *
281      * @param pv the reference {@link PVCoordinates}
282      * @return a {@link PVCoordinates} {@link Matcher}
283      */
284     public static Matcher<PVCoordinates> pvIs(PVCoordinates pv) {
285         return pvCloseTo(pv, 0);
286     }
287 
288     /**
289      * Match a {@link PVCoordinates} close to another one.
290      *
291      * @param pv     the reference {@link PVCoordinates}
292      * @param absTol distance a matched {@link PVCoordinates} can be from the reference in
293      *               any one coordinate.
294      * @return a matcher of {@link PVCoordinates}.
295      */
296     public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv,
297                                                    double absTol) {
298         return pvIs(vectorCloseTo(pv.getPosition(), absTol),
299                 vectorCloseTo(pv.getVelocity(), absTol));
300     }
301 
302     /**
303      * Match a {@link PVCoordinates} close to another one.
304      *
305      * @param pv   the reference {@link PVCoordinates}
306      * @param ulps the units in last place any coordinate can be off by.
307      * @return a matcher of {@link PVCoordinates}.
308      */
309     public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv, int ulps) {
310         return pvIs(vectorCloseTo(pv.getPosition(), ulps),
311                 vectorCloseTo(pv.getVelocity(), ulps));
312     }
313 
314     /**
315      * Checks if two numbers are relatively close to each other. For absolute
316      * comparisons, see {@link #closeTo(double, double)}.
317      *
318      * @param expected the expected value in the relative comparison
319      * @param ulps     the units in last place of {@code expected} the two
320      *                 numbers can be off by.
321      * @return a matcher of numbers
322      */
323     public static Matcher<Double> relativelyCloseTo(final double expected,
324                                                     final int ulps) {
325         return new TypeSafeDiagnosingMatcher<Double>() {
326 
327             @Override
328             public void describeTo(Description description) {
329                 description.appendText("a numeric value within ")
330                         .appendValue(ulps).appendText(" ulps of ")
331                         .appendValue(expected);
332             }
333 
334             @Override
335             protected boolean matchesSafely(Double item,
336                                             Description mismatchDescription) {
337                 if (!Precision.equals(item, expected, ulps)) {
338                     mismatchDescription
339                             .appendValue(item)
340                             .appendText(" was off by ")
341                             .appendValue(
342                                     Double.doubleToLongBits(item)
343                                             - Double.doubleToLongBits(expected))
344                             .appendText(" ulps");
345                     return false;
346                 }
347                 return true;
348             }
349         };
350     }
351 
352     /**
353      * Check a number is close to another number using a relative
354      * <strong>or</strong> absolute comparison.
355      *
356      * @param number the expected value
357      * @param absTol absolute tolerance of comparison
358      * @param ulps   units in last place tolerance for relative comparison
359      * @return a matcher that matches if the differences is less than or equal
360      * to absTol <strong>or</strong> the two numbers differ by less or equal
361      * ulps.
362      */
363     public static Matcher<Double> numberCloseTo(double number, double absTol,
364                                                 int ulps) {
365         Collection<Matcher<Double>> matchers = new ArrayList<>(2);
366         matchers.add(closeTo(number, absTol));
367         matchers.add(relativelyCloseTo(number, ulps));
368         return anyOf(matchers);
369     }
370 
371     /**
372      * match a double array based on matchers for each element.
373      *
374      * @param matchers matcher for each element.
375      * @return a matcher for a double[]
376      * @see org.hamcrest.collection.IsArray
377      */
378     @SafeVarargs
379     public static Matcher<double[]> doubleArrayContaining(
380             Matcher<? super Double>... matchers) {
381         return new TypeSafeDiagnosingMatcher<double[]>() {
382 
383             @Override
384             public void describeTo(Description description) {
385                 description.appendList("[", ", ", "]", Arrays.<Matcher<?>>asList(matchers));
386             }
387 
388             @Override
389             protected boolean matchesSafely(double[] actual,
390                                             Description mismatchDescription) {
391                 if (actual.length != matchers.length) {
392                     mismatchDescription.appendText("array length was " + actual.length);
393                     return false;
394                 }
395                 for (int i = 0; i < actual.length; i++) {
396                     if (!matchers[i].matches(actual[i])) {
397                         mismatchDescription.appendText("in element " + i + " ");
398                         matchers[i].describeMismatch(actual[i],
399                                 mismatchDescription);
400                         return false;
401                     }
402                 }
403                 return true;
404             }
405         };
406     }
407 
408     /**
409      * Check that the double[] contains exactly the specified values.
410      *
411      * @param values the expected values.
412      * @return matcher for double[]s
413      * @see Matchers#arrayContaining(Object...)
414      */
415     public static Matcher<double[]> doubleArrayContaining(double... values) {
416         @SuppressWarnings("unchecked")
417         Matcher<Double>[] elementMatchers = new Matcher[values.length];
418         for (int i = 0; i < elementMatchers.length; i++) {
419             elementMatchers[i] = Matchers.is(values[i]);
420         }
421         return doubleArrayContaining(elementMatchers);
422     }
423 
424     /**
425      * Check that a double[] contains the specified values to within an absolute
426      * tolerance.
427      *
428      * @param values the expected values
429      * @param absTol the absolute tolerance used in comparisons
430      * @return a {@link Matcher} for double[]'s
431      */
432     public static Matcher<double[]> doubleArrayContaining(
433             final double[] values, final double absTol) {
434         @SuppressWarnings("unchecked")
435         Matcher<Double>[] elementMatchers = new Matcher[values.length];
436         for (int i = 0; i < elementMatchers.length; i++) {
437             elementMatchers[i] = closeTo(values[i], absTol);
438         }
439         return doubleArrayContaining(elementMatchers);
440     }
441 
442     /**
443      * Check that a double[] contains the specified values to within a relative
444      * tolerance.
445      *
446      * @param values the expected values
447      * @param ulps   the units in last place the values can be off by. These are ulps of
448      *               the expected values. Specifying 1 ulp corresponds to ~16 decimal
449      *               digits of accuracy.
450      * @return a {@link Matcher} for a double[]
451      */
452     public static Matcher<double[]> doubleArrayContaining(
453             final double[] values, final int ulps) {
454         @SuppressWarnings("unchecked")
455         Matcher<Double>[] elementMatchers = new Matcher[values.length];
456         for (int i = 0; i < elementMatchers.length; i++) {
457             elementMatchers[i] = relativelyCloseTo(values[i], ulps);
458         }
459         return doubleArrayContaining(elementMatchers);
460     }
461 
462     /**
463      * Check that a double[] contains the specified values to within a relative tolerance
464      * or absolute tolerance. Specifically, each comparison will pass if the difference is
465      * less than {@code absTol} <em>Or</em> the difference, expressed in ulps is less than
466      * {@code ulps}.
467      *
468      * @param values the expected values
469      * @param absTol absolute tolerance on comparisons
470      * @param ulps   relative tolerance, in units in last place
471      * @return matcher for the double array
472      */
473     public static Matcher<double[]> doubleArrayContaining(
474             final double[] values, final double absTol, final int ulps) {
475         @SuppressWarnings("unchecked")
476         Matcher<Double>[] elementMatchers = new Matcher[values.length];
477         for (int i = 0; i < elementMatchers.length; i++) {
478             elementMatchers[i] = numberCloseTo(values[i], absTol, ulps);
479         }
480         return doubleArrayContaining(elementMatchers);
481     }
482 
483     /**
484      * Match a {@link RealMatrix#getData()} as a double[][].
485      *
486      * @param dataMatcher matcher for the data as a double[][]
487      * @return a {@link RealMatrix} matcher
488      */
489     public static Matcher<RealMatrix> matrix(
490             final Matcher<double[][]> dataMatcher) {
491         return new TypeSafeDiagnosingMatcher<RealMatrix>() {
492             @Override
493             public void describeTo(Description description) {
494                 description.appendText("matrix that ").appendDescriptionOf(
495                         dataMatcher);
496             }
497 
498             @Override
499             protected boolean matchesSafely(RealMatrix item,
500                                             Description mismatchDescription) {
501                 if (dataMatcher.matches(item.getData())) {
502                     return true;
503                 } else {
504                     mismatchDescription.appendText("matrix did not match ");
505                     dataMatcher.describeMismatch(item.getData(),
506                             mismatchDescription);
507                     return false;
508                 }
509             }
510         };
511     }
512 
513     /**
514      * Match a {@link RealMatrix} to within the given absolute tolerance
515      *
516      * @param expected the expected values
517      * @param absTol   the absolute tolerance on the comparison
518      * @return a matrix matcher
519      * @see #matrix(Matcher)
520      * @see #doubleArrayContaining(double[], double)
521      */
522     public static Matcher<RealMatrix> matrixCloseTo(RealMatrix expected,
523                                                     double absTol) {
524         @SuppressWarnings("rawtypes")
525         Matcher[] rowMatchers = new Matcher[expected.getRowDimension()];
526         for (int i = 0; i < rowMatchers.length; i++) {
527             rowMatchers[i] = doubleArrayContaining(expected.getRow(i), absTol);
528         }
529         @SuppressWarnings("unchecked")
530         Matcher<double[][]> dataMatcher = array(rowMatchers);
531         return matrix(dataMatcher);
532     }
533 
534     /**
535      * Match a {@link RealMatrix} to within the given absolute tolerance
536      *
537      * @param expected the expected values
538      * @param absTol   the absolute tolerance on the comparison
539      * @param ulps     the maximum allowable difference, in units in last place
540      * @return a matrix matcher
541      * @see #matrix(Matcher)
542      * @see #doubleArrayContaining(double[], double)
543      */
544     public static Matcher<RealMatrix> matrixCloseTo(RealMatrix expected,
545                                                     double absTol, int ulps) {
546         @SuppressWarnings("rawtypes")
547         Matcher[] rowMatchers = new Matcher[expected.getRowDimension()];
548         for (int i = 0; i < rowMatchers.length; i++) {
549             rowMatchers[i] = doubleArrayContaining(expected.getRow(i), absTol,
550                     ulps);
551         }
552         @SuppressWarnings("unchecked")
553         Matcher<double[][]> dataMatcher = array(rowMatchers);
554         return matrix(dataMatcher);
555     }
556 
557     /**
558      * Create a matcher that matches if at least one of the given matchers match. Gives
559      * better descriptions that {@link org.hamcrest.CoreMatchers#anyOf(Iterable)}.
560      *
561      * @param matchers to try.
562      * @param <T>      type of object to match.
563      * @return a new matcher.
564      */
565     public static <T> Matcher<T> anyOf(Collection<? extends Matcher<? super T>> matchers) {
566         return new TypeSafeDiagnosingMatcher<T>() {
567             @Override
568             protected boolean matchesSafely(final T item,
569                                             final Description mismatchDescription) {
570                 boolean first = true;
571                 for (Matcher<? super T> matcher : matchers) {
572                     if (matcher.matches(item)) {
573                         return true;
574                     } else {
575                         if (!first) {
576                             mismatchDescription.appendText(" and ");
577                         }
578                         matcher.describeMismatch(item, mismatchDescription);
579                     }
580                     first = false;
581                 }
582                 return false;
583             }
584 
585             @Override
586             public void describeTo(final Description description) {
587                 description.appendList("(", " or ", ")", matchers);
588             }
589         };
590     }
591 
592     /* Copid from Hamcrest's IsCloseTo under the new BSD license.
593      * Copyright (c) 2000-2006 hamcrest.org
594      */
595 
596     /**
597      * Creates a matcher of {@link Double}s that matches when an examined double
598      * is equal to the specified <code>operand</code>, within a range of +/-
599      * <code>error</code>. <p/> For example:
600      * <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
601      *
602      * @param value the expected value of matching doubles
603      * @param delta the delta (+/-) within which matches will be allowed
604      * @return a double matcher.
605      */
606     public static Matcher<Double> closeTo(final double value,
607                                           final double delta) {
608 
609         return new TypeSafeMatcher<Double>() {
610             @Override
611             public boolean matchesSafely(Double item) {
612                 return actualDelta(item) <= 0.0;
613             }
614 
615             @Override
616             public void describeMismatchSafely(Double item, Description mismatchDescription) {
617                 mismatchDescription.appendValue(item)
618                         .appendText(" differed by ")
619                         .appendValue(actualDelta(item));
620             }
621 
622             @Override
623             public void describeTo(Description description) {
624                 description.appendText("a numeric value within ")
625                         .appendValue(delta)
626                         .appendText(" of ")
627                         .appendValue(value);
628             }
629 
630             private double actualDelta(Double item) {
631                 return (FastMath.abs((item - value)) - delta);
632             }
633         };
634 
635     }
636 
637     /* Replace with Matchers.greaterThan(...) if hamcrest becomes available. */
638 
639     /**
640      * Create a matcher to see if a value is greater than another one using {@link
641      * Comparable#compareTo(Object)}.
642      *
643      * @param expected value.
644      * @param <T>      type of value.
645      * @return matcher of value.
646      */
647     public static <T extends Comparable<T>> Matcher<T> greaterThan(final T expected) {
648         return new TypeSafeDiagnosingMatcher<T>() {
649             @Override
650             protected boolean matchesSafely(T item, Description mismatchDescription) {
651                 if (expected.compareTo(item) >= 0) {
652                     mismatchDescription.appendText("less than or equal to ")
653                             .appendValue(expected);
654                     return false;
655                 }
656                 return true;
657             }
658 
659             @Override
660             public void describeTo(Description description) {
661                 description.appendText("greater than ").appendValue(expected);
662             }
663         };
664     }
665 
666 
667     /**
668      * Matcher for the distance in seconds between two {@link AbsoluteDate}s. Uses {@link
669      * AbsoluteDate#durationFrom(AbsoluteDate)}.
670      *
671      * @param date         the date to compare with.
672      * @param valueMatcher the matcher for the delta. For example, {@code closeTo(0,
673      *                     1e-10)}.
674      * @return a matcher that checks the time difference between two {@link
675      * AbsoluteDate}s.
676      */
677     public static Matcher<AbsoluteDate> durationFrom(final AbsoluteDate date,
678                                                      final Matcher<Double> valueMatcher) {
679         return new TypeSafeDiagnosingMatcher<AbsoluteDate>() {
680             @Override
681             public void describeTo(Description description) {
682                 description.appendText("delta from ");
683                 description.appendValue(date);
684                 description.appendText(" is ");
685                 description.appendDescriptionOf(valueMatcher);
686             }
687 
688             @Override
689             protected boolean matchesSafely(AbsoluteDate item,
690                                             Description mismatchDescription) {
691                 double delta = item.durationFrom(date);
692                 boolean matched = valueMatcher.matches(delta);
693                 if (!matched) {
694                     mismatchDescription.appendText("delta to ");
695                     mismatchDescription.appendValue(item);
696                     mismatchDescription.appendText(" ");
697                     valueMatcher.describeMismatch(delta, mismatchDescription);
698                 }
699                 return matched;
700             }
701         };
702     }
703 
704     /**
705      * Matcher that compares to {@link Rotation}s using {@link Rotation#distance(Rotation,
706      * Rotation)}.
707      *
708      * @param from         one of the rotations to compare
709      * @param valueMatcher matcher for the distances. For example {@code closeTo(0,
710      *                     1e-10)}.
711      * @return a matcher for rotations.
712      */
713     public static Matcher<Rotation> distanceIs(final Rotation from,
714                                                final Matcher<Double> valueMatcher) {
715         return new TypeSafeDiagnosingMatcher<Rotation>() {
716 
717             @Override
718             public void describeTo(Description description) {
719                 description.appendText("distance from ");
720                 description.appendValue(from);
721                 description.appendText(" is ");
722                 description.appendDescriptionOf(valueMatcher);
723             }
724 
725             @Override
726             protected boolean matchesSafely(Rotation item,
727                                             Description mismatchDescription) {
728                 double distance = Rotation.distance(from, item);
729                 boolean matched = valueMatcher.matches(distance);
730                 if (!matched) {
731                     mismatchDescription.appendText("distance to ");
732                     mismatchDescription.appendValue(item);
733                     mismatchDescription.appendText(" was ");
734                     valueMatcher
735                             .describeMismatch(distance, mismatchDescription);
736                 }
737                 return matched;
738             }
739         };
740     }
741 
742     /**
743      * Check that two attitudes are equivalent.
744      *
745      * @param expected attitude.
746      * @return attitude matcher.
747      */
748     public static Matcher<Attitude> attitudeIs(final Attitude expected) {
749         final Matcher<AbsoluteDate> dateMatcher = durationFrom(expected.getDate(), is(0.0));
750         final Matcher<Rotation> rotationMatcher = distanceIs(expected.getRotation(), is(0.0));
751         final Matcher<Vector3D> spinMatcher = vectorCloseTo(expected.getSpin(), 0);
752         final Matcher<Vector3D> accelerationMatcher =
753                 vectorCloseTo(expected.getRotationAcceleration(), 0);
754         final Rotation r = expected.getRotation();
755         return new TypeSafeDiagnosingMatcher<Attitude>() {
756 
757             @Override
758             protected boolean matchesSafely(Attitude item,
759                                             final Description mismatchDescription) {
760                 item = item.withReferenceFrame(expected.getReferenceFrame());
761                 if (!dateMatcher.matches(item)) {
762                     mismatchDescription.appendText("date ")
763                             .appendDescriptionOf(dateMatcher);
764                 }
765                 if (!rotationMatcher.matches(item.getRotation())) {
766                     mismatchDescription.appendText("rotation ")
767                             .appendDescriptionOf(rotationMatcher);
768                     return false;
769                 }
770                 if (!spinMatcher.matches(item.getSpin())) {
771                     mismatchDescription.appendText("spin ")
772                             .appendDescriptionOf(spinMatcher);
773                     return false;
774                 }
775                 if (!accelerationMatcher.matches(item.getRotationAcceleration())) {
776                     mismatchDescription.appendText("rotation acceleration ")
777                             .appendDescriptionOf(accelerationMatcher);
778                     return false;
779                 }
780                 return true;
781             }
782 
783             @Override
784             public void describeTo(Description description) {
785                 description.appendText("attitude on ").appendValue(expected.getDate())
786                         .appendText(" rotation ").appendValueList("[", ",", "]", r.getQ0(), r.getQ1(), r.getQ2(), r.getQ3())
787                         .appendText(" spin ").appendDescriptionOf(spinMatcher)
788                         .appendText(" acceleration ").appendDescriptionOf(accelerationMatcher);
789             }
790         };
791     }
792 
793 
794 }