001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.assertions;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.AssertionUtils.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022import static org.apache.juneau.commons.utils.ThrowableUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.io.*;
026import java.util.*;
027import java.util.function.*;
028
029import org.apache.juneau.commons.utils.*;
030import org.apache.juneau.cp.*;
031import org.apache.juneau.json.*;
032import org.apache.juneau.serializer.*;
033
034/**
035 * Used for fluent assertion calls against POJOs.
036 *
037 * <h5 class='section'>Test Methods:</h5>
038 * <p>
039 * <ul class='javatree'>
040 *    <li class='jc'>{@link FluentObjectAssertion}
041 *    <ul class='javatreec'>
042 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
043 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
044 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
045 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
046 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
047 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
048 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
049 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
050 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)}
052 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
053 *       <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)}
054 *       <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)}
055 *       <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
057 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
058 *    </ul>
059 * </ul>
060 *
061 * <h5 class='section'>Transform Methods:</h5>
062 * <p>
063 * <ul class='javatree'>
064 *    <li class='jc'>{@link FluentObjectAssertion}
065 *    <ul class='javatreec'>
066 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
067 *       <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)}
068 *       <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)}
069 *       <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()}
070 *       <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()}
071 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)}
072 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
073 * </ul>
074 * </ul>
075 *
076 * <h5 class='section'>Configuration Methods:</h5>
077 * <p>
078 * <ul class='javatree'>
079 *    <li class='jc'>{@link Assertion}
080 *    <ul class='javatreec'>
081 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
082 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
083 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
084 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
085 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
086 *    </ul>
087 * </ul>
088 *
089 * <h5 class='section'>See Also:</h5><ul>
090 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a>
091 * </ul>
092 *
093 * @param <T> The object type.
094 * @param <R> The return type.
095 */
096public class FluentObjectAssertion<T,R> extends FluentAssertion<R> {
097
098   // @formatter:off
099   private static final Messages MESSAGES = Messages.of(FluentObjectAssertion.class, "Messages");
100   private static final String
101      MSG_unexpectedType = MESSAGES.getString("unexpectedType"),
102      MSG_unexpectedComparison = MESSAGES.getString("unexpectedComparison"),
103      MSG_unexpectedValue = MESSAGES.getString("unexpectedValue"),
104      MSG_unexpectedValueDidNotExpect = MESSAGES.getString("unexpectedValueDidNotExpect"),
105      MSG_notTheSameValue = MESSAGES.getString("notTheSameValue"),
106      MSG_valueWasNull = MESSAGES.getString("valueWasNull"),
107      MSG_valueWasNotNull = MESSAGES.getString("valueWasNotNull"),
108      MSG_expectedValueNotFound = MESSAGES.getString("expectedValueNotFound"),
109      MSG_unexpectedValueFound = MESSAGES.getString("unexpectedValueFound"),
110      MSG_unexpectedValue2 = MESSAGES.getString("unexpectedValue2");
111   // @formatter:on
112
113   private static final JsonSerializer JSON = JsonSerializer.create().json5().build();
114
115   private static final JsonSerializer JSON_SORTED = JsonSerializer.create().json5().sortProperties().sortCollections().sortMaps().build();
116
117   private final T value;
118
119   /**
120    * Chained constructor.
121    *
122    * <p>
123    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
124    *
125    * @param creator
126    *    The assertion that created this assertion.
127    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
128    * @param value
129    *    The object being tested.
130    *    <br>Can be <jk>null</jk>.
131    * @param returns
132    *    The object to return after a test method is called.
133    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
134    * used on the same assertion.
135    */
136   public FluentObjectAssertion(Assertion creator, T value, R returns) {
137      super(creator, returns);
138      this.value = value;
139   }
140
141   /**
142    * Constructor.
143    *
144    * @param value
145    *    The object being tested.
146    *    <br>Can be <jk>null</jk>.
147    * @param returns
148    *    The object to return after a test method is called.
149    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
150    * used on the same assertion.
151    */
152   public FluentObjectAssertion(T value, R returns) {
153      this(null, value, returns);
154   }
155
156   /**
157    * Converts this assertion into an {@link FluentAnyAssertion} so that it can be converted to other assertion types.
158    *
159    * @return This object.
160    */
161   public FluentAnyAssertion<T,R> asAny() {
162      return new FluentAnyAssertion<>(this, orElse(null), returns());
163   }
164
165   /**
166    * Converts this object to simplified JSON and returns it as a new assertion.
167    *
168    * <h5 class='section'>Example:</h5>
169    * <p class='bjava'>
170    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
171    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
172    *       .asJson()
173    *       .is(<js>"{foo:'bar',baz:'qux'}"</js>);
174    * </p>
175    *
176    * @return A new fluent string assertion.
177    */
178   public FluentStringAssertion<R> asJson() {
179      return asString(JSON);
180   }
181
182   /**
183    * Converts this object to sorted simplified JSON and returns it as a new assertion.
184    *
185    * <h5 class='section'>Example:</h5>
186    * <p class='bjava'>
187    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
188    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
189    *       .asJsonSorted()
190    *       .is(<js>"{baz:'qux',foo:'bar'}"</js>);
191    * </p>
192    *
193    * @return A new fluent string assertion.
194    */
195   public FluentStringAssertion<R> asJsonSorted() {
196      return asString(JSON_SORTED);
197   }
198
199   /**
200    * Converts this object to a string using {@link Object#toString} and returns it as a new assertion.
201    *
202    * <h5 class='section'>Example:</h5>
203    * <p class='bjava'>
204    *    <jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
205    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
206    *       .asString()
207    *       .is(<js>"foobar"</js>);
208    * </p>
209    *
210    * @return A new fluent string assertion.
211    */
212   public FluentStringAssertion<R> asString() {
213      return new FluentStringAssertion<>(this, valueAsString(), returns());
214   }
215
216   /**
217    * Converts this object to a string using the specified function and returns it as a new assertion.
218    *
219    * <h5 class='section'>Example:</h5>
220    * <p class='bjava'>
221    *    <jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
222    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
223    *       .asString(<jv>x</jv>-&gt;<jv>x</jv>.toString())
224    *       .is(<js>"foobar"</js>);
225    * </p>
226    *
227    * @param function The conversion function.
228    * @return A new fluent string assertion.
229    */
230   public FluentStringAssertion<R> asString(Function<T,String> function) {
231      return new FluentStringAssertion<>(this, function.apply(value), returns());
232   }
233
234   /**
235    * Converts this object to text using the specified serializer and returns it as a new assertion.
236    *
237    * <h5 class='section'>Example:</h5>
238    * <p class='bjava'>
239    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
240    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
241    *       .asString(XmlSerializer.<jsf>DEFAULT</jsf>)
242    *       .is(<js>"&lt;object&gt;&lt;foo&gt;bar&lt;/foo&gt;&lt;baz&gt;qux&lt;/baz&gt;&lt;/object&gt;"</js>);
243    * </p>
244    *
245    * @param ws The serializer to use to convert the object to text.
246    * @return A new fluent string assertion.
247    */
248   public FluentStringAssertion<R> asString(WriterSerializer ws) {
249      try {
250         return new FluentStringAssertion<>(this, ws.serialize(value), returns());
251      } catch (SerializeException e) {
252         throw toRex(e);
253      }
254   }
255
256   /**
257    * Applies a transform on the inner object and returns a new inner object.
258    *
259    * @param function The function to apply.
260    * @return This object.
261    */
262   public FluentObjectAssertion<T,R> asTransformed(Function<T,T> function) { // NOSONAR - Intentional.
263      return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns());
264   }
265
266   /**
267    * Applies a transform on the inner object and returns a new inner object.
268    *
269    * @param <T2> The transform-to type.
270    * @param function The function to apply.
271    * @return This object.
272    */
273   public <T2> FluentObjectAssertion<T2,R> asTransformedTo(Function<T,T2> function) {
274      return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns());
275   }
276
277   /**
278    * Asserts that the value passes the specified predicate test.
279    *
280    * @param test The predicate to use to test the value.
281    * @return The fluent return object.
282    * @throws AssertionError If assertion failed.
283    */
284   public R is(Predicate<T> test) throws AssertionError {
285      if (nn(test) && ! test.test(value))
286         throw error(getFailureMessage(test, value));
287      return returns();
288   }
289
290   /**
291    * Asserts that the value equals the specified value.
292    *
293    * @param value The value to check against.
294    * @return The fluent return object.
295    * @throws AssertionError If assertion failed.
296    */
297   public R is(T value) throws AssertionError {
298      if (this.value == value)
299         return returns();
300      if (! equals(orElse(null), value))
301         throw error(MSG_unexpectedValue, r(value), r(this.value));
302      return returns();
303   }
304
305   /**
306    * Asserts that the value is one of the specified values.
307    *
308    * @param values The values to check against.
309    * @return The fluent return object.
310    * @throws AssertionError If assertion failed.
311    */
312   @SafeVarargs
313   public final R isAny(T...values) throws AssertionError {
314      for (var v : values)
315         if (equals(orElse(null), v))
316            return returns();
317      throw error(MSG_expectedValueNotFound, r(values), r(value));
318   }
319
320   /**
321    * Asserts that the object is an instance of the specified class.
322    *
323    * <h5 class='section'>Example:</h5>
324    * <p class='bjava'>
325    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
326    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>).isExactType(MyBean.<jk>class</jk>);
327    * </p>
328    *
329    * @param type The value to check against.
330    * @return The fluent return object.
331    * @throws AssertionError If assertion failed.
332    */
333   public R isExactType(Class<?> type) throws AssertionError {
334      assertArgNotNull("parent", type);
335      if (value().getClass() != type)
336         throw error(MSG_unexpectedType, cn(type), cn(value));
337      return returns();
338   }
339
340   /**
341    * Asserts that the object is not null.
342    *
343    * <p>
344    * Equivalent to {@link #isNotNull()}.
345    *
346    * @return The fluent return object.
347    * @throws AssertionError If assertion failed.
348    */
349   public R isExists() throws AssertionError { return isNotNull(); }
350
351   /**
352    * Converts this object to simplified JSON and runs the {@link FluentStringAssertion#is(String)} on the result.
353    *
354    * <h5 class='section'>Example:</h5>
355    * <p class='bjava'>
356    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
357    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
358    *       .asJson()
359    *       .is(<js>"{foo:'bar',baz:'qux'}"</js>);
360    * </p>
361    *
362    * @param value The expected string value.
363    * @return The fluent return object.
364    */
365   public R isJson(String value) {
366      return asJson().is(value);
367   }
368
369   /**
370    * Asserts that the value does not equal the specified value.
371    *
372    * @param value The value to check against.
373    * @return The fluent return object.
374    * @throws AssertionError If assertion failed.
375    */
376   public R isNot(T value) throws AssertionError {
377      if (equals(orElse(null), value))
378         throw error(MSG_unexpectedValueDidNotExpect, r(value), r(orElse(null)));
379      return returns();
380   }
381
382   /**
383    * Asserts that the value is not one of the specified values.
384    *
385    * @param values The values to check against.
386    * @return The fluent return object.
387    * @throws AssertionError If assertion failed.
388    */
389   @SafeVarargs
390   public final R isNotAny(T...values) throws AssertionError {
391      for (var v : values)
392         if (equals(orElse(null), v))
393            throw error(MSG_unexpectedValueFound, r(v), r(value));
394      return returns();
395   }
396
397   /**
398    * Asserts that the object is not null.
399    *
400    * <p>
401    * Equivalent to {@link #isNotNull()}.
402    *
403    * @return The fluent return object.
404    * @throws AssertionError If assertion failed.
405    */
406   public R isNotNull() throws AssertionError {
407      if (value == null)
408         throw error(MSG_valueWasNull);
409      return returns();
410   }
411
412   /**
413    * Asserts that the object i null.
414    *
415    * <p>
416    * Equivalent to {@link #isNotNull()}.
417    *
418    * @return The fluent return object.
419    * @throws AssertionError If assertion failed.
420    */
421   public R isNull() throws AssertionError {
422      if (nn(value))
423         throw error(MSG_valueWasNotNull);
424      return returns();
425   }
426
427   /**
428    * Asserts that the specified object is the same object as this object.
429    *
430    * @param value The value to check against.
431    * @return The fluent return object.
432    * @throws AssertionError If assertion failed.
433    */
434   public R isSame(T value) throws AssertionError {
435      if (this.value == value)
436         return returns();
437      throw error(MSG_notTheSameValue, r(value), id(value), r(this.value), id(this.value));
438   }
439
440   /**
441    * Verifies that two objects are equivalent after converting them both to JSON.
442    *
443    * @param o The object to compare against.
444    * @return The fluent return object.
445    * @throws AssertionError If assertion failed.
446    */
447   public R isSameJsonAs(Object o) throws AssertionError {
448      return isSameSerializedAs(o, JSON);
449   }
450
451   /**
452    * Asserts that the specified object is the same as this object after converting both to strings using the specified serializer.
453    *
454    * @param o The object to compare against.
455    * @param serializer The serializer to use to serialize this object.
456    * @return The fluent return object.
457    * @throws AssertionError If assertion failed.
458    */
459   public R isSameSerializedAs(Object o, WriterSerializer serializer) {
460      var s1 = serializer.toString(value);
461      var s2 = serializer.toString(o);
462      if (neq(s1, s2))
463         throw error(MSG_unexpectedComparison, s2, s1);
464      return returns();
465   }
466
467   /**
468    * Verifies that two objects are equivalent after converting them both to sorted JSON.
469    *
470    * <p>
471    * Properties, maps, and collections are all sorted on both objects before comparison.
472    *
473    * @param o The object to compare against.
474    * @return The fluent return object.
475    * @throws AssertionError If assertion failed.
476    */
477   public R isSameSortedJsonAs(Object o) {
478      return isSameSerializedAs(o, JSON_SORTED);
479   }
480
481   /**
482    * Asserts that the value converted to a string equals the specified value.
483    *
484    * @param value The value to check against.
485    * @return The fluent return object.
486    * @throws AssertionError If assertion failed.
487    */
488   public R isString(String value) {
489      return asString().is(value);
490   }
491
492   /**
493    * Asserts that the object is an instance of the specified class.
494    *
495    * <h5 class='section'>Example:</h5>
496    * <p class='bjava'>
497    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
498    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>);
499    * </p>
500    *
501    * @param parent The value to check against.
502    * @return The fluent return object.
503    * @throws AssertionError If assertion failed.
504    */
505   public R isType(Class<?> parent) throws AssertionError {
506      assertArgNotNull("parent", parent);
507      if (! info(value()).isChildOf(parent))
508         throw error(MSG_unexpectedType, cn(parent), cn(value));
509      return returns();
510   }
511
512   @Override /* Overridden from Assertion */
513   public FluentObjectAssertion<T,R> setMsg(String msg, Object...args) {
514      super.setMsg(msg, args);
515      return this;
516   }
517
518   @Override /* Overridden from Assertion */
519   public FluentObjectAssertion<T,R> setOut(PrintStream value) {
520      super.setOut(value);
521      return this;
522   }
523
524   @Override /* Overridden from Assertion */
525   public FluentObjectAssertion<T,R> setSilent() {
526      super.setSilent();
527      return this;
528   }
529
530   @Override /* Overridden from Assertion */
531   public FluentObjectAssertion<T,R> setStdOut() {
532      super.setStdOut();
533      return this;
534   }
535
536   @Override /* Overridden from Assertion */
537   public FluentObjectAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
538      super.setThrowable(value);
539      return this;
540   }
541
542   /**
543    * Returns the string form of the inner object.
544    * Subclasses can override this method to affect the {@link #asString()} method (and related).
545    */
546   @Override /* Overridden from Object */
547   public String toString() {
548      return valueAsString();
549   }
550
551   /**
552    * Checks two objects for equality.
553    *
554    * @param o1 The first object.
555    * @param o2 The second object.
556    * @return <jk>true</jk> if the objects are equal.
557    */
558   protected boolean equals(Object o1, Object o2) {
559      if (o1 == o2)
560         return true;
561      if (o1 == null || o2 == null)
562         return false;
563      if (o1.equals(o2))
564         return true;
565      if (isArray(o1))
566         return readable(o1).equals(readable(o2));
567      return false;
568   }
569
570   /**
571    * Returns the predicate failure message.
572    *
573    * <p>
574    * If the predicate extends from {@link AssertionPredicate}, then the message comes from {@link AssertionPredicate#getFailureMessage()}.
575    * Otherwise, returns a generic <js>"Unexpected value: x"</js> message.
576    *
577    * @param p The function to run against the value.
578    * @param value The value that failed the test.
579    * @return The result, never <jk>null</jk>.
580    */
581   protected String getFailureMessage(Predicate<?> p, Object value) {
582      if (p instanceof AssertionPredicate p2)
583         return p2.getFailureMessage();
584      return f(MSG_unexpectedValue2, r(value));
585   }
586
587   /**
588    * Returns the result of running the specified function against the value and returns the result.
589    *
590    * @param <T2> The mapper-to type.
591    * @param mapper The function to run against the value.
592    * @return The result, never <jk>null</jk>.
593    */
594   protected <T2> Optional<T2> map(Function<? super T,? extends T2> mapper) {
595      return opt().map(mapper);
596   }
597
598   /**
599    * Returns the value wrapped in an {@link Optional}.
600    *
601    * @return The value wrapped in an {@link Optional}.
602    */
603   protected Optional<T> opt() {
604      return Utils.opt(value);  // NOAI
605   }
606
607   /**
608    * Returns the inner value or the other value if the value is <jk>null</jk>.
609    *
610    * @param other The other value.
611    * @return The inner value.
612    */
613   protected T orElse(T other) {
614      return value == null ? other : value;
615   }
616
617   /**
618    * Returns the inner value after asserting it is not <jk>null</jk>.
619    *
620    * @return The inner value.
621    * @throws AssertionError If inner value was <jk>null</jk>.
622    */
623   protected T value() throws AssertionError {
624      isExists();
625      return value;
626   }
627
628   /**
629    * Returns the inner value as a string.
630    *
631    * @return The inner value as a string, or <jk>null</jk> if the value was null.
632    */
633   protected String valueAsString() {
634      return readable(value);
635   }
636
637   /**
638    * Returns <jk>true</jk> if the inner value is not null.
639    *
640    * @return <jk>true</jk> if the inner value is not null.
641    */
642   protected boolean valueIsNotNull() {
643      return nn(value);
644   }
645
646   /**
647    * Returns <jk>true</jk> if the inner value is null.
648    *
649    * @return <jk>true</jk> if the inner value is null.
650    */
651   protected boolean valueIsNull() {
652      return value == null;
653   }
654}