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