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.common.utils.StringUtils.*;
020import static org.apache.juneau.common.utils.ThrowableUtils.*;
021import static org.apache.juneau.common.utils.Utils.*;
022
023import java.io.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.common.utils.*;
028import org.apache.juneau.cp.*;
029import org.apache.juneau.internal.*;
030import org.apache.juneau.json.*;
031import org.apache.juneau.reflect.*;
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   //-----------------------------------------------------------------------------------------------------------------
099   // Static
100   //-----------------------------------------------------------------------------------------------------------------
101
102   private static final Messages MESSAGES = Messages.of(FluentObjectAssertion.class, "Messages");
103   private static final String
104      MSG_unexpectedType = MESSAGES.getString("unexpectedType"),
105      MSG_unexpectedComparison = MESSAGES.getString("unexpectedComparison"),
106      MSG_unexpectedValue = MESSAGES.getString("unexpectedValue"),
107      MSG_unexpectedValueDidNotExpect = MESSAGES.getString("unexpectedValueDidNotExpect"),
108      MSG_notTheSameValue = MESSAGES.getString("notTheSameValue"),
109      MSG_valueWasNull = MESSAGES.getString("valueWasNull"),
110      MSG_valueWasNotNull = MESSAGES.getString("valueWasNotNull"),
111      MSG_expectedValueNotFound = MESSAGES.getString("expectedValueNotFound"),
112      MSG_unexpectedValueFound = MESSAGES.getString("unexpectedValueFound"),
113      MSG_unexpectedValue2 = MESSAGES.getString("unexpectedValue2");
114
115   private static final JsonSerializer JSON = JsonSerializer.create()
116      .json5()
117      .build();
118
119   private static final JsonSerializer JSON_SORTED = JsonSerializer.create()
120      .json5()
121      .sortProperties()
122      .sortCollections()
123      .sortMaps()
124      .build();
125
126   //-----------------------------------------------------------------------------------------------------------------
127   // Instance
128   //-----------------------------------------------------------------------------------------------------------------
129
130   private final T value;
131
132   /**
133    * Constructor.
134    *
135    * @param value
136    *    The object being tested.
137    *    <br>Can be <jk>null</jk>.
138    * @param returns
139    *    The object to return after a test method is called.
140    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
141    * used on the same assertion.
142    */
143   public FluentObjectAssertion(T value, R returns) {
144      this(null, value, returns);
145   }
146
147   /**
148    * Chained constructor.
149    *
150    * <p>
151    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
152    *
153    * @param creator
154    *    The assertion that created this assertion.
155    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
156    * @param value
157    *    The object being tested.
158    *    <br>Can be <jk>null</jk>.
159    * @param returns
160    *    The object to return after a test method is called.
161    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
162    * used on the same assertion.
163    */
164   public FluentObjectAssertion(Assertion creator, T value, R returns) {
165      super(creator, returns);
166      this.value = value;
167   }
168
169   //-----------------------------------------------------------------------------------------------------------------
170   // Transform methods
171   //-----------------------------------------------------------------------------------------------------------------
172
173   /**
174    * Converts this object to a string using {@link Object#toString} and returns it as a new assertion.
175    *
176    * <h5 class='section'>Example:</h5>
177    * <p class='bjava'>
178    *    <jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
179    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
180    *       .asString()
181    *       .is(<js>"foobar"</js>);
182    * </p>
183    *
184    * @return A new fluent string assertion.
185    */
186   public FluentStringAssertion<R> asString() {
187      return new FluentStringAssertion<>(this, valueAsString(), returns());
188   }
189
190   /**
191    * Converts this object to text using the specified serializer and returns it as a new assertion.
192    *
193    * <h5 class='section'>Example:</h5>
194    * <p class='bjava'>
195    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
196    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
197    *       .asString(XmlSerializer.<jsf>DEFAULT</jsf>)
198    *       .is(<js>"&lt;object&gt;&lt;foo&gt;bar&lt;/foo&gt;&lt;baz&gt;qux&lt;/baz&gt;&lt;/object&gt;"</js>);
199    * </p>
200    *
201    * @param ws The serializer to use to convert the object to text.
202    * @return A new fluent string assertion.
203    */
204   public FluentStringAssertion<R> asString(WriterSerializer ws) {
205      try {
206         return new FluentStringAssertion<>(this, ws.serialize(value), returns());
207      } catch (SerializeException e) {
208         throw asRuntimeException(e);
209      }
210   }
211
212   /**
213    * Converts this object to a string using the specified function and returns it as a new assertion.
214    *
215    * <h5 class='section'>Example:</h5>
216    * <p class='bjava'>
217    *    <jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
218    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
219    *       .asString(<jv>x</jv>-&gt;<jv>x</jv>.toString())
220    *       .is(<js>"foobar"</js>);
221    * </p>
222    *
223    * @param function The conversion function.
224    * @return A new fluent string assertion.
225    */
226   public FluentStringAssertion<R> asString(Function<T,String> function) {
227      return new FluentStringAssertion<>(this, function.apply(value), returns());
228   }
229
230   /**
231    * Converts this object to simplified JSON and returns it as a new assertion.
232    *
233    * <h5 class='section'>Example:</h5>
234    * <p class='bjava'>
235    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
236    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
237    *       .asJson()
238    *       .is(<js>"{foo:'bar',baz:'qux'}"</js>);
239    * </p>
240    *
241    * @return A new fluent string assertion.
242    */
243   public FluentStringAssertion<R> asJson() {
244      return asString(JSON);
245   }
246
247   /**
248    * Converts this object to sorted simplified JSON and returns it as a new assertion.
249    *
250    * <h5 class='section'>Example:</h5>
251    * <p class='bjava'>
252    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
253    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
254    *       .asJsonSorted()
255    *       .is(<js>"{baz:'qux',foo:'bar'}"</js>);
256    * </p>
257    *
258    * @return A new fluent string assertion.
259    */
260   public FluentStringAssertion<R> asJsonSorted() {
261      return asString(JSON_SORTED);
262   }
263
264   /**
265    * Applies a transform on the inner object and returns a new inner object.
266    *
267    * @param function The function to apply.
268    * @return This object.
269    */
270   public FluentObjectAssertion<T,R> asTransformed(Function<T,T> function) {  // NOSONAR - Intentional.
271      return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns());
272   }
273
274   /**
275    * Applies a transform on the inner object and returns a new inner object.
276    *
277    * @param <T2> The transform-to type.
278    * @param function The function to apply.
279    * @return This object.
280    */
281   public <T2> FluentObjectAssertion<T2,R> asTransformedTo(Function<T,T2> function) {
282      return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns());
283   }
284
285   /**
286    * Converts this assertion into an {@link FluentAnyAssertion} so that it can be converted to other assertion types.
287    *
288    * @return This object.
289    */
290   public FluentAnyAssertion<T,R> asAny() {
291      return new FluentAnyAssertion<>(this, orElse(null), returns());
292   }
293
294   //-----------------------------------------------------------------------------------------------------------------
295   // Test methods
296   //-----------------------------------------------------------------------------------------------------------------
297
298   /**
299    * Asserts that the object is not null.
300    *
301    * <p>
302    * Equivalent to {@link #isNotNull()}.
303    *
304    * @return The fluent return object.
305    * @throws AssertionError If assertion failed.
306    */
307   public R isExists() throws AssertionError {
308      return isNotNull();
309   }
310
311   /**
312    * Asserts that the object i null.
313    *
314    * <p>
315    * Equivalent to {@link #isNotNull()}.
316    *
317    * @return The fluent return object.
318    * @throws AssertionError If assertion failed.
319    */
320   public R isNull() throws AssertionError {
321      if (value != null)
322         throw error(MSG_valueWasNotNull);
323      return returns();
324   }
325
326   /**
327    * Asserts that the object is not null.
328    *
329    * <p>
330    * Equivalent to {@link #isNotNull()}.
331    *
332    * @return The fluent return object.
333    * @throws AssertionError If assertion failed.
334    */
335   public R isNotNull() throws AssertionError {
336      if (value == null)
337         throw error(MSG_valueWasNull);
338      return returns();
339   }
340
341   /**
342    * Asserts that the value equals the specified value.
343    *
344    * @param value The value to check against.
345    * @return The fluent return object.
346    * @throws AssertionError If assertion failed.
347    */
348   public R is(T value) throws AssertionError {
349      if (this.value == value)
350         return returns();
351      if (! equals(orElse(null), value))
352         throw error(MSG_unexpectedValue, value, this.value);
353      return returns();
354   }
355
356   /**
357    * Asserts that the value converted to a string equals the specified value.
358    *
359    * @param value The value to check against.
360    * @return The fluent return object.
361    * @throws AssertionError If assertion failed.
362    */
363   public R isString(String value) {
364      return asString().is(value);
365   }
366
367   /**
368    * Asserts that the value does not equal the specified value.
369    *
370    * @param value The value to check against.
371    * @return The fluent return object.
372    * @throws AssertionError If assertion failed.
373    */
374   public R isNot(T value) throws AssertionError {
375      if (equals(orElse(null), value))
376         throw error(MSG_unexpectedValueDidNotExpect, value, orElse(null));
377      return returns();
378   }
379
380   /**
381    * Asserts that the value is one of the specified values.
382    *
383    * @param values The values to check against.
384    * @return The fluent return object.
385    * @throws AssertionError If assertion failed.
386    */
387   @SafeVarargs
388   public final R isAny(T...values) throws AssertionError {
389      for (var v : values)
390         if (equals(orElse(null), v))
391            return returns();
392      throw error(MSG_expectedValueNotFound, values, value);
393   }
394
395   /**
396    * Asserts that the value is not one of the specified values.
397    *
398    * @param values The values to check against.
399    * @return The fluent return object.
400    * @throws AssertionError If assertion failed.
401    */
402   @SafeVarargs
403   public final R isNotAny(T...values) throws AssertionError {
404      for (var v : values)
405         if (equals(orElse(null), v))
406            throw error(MSG_unexpectedValueFound, v, value);
407      return returns();
408   }
409
410   /**
411    * Asserts that the specified object is the same object as this object.
412    *
413    * @param value The value to check against.
414    * @return The fluent return object.
415    * @throws AssertionError If assertion failed.
416    */
417   public R isSame(T value) throws AssertionError {
418      if (this.value == value)
419         return returns();
420      throw error(MSG_notTheSameValue, value, Utils2.identity(value), this.value, Utils2.identity(this.value));
421   }
422
423   /**
424    * Verifies that two objects are equivalent after converting them both to JSON.
425    *
426    * @param o The object to compare against.
427    * @return The fluent return object.
428    * @throws AssertionError If assertion failed.
429    */
430   public R isSameJsonAs(Object o) throws AssertionError {
431      return isSameSerializedAs(o, JSON);
432   }
433
434   /**
435    * Verifies that two objects are equivalent after converting them both to sorted JSON.
436    *
437    * <p>
438    * Properties, maps, and collections are all sorted on both objects before comparison.
439    *
440    * @param o The object to compare against.
441    * @return The fluent return object.
442    * @throws AssertionError If assertion failed.
443    */
444   public R isSameSortedJsonAs(Object o) {
445      return isSameSerializedAs(o, JSON_SORTED);
446   }
447
448   /**
449    * Asserts that the specified object is the same as this object after converting both to strings using the specified serializer.
450    *
451    * @param o The object to compare against.
452    * @param serializer The serializer to use to serialize this object.
453    * @return The fluent return object.
454    * @throws AssertionError If assertion failed.
455    */
456   public R isSameSerializedAs(Object o, WriterSerializer serializer) {
457      var s1 = serializer.toString(value);
458      var s2 = serializer.toString(o);
459      if (Utils.ne(s1, s2))
460         throw error(MSG_unexpectedComparison, s2, s1);
461      return returns();
462   }
463
464   /**
465    * Asserts that the object is an instance of the specified class.
466    *
467    * <h5 class='section'>Example:</h5>
468    * <p class='bjava'>
469    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
470    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>);
471    * </p>
472    *
473    * @param parent The value to check against.
474    * @return The fluent return object.
475    * @throws AssertionError If assertion failed.
476    */
477   public R isType(Class<?> parent) throws AssertionError {
478      Utils.assertArgNotNull("parent", parent);
479      if (! ClassInfo.of(value()).isChildOf(parent))
480         throw error(MSG_unexpectedType, className(parent), className(value));
481      return returns();
482   }
483
484   /**
485    * Asserts that the object is an instance of the specified class.
486    *
487    * <h5 class='section'>Example:</h5>
488    * <p class='bjava'>
489    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
490    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>).isExactType(MyBean.<jk>class</jk>);
491    * </p>
492    *
493    * @param type The value to check against.
494    * @return The fluent return object.
495    * @throws AssertionError If assertion failed.
496    */
497   public R isExactType(Class<?> type) throws AssertionError {
498      Utils.assertArgNotNull("parent", type);
499      if (value().getClass() != type)
500         throw error(MSG_unexpectedType, className(type), className(value));
501      return returns();
502   }
503
504   /**
505    * Asserts that the value passes the specified predicate test.
506    *
507    * @param test The predicate to use to test the value.
508    * @return The fluent return object.
509    * @throws AssertionError If assertion failed.
510    */
511   public R is(Predicate<T> test) throws AssertionError {
512      if (test != null && ! test.test(value))
513         throw error(getFailureMessage(test, value));
514      return returns();
515   }
516
517   /**
518    * Converts this object to simplified JSON and runs the {@link FluentStringAssertion#is(String)} on the result.
519    *
520    * <h5 class='section'>Example:</h5>
521    * <p class='bjava'>
522    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
523    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
524    *       .asJson()
525    *       .is(<js>"{foo:'bar',baz:'qux'}"</js>);
526    * </p>
527    *
528    * @param value The expected string value.
529    * @return The fluent return object.
530    */
531   public R isJson(String value) {
532      return asJson().is(value);
533   }
534
535   //-----------------------------------------------------------------------------------------------------------------
536   // Fluent setters
537   //-----------------------------------------------------------------------------------------------------------------
538   @Override /* Overridden from Assertion */
539   public FluentObjectAssertion<T,R> setMsg(String msg, Object...args) {
540      super.setMsg(msg, args);
541      return this;
542   }
543
544   @Override /* Overridden from Assertion */
545   public FluentObjectAssertion<T,R> setOut(PrintStream value) {
546      super.setOut(value);
547      return this;
548   }
549
550   @Override /* Overridden from Assertion */
551   public FluentObjectAssertion<T,R> setSilent() {
552      super.setSilent();
553      return this;
554   }
555
556   @Override /* Overridden from Assertion */
557   public FluentObjectAssertion<T,R> setStdOut() {
558      super.setStdOut();
559      return this;
560   }
561
562   @Override /* Overridden from Assertion */
563   public FluentObjectAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
564      super.setThrowable(value);
565      return this;
566   }
567   //-----------------------------------------------------------------------------------------------------------------
568   // Utility methods
569   //-----------------------------------------------------------------------------------------------------------------
570
571   /**
572    * Returns the inner value after asserting it is not <jk>null</jk>.
573    *
574    * @return The inner value.
575    * @throws AssertionError If inner value was <jk>null</jk>.
576    */
577   protected T value() throws AssertionError {
578      isExists();
579      return value;
580   }
581
582   /**
583    * Returns the inner value as a string.
584    *
585    * @return The inner value as a string, or <jk>null</jk> if the value was null.
586    */
587   protected String valueAsString() {
588      return Utils.s(value);
589   }
590
591   /**
592    * Returns the inner value or the other value if the value is <jk>null</jk>.
593    *
594    * @param other The other value.
595    * @return The inner value.
596    */
597   protected T orElse(T other) {
598      return value == null ? other : value;
599   }
600
601   /**
602    * Returns <jk>true</jk> if the inner value is null.
603    *
604    * @return <jk>true</jk> if the inner value is null.
605    */
606   protected boolean valueIsNull() {
607      return value == null;
608   }
609
610   /**
611    * Returns <jk>true</jk> if the inner value is not null.
612    *
613    * @return <jk>true</jk> if the inner value is not null.
614    */
615   protected boolean valueIsNotNull() {
616      return value != null;
617   }
618
619   /**
620    * Returns the value wrapped in an {@link Optional}.
621    *
622    * @return The value wrapped in an {@link Optional}.
623    */
624   protected Optional<T> opt() {
625      return Utils.opt(value);
626   }
627
628   /**
629    * Returns the result of running the specified function against the value and returns the result.
630    *
631    * @param <T2> The mapper-to type.
632    * @param mapper The function to run against the value.
633    * @return The result, never <jk>null</jk>.
634    */
635   protected <T2> Optional<T2> map(Function<? super T, ? extends T2> mapper) {
636      return opt().map(mapper);
637   }
638
639   /**
640    * Returns the predicate failure message.
641    *
642    * <p>
643    * If the predicate extends from {@link AssertionPredicate}, then the message comes from {@link AssertionPredicate#getFailureMessage()}.
644    * Otherwise, returns a generic <js>"Unexpected value: x"</js> message.
645    *
646    * @param p The function to run against the value.
647    * @param value The value that failed the test.
648    * @return The result, never <jk>null</jk>.
649    */
650   protected String getFailureMessage(Predicate<?> p, Object value) {
651      if (p instanceof AssertionPredicate)
652         return ((AssertionPredicate<?>)p).getFailureMessage();
653      return format(MSG_unexpectedValue2, value);
654   }
655
656   /**
657    * Checks two objects for equality.
658    *
659    * @param o1 The first object.
660    * @param o2 The second object.
661    * @return <jk>true</jk> if the objects are equal.
662    */
663   protected boolean equals(Object o1, Object o2) {
664      if (o1 == o2)
665         return true;
666      if (o1 == null || o2 == null)
667         return false;
668      if (o1.equals(o2))
669         return true;
670      if (isArray(o1))
671         return stringifyDeep(o1).equals(stringifyDeep(o2));
672      return false;
673   }
674
675   /**
676    * Returns the string form of the inner object.
677    * Subclasses can override this method to affect the {@link #asString()} method (and related).
678    */
679   @Override /* Object */
680   public String toString() {
681      return valueAsString();
682   }
683}