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.Utils.*;
020import static org.apache.juneau.internal.CollectionUtils.*;
021import static java.util.Arrays.*;
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.serializer.*;
031
032/**
033 * Used for fluent assertion calls against array objects.
034 *
035 * <h5 class='section'>Test Methods:</h5>
036 * <p>
037 * <ul class='javatree'>
038 *    <li class='jc'>{@link FluentArrayAssertion}
039 *    <ul class='javatreec'>
040 *       <li class='jm'>{@link FluentArrayAssertion#isHas(Object[]) isHas(Object[])}
041 *       <li class='jm'>{@link FluentArrayAssertion#is(Predicate) is(Predicate)}
042 *       <li class='jm'>{@link FluentArrayAssertion#isAny(Predicate) isAny(Predicate)}
043 *       <li class='jm'>{@link FluentArrayAssertion#isAll(Predicate) isAll(Predicate)}
044 *       <li class='jm'>{@link FluentArrayAssertion#isEmpty() isEmpty()}
045 *       <li class='jm'>{@link FluentArrayAssertion#isNotEmpty() isNotEmpty()}
046 *       <li class='jm'>{@link FluentArrayAssertion#isSize(int size) isSize(int size)}
047 *       <li class='jm'>{@link FluentArrayAssertion#isContains(Object) isContains(Object)}
048 *       <li class='jm'>{@link FluentArrayAssertion#isNotContains(Object) isNotContains(Object)}
049 *    </ul>
050 *    <li class='jc'>{@link FluentObjectAssertion}
051 *    <ul class='javatreec'>
052 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
053 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
054 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
055 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
057 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
058 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
059 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
060 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
061 *       <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)}
062 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
063 *       <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)}
064 *       <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)}
065 *       <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)}
066 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
067 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
068 *    </ul>
069 * </ul>
070 *
071 * <h5 class='section'>Transform Methods:</h5>
072 * <p>
073 * <ul class='javatree'>
074 *    <li class='jc'>{@link FluentArrayAssertion}
075 *    <ul class='javatreec'>
076 *       <li class='jm'>{@link FluentArrayAssertion#asStrings() asStrings()}
077 *       <li class='jm'>{@link FluentArrayAssertion#asStrings(Function) asStrings(Function)}
078 *       <li class='jm'>{@link FluentArrayAssertion#asCdl() asCdl()}
079 *       <li class='jm'>{@link FluentArrayAssertion#asCdl(Function) asCdl(Function)}
080 *       <li class='jm'>{@link FluentArrayAssertion#asBeanList() asBeanList()}
081 *       <li class='jm'>{@link FluentArrayAssertion#asItem(int) asItem(int)}
082 *       <li class='jm'>{@link FluentArrayAssertion#asSorted() asSorted()}
083 *       <li class='jm'>{@link FluentArrayAssertion#asSorted(Comparator) asSorted(Comparator)}
084 *    </ul>
085 *    <li class='jc'>{@link FluentObjectAssertion}
086 *    <ul class='javatreec'>
087 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
088 *       <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)}
089 *       <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)}
090 *       <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()}
091 *       <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()}
092 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)}
093 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
094 * </ul>
095 * </ul>
096 *
097 * <h5 class='section'>Configuration Methods:</h5>
098 * <p>
099 * <ul class='javatree'>
100 *    <li class='jc'>{@link Assertion}
101 *    <ul class='javatreec'>
102 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
103 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
104 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
105 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
106 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
107 *    </ul>
108 * </ul>
109 *
110 * <h5 class='section'>See Also:</h5><ul>
111 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a>
112 * </ul>
113 *
114 * @param <E> The entry type.
115 * @param <R> The return type.
116 */
117public class FluentArrayAssertion<E,R> extends FluentObjectAssertion<E[],R> {
118
119   //-----------------------------------------------------------------------------------------------------------------
120   // Static
121   //-----------------------------------------------------------------------------------------------------------------
122
123   private static final Messages MESSAGES = Messages.of(FluentArrayAssertion.class, "Messages");
124   private static final String
125      MSG_arrayWasNotEmpty = MESSAGES.getString("arrayWasNotEmpty"),
126      MSG_arrayWasEmpty = MESSAGES.getString("arrayWasEmpty"),
127      MSG_arrayUnexpectedSize = MESSAGES.getString("arrayUnexpectedSize"),
128      MSG_arrayDidNotContainExpectedValue = MESSAGES.getString("arrayDidNotContainExpectedValue"),
129      MSG_arrayContainedUnexpectedValue = MESSAGES.getString("arrayContainedUnexpectedValue"),
130      MSG_arrayDidNotContainExpectedValueAt = MESSAGES.getString("arrayDidNotContainExpectedValueAt"),
131      MSG_arrayDidntContainAnyMatchingValue = MESSAGES.getString("arrayDidntContainAnyMatchingValue"),
132      MSG_arrayContainedNonMatchingValueAt = MESSAGES.getString("arrayContainedNonMatchingValueAt");
133
134   //-----------------------------------------------------------------------------------------------------------------
135   // Instance
136   //-----------------------------------------------------------------------------------------------------------------
137
138   /**
139    * Constructor.
140    *
141    * @param value
142    *    The object being tested.
143    *    <br>Can be <jk>null</jk>.
144    * @param returns
145    *    The object to return after a test method is called.
146    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
147    * used on the same assertion.
148    */
149   public FluentArrayAssertion(E[] value, R returns) {
150      this(null, value, returns);
151   }
152
153   /**
154    * Chained constructor.
155    *
156    * <p>
157    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
158    *
159    * @param creator
160    *    The assertion that created this assertion.
161    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
162    * @param value
163    *    The object being tested.
164    *    <br>Can be <jk>null</jk>.
165    * @param returns
166    *    The object to return after a test method is called.
167    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
168    * used on the same assertion.
169    */
170   public FluentArrayAssertion(Assertion creator, E[] value, R returns) {
171      super(creator, value, returns);
172   }
173
174   //-----------------------------------------------------------------------------------------------------------------
175   // Transform methods
176   //-----------------------------------------------------------------------------------------------------------------
177
178   @Override /* FluentObjectAssertion */
179   public FluentArrayAssertion<E,R> asTransformed(Function<E[],E[]> function) {  // NOSONAR - Intentional.
180      return new FluentArrayAssertion<>(this, function.apply(orElse(null)), returns());
181   }
182
183   @Override /* FluentBaseAssertion */
184   public FluentStringAssertion<R> asString() {
185      return new FluentStringAssertion<>(this, toString(), returns());
186   }
187
188   /**
189    * Converts this assertion into a {@link FluentListAssertion} of strings.
190    *
191    * @return A new fluent string assertion.
192    */
193   public FluentStringListAssertion<R> asStrings() {
194      return new FluentStringListAssertion<>(this, valueIsNull() ? null : stream(value()).map((o) -> s(o)).toList(), returns());
195   }
196
197   /**
198    * Runs the stringify function against all values in this list and returns it as a fluent string list assertion.
199    *
200    * @param function The function to apply to all values in this list.
201    * @return A new fluent string list assertion.  Never <jk>null</jk>.
202    */
203   public FluentStringListAssertion<R> asStrings(Function<E,String> function) {
204      List<String> l = valueIsNull() ? null : stream(value()).map(function::apply).toList();
205      return new FluentStringListAssertion<>(this, l, returns());
206   }
207
208   /**
209    * Converts the entries in this list to a simple comma-delimited list and returns the value as a fluent string assertion.
210    *
211    * @return A fluent string assertion.  Never <jk>null</jk>.
212    */
213   public FluentStringAssertion<R> asCdl() {
214      return new FluentStringAssertion<>(this, valueIsNull() ? null : Utils.join(value(), ','), returns());
215   }
216
217   /**
218    * Converts the entries to strings using the specified stringify function, combines them into a simple comma-delimited list, and returns the value as a fluent string assertion.
219    *
220    * @param function The function to apply to all values in this list.
221    * @return A fluent string assertion.  Never <jk>null</jk>.
222    */
223   public FluentStringAssertion<R> asCdl(Function<E,String> function) {
224      List<String> l = valueIsNull() ? null : stream(value()).map(function::apply).toList();
225      return new FluentStringAssertion<>(this, Utils.join(l, ','), returns());
226   }
227
228   /**
229    * Converts this assertion into a {@link FluentBeanListAssertion}.
230    *
231    * <h5 class='section'>Example:</h5>
232    * <p class='bjava'>
233    *    <jc>// Extracts the 'foo' property from an array of beans and validates their values.</jc>.
234    *    <jsm>assertObject</jsm>(<jv>myArrayOfBeans</jv>)
235    *       .asBeanList()
236    *       .asProperty(<js>"foo"</js>)
237    *       .asSorted()
238    *       .equals(<js>"value1"</js>,<js>"value2"</js>,<js>"value3"</js>);
239    * </p>
240    *
241    * @return A new fluent string assertion.
242    */
243   public FluentBeanListAssertion<E,R> asBeanList() {
244      return new FluentBeanListAssertion<>(this, toList(), returns());
245   }
246
247   /**
248    * Returns an object assertion on the item specified at the specified index.
249    *
250    * <p>
251    * If the array is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
252    * (meaning {@link FluentAnyAssertion#isExists()} returns <jk>false</jk>).
253    *
254    * @param index The index of the item to retrieve from the array.
255    * @return A new assertion.
256    */
257   public FluentAnyAssertion<E,R> asItem(int index) {
258      return new FluentAnyAssertion<>(this, at(index), returns());
259   }
260
261   /**
262    * Sorts the entries in this list.
263    *
264    * @return A new list assertion.  The contents of the original list remain unchanged.
265    */
266   public FluentListAssertion<E,R> asSorted() {
267      return new FluentListAssertion<>(this, toSortedList(null), returns());
268   }
269
270   /**
271    * Sorts the entries in this list using the specified comparator.
272    *
273    * @param comparator The comparator to use to sort the list.
274    * @return A new list assertion.  The contents of the original list remain unchanged.
275    */
276   public FluentListAssertion<E,R> asSorted(Comparator<E> comparator) {
277      return new FluentListAssertion<>(this, toSortedList(comparator), returns());
278   }
279
280   //-----------------------------------------------------------------------------------------------------------------
281   // Test methods
282   //-----------------------------------------------------------------------------------------------------------------
283
284   /**
285    * Asserts that the contents of this list contain the specified values.
286    *
287    * @param entries The expected entries in this list.
288    * @return This object.
289    * @throws AssertionError If assertion failed.
290    */
291   public R isHas(E...entries) throws AssertionError {
292      Utils.assertArgNotNull("entries", entries);
293      Predicate<E>[] p = stream(entries).map(AssertionPredicates::eq).toArray(Predicate[]::new);
294      return is(p);
295   }
296
297   /**
298    * Asserts that the contents of this list pass the specified tests.
299    *
300    * @param tests The tests to run.  <jk>null</jk> entries are ignored.
301    * @return This object.
302    * @throws AssertionError If assertion failed.
303    */
304   @SafeVarargs
305   public final R is(Predicate<E>...tests) throws AssertionError {
306      isSize(tests.length);
307      for (int i = 0, j = length(); i < j; i++) {
308         var t = tests[i];
309         if (t != null && ! t.test(at(i)))
310            throw error(MSG_arrayDidNotContainExpectedValueAt, i, getFailureMessage(t, at(i)));
311      }
312      return returns();
313   }
314
315   /**
316    * Asserts that at least one value in the array passes the specified test.
317    *
318    * @param test The predicate test.
319    * @return The fluent return object.
320    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
321    */
322   public R isAny(Predicate<E> test) throws AssertionError {
323      Utils.assertArgNotNull("test", test);
324      for (var v : value())
325         if (test.test(v))
326            return returns();
327      throw error(MSG_arrayDidntContainAnyMatchingValue, (Object)value());
328   }
329
330   /**
331    * Asserts that all values in the array passes the specified test.
332    *
333    * @param test The predicate test.
334    * @return The fluent return object.
335    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
336    */
337   public R isAll(Predicate<E> test) throws AssertionError {
338      Utils.assertArgNotNull("test", test);
339      for (int i = 0, j = length(); i < j; i++)
340         if (! test.test(at(i)))
341            throw error(MSG_arrayContainedNonMatchingValueAt, i, getFailureMessage(test, at(i)));
342      return returns();
343   }
344
345   /**
346    * Asserts that the collection exists and is empty.
347    *
348    * @return The fluent return object.
349    * @throws AssertionError If assertion failed.
350    */
351   public R isEmpty() throws AssertionError {
352      if (length() != 0)
353         throw error(MSG_arrayWasNotEmpty);
354      return returns();
355   }
356
357   /**
358    * Asserts that the collection exists and is not empty.
359    *
360    * @return The fluent return object.
361    * @throws AssertionError If assertion failed.
362    */
363   public R isNotEmpty() throws AssertionError {
364      if (length() == 0)
365         throw error(MSG_arrayWasEmpty);
366      return returns();
367   }
368
369   /**
370    * Asserts that the collection exists and is the specified size.
371    *
372    * @param size The expected size.
373    * @return The fluent return object.
374    * @throws AssertionError If assertion failed.
375    */
376   public R isSize(int size) throws AssertionError {
377      if (length() != size)
378         throw error(MSG_arrayUnexpectedSize, size, length());
379      return returns();
380   }
381
382   /**
383    * Asserts that the array contains the expected value.
384    *
385    * @param entry The value to check for.
386    * @return The fluent return object.
387    * @throws AssertionError If assertion failed.
388    */
389   public R isContains(E entry) throws AssertionError {
390      for (int i = 0, j = length(); i < j; i++)
391         if (Utils.eq(at(i), entry))
392            return returns();
393      throw error(MSG_arrayDidNotContainExpectedValue, entry, toString());
394   }
395
396   /**
397    * Asserts that the array does not contain the expected value.
398    *
399    * @param entry The value to check for.
400    * @return The fluent return object.
401    * @throws AssertionError If assertion failed.
402    */
403   public R isNotContains(E entry) throws AssertionError {
404      for (int i = 0, j = length(); i < j; i++)
405         if (Utils.eq(at(i), entry))
406            throw error(MSG_arrayContainedUnexpectedValue, entry, toString());
407      return returns();
408   }
409
410   //-----------------------------------------------------------------------------------------------------------------
411   // Fluent setters
412   //-----------------------------------------------------------------------------------------------------------------
413   @Override /* Overridden from Assertion */
414   public FluentArrayAssertion<E,R> setMsg(String msg, Object...args) {
415      super.setMsg(msg, args);
416      return this;
417   }
418
419   @Override /* Overridden from Assertion */
420   public FluentArrayAssertion<E,R> setOut(PrintStream value) {
421      super.setOut(value);
422      return this;
423   }
424
425   @Override /* Overridden from Assertion */
426   public FluentArrayAssertion<E,R> setSilent() {
427      super.setSilent();
428      return this;
429   }
430
431   @Override /* Overridden from Assertion */
432   public FluentArrayAssertion<E,R> setStdOut() {
433      super.setStdOut();
434      return this;
435   }
436
437   @Override /* Overridden from Assertion */
438   public FluentArrayAssertion<E,R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
439      super.setThrowable(value);
440      return this;
441   }
442   //-----------------------------------------------------------------------------------------------------------------
443   // Utility methods
444   //-----------------------------------------------------------------------------------------------------------------
445
446   private int length() {
447      return value().length;
448   }
449
450   private List<E> toList() {
451      return valueIsNull() ? null : Utils.list(value());
452   }
453
454   private List<E> toSortedList(Comparator<E> comparator) {
455      return valueIsNull() ? null : sortedList(comparator, value());
456   }
457
458   private E at(int index) {
459      return valueIsNull() || index >= length() || index < 0 ? null : value()[index];
460   }
461
462   @Override
463   public String toString() {
464      return valueIsNull() ? null : Arrays.toString(value());
465   }
466}