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