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