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}