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.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.ClassUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023 024import java.io.*; 025import java.util.*; 026import java.util.function.*; 027 028import org.apache.juneau.commons.function.*; 029import org.apache.juneau.commons.utils.*; 030 031/** 032 * Used for fluent assertion calls against POJOs. 033 * 034 * <h5 class='section'>Test Methods:</h5> 035 * <p> 036 * <ul class='javatree'> 037 * <li class='jc'>{@link FluentObjectAssertion} 038 * <ul class='javatreec'> 039 * <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()} 040 * <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)} 041 * <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)} 042 * <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)} 043 * <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)} 044 * <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)} 045 * <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()} 046 * <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()} 047 * <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)} 048 * <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)} 049 * <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, ThrowingFunction) isSameSerializedAs(Object, ThrowingFunction)} 050 * <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)} 051 * <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)} 052 * </ul> 053 * </ul> 054 * 055 * <h5 class='section'>Transform Methods:</h5> 056 * <p> 057 * <ul class='javatree'> 058 * <li class='jc'>{@link FluentObjectAssertion} 059 * <ul class='javatreec'> 060 * <li class='jm'>{@link FluentObjectAssertion#asString() asString()} 061 * <li class='jm'>{@link FluentObjectAssertion#asString(ThrowingFunction) asString(ThrowingFunction)} 062 * <li class='jm'>{@link FluentObjectAssertion#asTransformed(UnaryOperator) asTransformed(UnaryOperator)} 063 * <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()} 064 * </ul> 065 * </ul> 066 * 067 * <h5 class='section'>Configuration Methods:</h5> 068 * <p> 069 * <ul class='javatree'> 070 * <li class='jc'>{@link Assertion} 071 * <ul class='javatreec'> 072 * <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)} 073 * <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)} 074 * <li class='jm'>{@link Assertion#setSilent() setSilent()} 075 * <li class='jm'>{@link Assertion#setStdOut() setStdOut()} 076 * <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)} 077 * </ul> 078 * </ul> 079 * 080 * <h5 class='section'>See Also:</h5><ul> 081 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem</a> 082 * </ul> 083 * 084 * @param <T> The object type. 085 * @param <R> The return type. 086 */ 087@SuppressWarnings({ 088 "java:S115", // Constants use UPPER_snakeCase convention 089 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 090}) 091public class FluentObjectAssertion<T,R> extends FluentAssertion<R> { 092 093 private static final Messages MESSAGES = Messages.of(FluentObjectAssertion.class, "Messages"); 094 private static final String MSG_unexpectedType = MESSAGES.getString("unexpectedType"); 095 private static final String MSG_unexpectedComparison = MESSAGES.getString("unexpectedComparison"); 096 private static final String MSG_unexpectedValue = MESSAGES.getString("unexpectedValue"); 097 private static final String MSG_unexpectedValueDidNotExpect = MESSAGES.getString("unexpectedValueDidNotExpect"); 098 private static final String MSG_notTheSameValue = MESSAGES.getString("notTheSameValue"); 099 private static final String MSG_valueWasNull = MESSAGES.getString("valueWasNull"); 100 private static final String MSG_valueWasNotNull = MESSAGES.getString("valueWasNotNull"); 101 private static final String MSG_expectedValueNotFound = MESSAGES.getString("expectedValueNotFound"); 102 private static final String MSG_unexpectedValueFound = MESSAGES.getString("unexpectedValueFound"); 103 private static final String MSG_unexpectedValue2 = MESSAGES.getString("unexpectedValue2"); 104 105 private final T value; 106 107 /** 108 * Chained constructor. 109 * 110 * <p> 111 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 112 * 113 * @param creator 114 * The assertion that created this assertion. 115 * <br>Should be <jk>null</jk> if this is the top-level assertion. 116 * @param value 117 * The object being tested. 118 * <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value). 119 * @param returns 120 * The object to return after a test method is called. 121 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 122 * used on the same assertion. 123 */ 124 public FluentObjectAssertion(Assertion creator, T value, R returns) { 125 super(creator, returns); 126 this.value = value; 127 } 128 129 /** 130 * Constructor. 131 * 132 * @param value 133 * The object being tested. 134 * <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value). 135 * @param returns 136 * The object to return after a test method is called. 137 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 138 * used on the same assertion. 139 */ 140 public FluentObjectAssertion(T value, R returns) { 141 this(null, value, returns); 142 } 143 144 /** 145 * Converts this assertion into an {@link FluentAnyAssertion} so that it can be converted to other assertion types. 146 * 147 * @return This object. 148 */ 149 public FluentAnyAssertion<T,R> asAny() { 150 return new FluentAnyAssertion<>(this, orElse(null), returns()); 151 } 152 153 /** 154 * Converts this object to a string using {@link Object#toString} and returns it as a new assertion. 155 * 156 * <h5 class='section'>Example:</h5> 157 * <p class='bjava'> 158 * <jc>// Validates that the specified object is "foobar" after converting to a string.</jc> 159 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 160 * .asString() 161 * .is(<js>"foobar"</js>); 162 * </p> 163 * 164 * @return A new fluent string assertion. 165 */ 166 public FluentStringAssertion<R> asString() { 167 return new FluentStringAssertion<>(this, valueAsString(), returns()); 168 } 169 170 /** 171 * Converts this object to a string using the specified function and returns it as a new assertion. 172 * 173 * <h5 class='section'>Example:</h5> 174 * <p class='bjava'> 175 * <jc>// Validates that the specified object is "foobar" after converting to a string.</jc> 176 * <jsm>assertObject</jsm>(<jv>myPojo</jv>) 177 * .asString(<jv>x</jv>-><jv>x</jv>.toString()) 178 * .is(<js>"foobar"</js>); 179 * </p> 180 * 181 * @param function The conversion function. May throw a checked exception. 182 * @return A new fluent string assertion. 183 */ 184 public FluentStringAssertion<R> asString(ThrowingFunction<? super T,String> function) { 185 return new FluentStringAssertion<>(this, function.apply(value), returns()); 186 } 187 188 /** 189 * Applies a transform on the inner object and returns a new inner object. 190 * 191 * @param function The function to apply. 192 * @return This object. 193 */ 194 public FluentObjectAssertion<T,R> asTransformed(UnaryOperator<T> function) { 195 return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns()); 196 } 197 198 /** 199 * Applies a transform on the inner object and returns a new inner object. 200 * 201 * @param <T2> The transform-to type. 202 * @param function The function to apply. 203 * @return This object. 204 */ 205 public <T2> FluentObjectAssertion<T2,R> asTransformedTo(Function<T,T2> function) { 206 return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns()); 207 } 208 209 /** 210 * Asserts that the value passes the specified predicate test. 211 * 212 * @param test The predicate to use to test the value. 213 * @return The fluent return object. 214 * @throws AssertionError If assertion failed. 215 */ 216 public R is(Predicate<T> test) throws AssertionError { 217 if (nn(test) && ! test.test(value)) 218 throw error(getFailureMessage(test, value)); 219 return returns(); 220 } 221 222 /** 223 * Asserts that the value equals the specified value. 224 * 225 * @param value The value to check against. 226 * @return The fluent return object. 227 * @throws AssertionError If assertion failed. 228 */ 229 public R is(T value) throws AssertionError { 230 if (this.value == value) 231 return returns(); 232 if (! equals(orElse(null), value)) 233 throw error(MSG_unexpectedValue, r(value), r(this.value)); 234 return returns(); 235 } 236 237 /** 238 * Asserts that the value is one of the specified values. 239 * 240 * @param values The values to check against. 241 * @return The fluent return object. 242 * @throws AssertionError If assertion failed. 243 */ 244 @SafeVarargs 245 public final R isAny(T...values) throws AssertionError { 246 for (var v : values) 247 if (equals(orElse(null), v)) 248 return returns(); 249 throw error(MSG_expectedValueNotFound, r(values), r(value)); 250 } 251 252 /** 253 * Asserts that the object is an instance of the specified class. 254 * 255 * <h5 class='section'>Example:</h5> 256 * <p class='bjava'> 257 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 258 * <jsm>assertObject</jsm>(<jv>myPojo</jv>).isExactType(MyBean.<jk>class</jk>); 259 * </p> 260 * 261 * @param type The value to check against. 262 * @return The fluent return object. 263 * @throws AssertionError If assertion failed. 264 */ 265 public R isExactType(Class<?> type) throws AssertionError { 266 reqnn("type", type); 267 if (value().getClass() != type) 268 throw error(MSG_unexpectedType, cn(type), cn(value)); 269 return returns(); 270 } 271 272 /** 273 * Asserts that the object is not null. 274 * 275 * <p> 276 * Equivalent to {@link #isNotNull()}. 277 * 278 * @return The fluent return object. 279 * @throws AssertionError If assertion failed. 280 */ 281 public R isExists() throws AssertionError { return isNotNull(); } 282 283 /** 284 * Asserts that the value does not equal the specified value. 285 * 286 * @param value The value to check against. 287 * @return The fluent return object. 288 * @throws AssertionError If assertion failed. 289 */ 290 public R isNot(T value) throws AssertionError { 291 if (equals(orElse(null), value)) 292 throw error(MSG_unexpectedValueDidNotExpect, r(value), r(orElse(null))); 293 return returns(); 294 } 295 296 /** 297 * Asserts that the value is not one of the specified values. 298 * 299 * @param values The values to check against. 300 * @return The fluent return object. 301 * @throws AssertionError If assertion failed. 302 */ 303 @SafeVarargs 304 public final R isNotAny(T...values) throws AssertionError { 305 for (var v : values) 306 if (equals(orElse(null), v)) 307 throw error(MSG_unexpectedValueFound, r(v), r(value)); 308 return returns(); 309 } 310 311 /** 312 * Asserts that the object is not null. 313 * 314 * <p> 315 * Equivalent to {@link #isNotNull()}. 316 * 317 * @return The fluent return object. 318 * @throws AssertionError If assertion failed. 319 */ 320 public R isNotNull() throws AssertionError { 321 if (value == null) 322 throw error(MSG_valueWasNull); 323 return returns(); 324 } 325 326 /** 327 * Asserts that the object i null. 328 * 329 * <p> 330 * Equivalent to {@link #isNotNull()}. 331 * 332 * @return The fluent return object. 333 * @throws AssertionError If assertion failed. 334 */ 335 public R isNull() throws AssertionError { 336 if (nn(value)) 337 throw error(MSG_valueWasNotNull); 338 return returns(); 339 } 340 341 /** 342 * Asserts that the specified object is the same object as this object. 343 * 344 * @param value The value to check against. 345 * @return The fluent return object. 346 * @throws AssertionError If assertion failed. 347 */ 348 public R isSame(T value) throws AssertionError { 349 if (this.value == value) 350 return returns(); 351 throw error(MSG_notTheSameValue, r(value), id(value), r(this.value), id(this.value)); 352 } 353 354 /** 355 * Asserts that the specified object is the same as this object after converting both to strings using the specified function. 356 * 357 * <h5 class='section'>Example:</h5> 358 * <p class='bjava'> 359 * <jsm>assertObject</jsm>(<jv>myPojo</jv>).isSameSerializedAs(<jv>other</jv>, <jv>x</jv> -> <jv>x</jv>.toString()); 360 * </p> 361 * 362 * @param o The object to compare against. 363 * @param function The function to use to serialize the objects. May throw a checked exception. 364 * @return The fluent return object. 365 * @throws AssertionError If assertion failed. 366 */ 367 @SuppressWarnings({ 368 "unchecked" // Comparison function is applied to both the typed value and the untyped comparison object. 369 }) 370 public R isSameSerializedAs(Object o, ThrowingFunction<? super T,String> function) { 371 var s1 = function.apply(value); 372 var s2 = function.apply((T)o); 373 if (neq(s1, s2)) 374 throw error(MSG_unexpectedComparison, s2, s1); 375 return returns(); 376 } 377 378 /** 379 * Asserts that the value converted to a string equals the specified value. 380 * 381 * @param value The value to check against. 382 * @return The fluent return object. 383 * @throws AssertionError If assertion failed. 384 */ 385 public R isString(String value) { 386 return asString().is(value); 387 } 388 389 /** 390 * Asserts that the object is an instance of the specified class. 391 * 392 * <h5 class='section'>Example:</h5> 393 * <p class='bjava'> 394 * <jc>// Validates that the specified object is an instance of MyBean.</jc> 395 * <jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>); 396 * </p> 397 * 398 * @param parent The value to check against. 399 * @return The fluent return object. 400 * @throws AssertionError If assertion failed. 401 */ 402 public R isType(Class<?> parent) throws AssertionError { 403 reqnn("parent", parent); 404 if (! info(value()).isAssignableTo(parent)) 405 throw error(MSG_unexpectedType, cn(parent), cn(value)); 406 return returns(); 407 } 408 409 @Override /* Overridden from Assertion */ 410 public FluentObjectAssertion<T,R> setMsg(String msg, Object...args) { 411 super.setMsg(msg, args); 412 return this; 413 } 414 415 @Override /* Overridden from Assertion */ 416 public FluentObjectAssertion<T,R> setOut(PrintStream value) { 417 super.setOut(value); 418 return this; 419 } 420 421 @Override /* Overridden from Assertion */ 422 public FluentObjectAssertion<T,R> setSilent() { 423 super.setSilent(); 424 return this; 425 } 426 427 @Override /* Overridden from Assertion */ 428 public FluentObjectAssertion<T,R> setStdOut() { 429 super.setStdOut(); 430 return this; 431 } 432 433 @Override /* Overridden from Assertion */ 434 public FluentObjectAssertion<T,R> setThrowable(Class<? extends RuntimeException> value) { 435 super.setThrowable(value); 436 return this; 437 } 438 439 /** 440 * Returns the string form of the inner object. 441 * Subclasses can override this method to affect the {@link #asString()} method (and related). 442 */ 443 @Override /* Overridden from Object */ 444 public String toString() { 445 return valueAsString(); 446 } 447 448 /** 449 * Checks two objects for equality. 450 * 451 * @param o1 The first object. 452 * @param o2 The second object. 453 * @return <jk>true</jk> if the objects are equal. 454 */ 455 protected boolean equals(Object o1, Object o2) { 456 if (o1 == o2) 457 return true; 458 if (o1 == null || o2 == null) 459 return false; 460 if (o1.equals(o2)) 461 return true; 462 if (isArray(o1)) 463 return readable(o1).equals(readable(o2)); 464 return false; 465 } 466 467 /** 468 * Returns the predicate failure message. 469 * 470 * <p> 471 * If the predicate extends from {@link AssertionPredicate}, then the message comes from {@link AssertionPredicate#getFailureMessage()}. 472 * Otherwise, returns a generic <js>"Unexpected value: x"</js> message. 473 * 474 * @param p The function to run against the value. 475 * @param value The value that failed the test. 476 * @return The result, never <jk>null</jk>. 477 */ 478 protected String getFailureMessage(Predicate<?> p, Object value) { 479 if (p instanceof AssertionPredicate<?> p2) 480 return p2.getFailureMessage(); 481 return f(MSG_unexpectedValue2, r(value)); 482 } 483 484 /** 485 * Returns the result of running the specified function against the value and returns the result. 486 * 487 * @param <T2> The mapper-to type. 488 * @param mapper The function to run against the value. 489 * @return The result, never <jk>null</jk>. 490 */ 491 protected <T2> Optional<T2> map(Function<? super T,? extends T2> mapper) { 492 return opt().map(mapper); 493 } 494 495 /** 496 * Returns the value wrapped in an {@link Optional}. 497 * 498 * @return The value wrapped in an {@link Optional}. 499 */ 500 protected Optional<T> opt() { 501 return Shorts.o(value); // NOAI 502 } 503 504 /** 505 * Returns the inner value or the other value if the value is <jk>null</jk>. 506 * 507 * @param other The other value. 508 * @return The inner value. 509 */ 510 protected T orElse(T other) { 511 return value == null ? other : value; 512 } 513 514 /** 515 * Returns the inner value after asserting it is not <jk>null</jk>. 516 * 517 * @return The inner value. 518 * @throws AssertionError If inner value was <jk>null</jk>. 519 */ 520 protected T value() throws AssertionError { 521 isExists(); 522 return value; 523 } 524 525 /** 526 * Returns the inner value as a string. 527 * 528 * @return The inner value as a string, or <jk>null</jk> if the value was null. 529 */ 530 protected String valueAsString() { 531 return readable(value); 532 } 533 534 /** 535 * Returns <jk>true</jk> if the inner value is not null. 536 * 537 * @return <jk>true</jk> if the inner value is not null. 538 */ 539 protected boolean valueIsNotNull() { 540 return nn(value); 541 } 542 543 /** 544 * Returns <jk>true</jk> if the inner value is null. 545 * 546 * @return <jk>true</jk> if the inner value is null. 547 */ 548 protected boolean valueIsNull() { 549 return value == null; 550 } 551}