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.utils.Utils.*;
020
021import java.io.*;
022import java.util.*;
023import java.util.function.*;
024
025import org.apache.juneau.cp.*;
026import org.apache.juneau.serializer.*;
027
028/**
029 * Used for fluent assertion calls against collections objects.
030 *
031 * <h5 class='section'>Test Methods:</h5>
032 * <p>
033 * <ul class='javatree'>
034 *    <li class='jc'>{@link FluentCollectionAssertion}
035 *    <ul class='javatreec'>
036 *       <li class='jm'>{@link FluentCollectionAssertion#isEmpty() isEmpty()}
037 *       <li class='jm'>{@link FluentCollectionAssertion#isNotEmpty() isNotEmpty()}
038 *       <li class='jm'>{@link FluentCollectionAssertion#isContains(Object) isContains(Object)}
039 *       <li class='jm'>{@link FluentCollectionAssertion#isNotContains(Object) isNotContains(Object)}
040 *       <li class='jm'>{@link FluentCollectionAssertion#isAny(Predicate) isAny(Predicate)}
041 *       <li class='jm'>{@link FluentCollectionAssertion#isAll(Predicate) isAll(Predicate)}
042 *       <li class='jm'>{@link FluentCollectionAssertion#isSize(int size) isSize(int size)}
043 *    </ul>
044 *    <li class='jc'>{@link FluentObjectAssertion}
045 *    <ul class='javatreec'>
046 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
047 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
048 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
049 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
050 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
052 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
053 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
054 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
055 *       <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
057 *       <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)}
058 *       <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)}
059 *       <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)}
060 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
061 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
062 *    </ul>
063 * </ul>
064 *
065 * <h5 class='section'>Transform Methods:</h5>
066 * <p>
067 * <ul class='javatree'>
068 *    <li class='jc'>{@link FluentCollectionAssertion}
069 *    <ul class='javatreec'>
070 *       <li class='jm'>{@link FluentCollectionAssertion#asStrings() asStrings()}
071 *       <li class='jm'>{@link FluentCollectionAssertion#asSize() asSize()}
072 *    </ul>
073 *    <li class='jc'>{@link FluentObjectAssertion}
074 *    <ul class='javatreec'>
075 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
076 *       <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)}
077 *       <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)}
078 *       <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()}
079 *       <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()}
080 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)}
081 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
082 * </ul>
083 * </ul>
084 *
085 * <h5 class='section'>Configuration Methods:</h5>
086 * <p>
087 * <ul class='javatree'>
088 *    <li class='jc'>{@link Assertion}
089 *    <ul class='javatreec'>
090 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
091 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
092 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
093 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
094 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
095 *    </ul>
096 * </ul>
097 *
098 * <h5 class='section'>See Also:</h5><ul>
099 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a>
100 * </ul>
101 *
102 * @param <E> The element type.
103 * @param <R> The return type.
104 */
105public class FluentCollectionAssertion<E,R> extends FluentObjectAssertion<Collection<E>,R> {
106
107   // @formatter:off
108   private static final Messages MESSAGES = Messages.of(FluentCollectionAssertion.class, "Messages");
109   private static final String
110      MSG_collectionWasNotEmpty = MESSAGES.getString("collectionWasNotEmpty"),
111      MSG_collectionDidNotContainExpectedValue = MESSAGES.getString("collectionDidNotContainExpectedValue"),
112      MSG_collectionDidNotContainTestedValue = MESSAGES.getString("collectionDidNotContainTestedValue"),
113      MSG_collectionContainedUnexpectedValue = MESSAGES.getString("collectionContainedUnexpectedValue"),
114      MSG_collectionWasEmpty = MESSAGES.getString("collectionWasEmpty"),
115      MSG_collectionDidNotHaveExpectedSize = MESSAGES.getString("collectionDidNotHaveExpectedSize");
116   // @formatter:on
117
118   /**
119    * Chained constructor.
120    *
121    * <p>
122    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
123    *
124    * @param creator
125    *    The assertion that created this assertion.
126    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
127    * @param value
128    *    The object being tested.
129    *    <br>Can be <jk>null</jk>.
130    * @param returns
131    *    The object to return after a test method is called.
132    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
133    * used on the same assertion.
134    */
135   public FluentCollectionAssertion(Assertion creator, Collection<E> value, R returns) {
136      super(creator, value, returns);
137   }
138
139   /**
140    * Constructor.
141    *
142    * @param value
143    *    The object being tested.
144    *    <br>Can be <jk>null</jk>.
145    * @param returns
146    *    The object to return after a test method is called.
147    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
148    * used on the same assertion.
149    */
150   public FluentCollectionAssertion(Collection<E> value, R returns) {
151      this(null, value, returns);
152   }
153
154   /**
155    * Returns an integer assertion on the size of this collection.
156    *
157    * <p>
158    * If the collection is <jk>null</jk>, the returned assertion is a null assertion
159    * (meaning {@link FluentIntegerAssertion#isExists()} returns <jk>false</jk>).
160    *
161    * @return A new assertion.
162    */
163   public FluentIntegerAssertion<R> asSize() {
164      return new FluentIntegerAssertion<>(this, valueIsNull() ? null : value().size(), returns());
165   }
166
167   /**
168    * Converts this assertion into a {@link FluentListAssertion} of strings.
169    *
170    * @return A new fluent string assertion.
171    */
172   public FluentStringListAssertion<R> asStrings() {
173      return new FluentStringListAssertion<>(this, valueIsNull() ? null : value().stream().map(o -> s(o)).toList(), returns());
174   }
175
176   @Override /* Overridden from FluentObjectAssertion */
177   public FluentCollectionAssertion<E,R> asTransformed(Function<Collection<E>,Collection<E>> function) { // NOSONAR - Intentional.
178      return new FluentCollectionAssertion<>(this, function.apply(orElse(null)), returns());
179   }
180
181   /**
182    * Asserts that all values in the collection pass the specified test.
183    *
184    * @param test The predicate test.
185    * @return The fluent return object.
186    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
187    */
188   public R isAll(Predicate<E> test) throws AssertionError {
189      if (test == null)
190         return returns();
191      value().forEach(x -> {
192         if (! test.test(x))
193            throw error(MSG_collectionDidNotContainTestedValue, r(value()));
194      });
195      return returns();
196   }
197
198   /**
199    * Asserts that at least one value in the collection passes the specified test.
200    *
201    * @param test The predicate test.
202    * @return The fluent return object.
203    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
204    */
205   public R isAny(Predicate<E> test) throws AssertionError {
206      if (test == null)
207         return returns();
208      for (var v : value())
209         if (test.test(v))
210            return returns();
211      throw error(MSG_collectionDidNotContainTestedValue, r(value()));
212   }
213
214   /**
215    * Asserts that the collection contains the expected value.
216    *
217    * @param entry The value to check for.
218    * @return The fluent return object.
219    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
220    */
221   public R isContains(E entry) throws AssertionError {
222      for (var v : value())
223         if (eq(v, entry))
224            return returns();
225      throw error(MSG_collectionDidNotContainExpectedValue, r(entry), r(value()));
226   }
227
228   /**
229    * Asserts that the collection exists and is empty.
230    *
231    * @return The fluent return object.
232    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
233    */
234   public R isEmpty() throws AssertionError {
235      if (! value().isEmpty())
236         throw error(MSG_collectionWasNotEmpty);
237      return returns();
238   }
239
240   /**
241    * Asserts that the collection contains the expected value.
242    *
243    * @param entry The value to check for.
244    * @return The fluent return object.
245    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
246    */
247   public R isNotContains(E entry) throws AssertionError {
248      value().forEach(x -> {
249         if (eq(x, entry))
250            throw error(MSG_collectionContainedUnexpectedValue, r(entry), r(value()));
251      });
252      return returns();
253   }
254
255   /**
256    * Asserts that the collection exists and is not empty.
257    *
258    * @return The fluent return object.
259    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
260    */
261   public R isNotEmpty() throws AssertionError {
262      if (value().isEmpty())
263         throw error(MSG_collectionWasEmpty);
264      return returns();
265   }
266
267   /**
268    * Asserts that the collection exists and is the specified size.
269    *
270    * @param size The expected size.
271    * @return The fluent return object.
272    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
273    */
274   public R isSize(int size) throws AssertionError {
275      if (getSize() != size)
276         throw error(MSG_collectionDidNotHaveExpectedSize, size, getSize());
277      return returns();
278   }
279
280   @Override /* Overridden from Assertion */
281   public FluentCollectionAssertion<E,R> setMsg(String msg, Object...args) {
282      super.setMsg(msg, args);
283      return this;
284   }
285
286   @Override /* Overridden from Assertion */
287   public FluentCollectionAssertion<E,R> setOut(PrintStream value) {
288      super.setOut(value);
289      return this;
290   }
291
292   @Override /* Overridden from Assertion */
293   public FluentCollectionAssertion<E,R> setSilent() {
294      super.setSilent();
295      return this;
296   }
297
298   @Override /* Overridden from Assertion */
299   public FluentCollectionAssertion<E,R> setStdOut() {
300      super.setStdOut();
301      return this;
302   }
303
304   @Override /* Overridden from Assertion */
305   public FluentCollectionAssertion<E,R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
306      super.setThrowable(value);
307      return this;
308   }
309
310   /**
311    * Returns the size of this collection if it is not <jk>null</jk>.
312    *
313    * @return the size of this collection if it is not <jk>null</jk>.
314    * @throws AssertionError If value was <jk>null</jk>.
315    */
316   protected int getSize() throws AssertionError { return value().size(); }
317}