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.StringUtils.*; 020 021import java.io.*; 022import java.util.*; 023import java.util.function.*; 024import java.util.regex.*; 025 026import org.apache.juneau.common.utils.*; 027import org.apache.juneau.cp.*; 028import org.apache.juneau.internal.*; 029import org.apache.juneau.serializer.*; 030 031/** 032 * Used for fluent assertion calls against strings. 033 * 034 * <h5 class='section'>Example:</h5> 035 * <p class='bjava'> 036 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 037 * <jv>client</jv> 038 * .get(<jsf>URL</jsf>) 039 * .run() 040 * .assertContent().is(<js>"OK"</js>); 041 * </p> 042 * 043 * 044 * <h5 class='section'>Test Methods:</h5> 045 * <p> 046 * <ul class='javatree'> 047 * <li class='jc'>{@link FluentStringAssertion} 048 * <ul class='javatreec'> 049 * <li class='jm'>{@link FluentStringAssertion#is(String) is(String)} 050 * <li class='jm'>{@link FluentStringAssertion#isNot(String) isNot(String)} 051 * <li class='jm'>{@link FluentStringAssertion#isLines(String...) isLines(String...)} 052 * <li class='jm'>{@link FluentStringAssertion#isSortedLines(String...) isSortedLines(String...)} 053 * <li class='jm'>{@link FluentStringAssertion#isIc(String) isIc(String)} 054 * <li class='jm'>{@link FluentStringAssertion#isNotIc(String) isNotIc(String)} 055 * <li class='jm'>{@link FluentStringAssertion#isContains(String...) isContains(String...)} 056 * <li class='jm'>{@link FluentStringAssertion#isNotContains(String...) isNotContains(String...)} 057 * <li class='jm'>{@link FluentStringAssertion#isEmpty() isEmpty()} 058 * <li class='jm'>{@link FluentStringAssertion#isNotEmpty() isNotEmpty()} 059 * <li class='jm'>{@link FluentStringAssertion#isString(Object) isString(Object)} 060 * <li class='jm'>{@link FluentStringAssertion#isMatches(String) isMatches(String)} 061 * <li class='jm'>{@link FluentStringAssertion#isPattern(String) isPattern(String)} 062 * <li class='jm'>{@link FluentStringAssertion#isPattern(String,int) isPattern(String,int)} 063 * <li class='jm'>{@link FluentStringAssertion#isPattern(Pattern) isPattern(Pattern)} 064 * <li class='jm'>{@link FluentStringAssertion#isStartsWith(String) isStartsWith(String)} 065 * <li class='jm'>{@link FluentStringAssertion#isEndsWith(String) isEndsWith(String)} 066 * </ul> 067 * <li class='jc'>{@link FluentObjectAssertion} 068 * <ul class='javatreec'> 069 * <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()} 070 * <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)} 071 * <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)} 072 * <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)} 073 * <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)} 074 * <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)} 075 * <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()} 076 * <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()} 077 * <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)} 078 * <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)} 079 * <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)} 080 * <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)} 081 * <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)} 082 * <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)} 083 * <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)} 084 * <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)} 085 * </ul> 086 * </ul> 087 * 088 * <h5 class='section'>Transform Methods:</h5> 089 * <p> 090 * <ul class='javatree'> 091 * <li class='jc'>{@link FluentStringAssertion} 092 * <ul class='javatreec'> 093 * <li class='jm'>{@link FluentStringAssertion#asReplaceAll(String,String) asReplaceAll(String,String)} 094 * <li class='jm'>{@link FluentStringAssertion#asReplace(String,String) asReplace(String,String)} 095 * <li class='jm'>{@link FluentStringAssertion#asUrlDecode() asUrlDecode()} 096 * <li class='jm'>{@link FluentStringAssertion#asLc() asLc()} 097 * <li class='jm'>{@link FluentStringAssertion#asUc() asUc()} 098 * <li class='jm'>{@link FluentStringAssertion#asLines() asLines()} 099 * <li class='jm'>{@link FluentStringAssertion#asSplit(String) asSplit(String)} 100 * <li class='jm'>{@link FluentStringAssertion#asLength() asLength()} 101 * <li class='jm'>{@link FluentStringAssertion#asOneLine() asOneLine()} 102 * </ul> 103 * <li class='jc'>{@link FluentObjectAssertion} 104 * <ul class='javatreec'> 105 * <li class='jm'>{@link FluentObjectAssertion#asString() asString()} 106 * <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)} 107 * <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)} 108 * <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()} 109 * <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()} 110 * <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)} 111 * <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()} 112 * </ul> 113 * </ul> 114 * 115 * <h5 class='section'>Configuration Methods:</h5> 116 * <p> 117 * <ul class='javatree'> 118 * <li class='jc'>{@link Assertion} 119 * <ul class='javatreec'> 120 * <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)} 121 * <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)} 122 * <li class='jm'>{@link Assertion#setSilent() setSilent()} 123 * <li class='jm'>{@link Assertion#setStdOut() setStdOut()} 124 * <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)} 125 * </ul> 126 * </ul> 127 * 128 * <h5 class='section'>See Also:</h5><ul> 129 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a> 130 * </ul> 131 * 132 * @param <R> The return type. 133 */ 134public class FluentStringAssertion<R> extends FluentObjectAssertion<String,R> { 135 136 //----------------------------------------------------------------------------------------------------------------- 137 // Static 138 //----------------------------------------------------------------------------------------------------------------- 139 140 private static final Messages MESSAGES = Messages.of(FluentStringAssertion.class, "Messages"); 141 private static final String 142 MSG_stringDifferedAtPosition = MESSAGES.getString("stringDifferedAtPosition"), 143 MSG_expectedStringHadDifferentNumbersOfLines = MESSAGES.getString("expectedStringHadDifferentNumbersOfLines"), 144 MSG_expectedStringHadDifferentValuesAtLine = MESSAGES.getString("expectedStringHadDifferentValuesAtLine"), 145 MSG_stringEqualedUnexpected = MESSAGES.getString("stringEqualedUnexpected"), 146 MSG_stringDidNotContainExpectedSubstring = MESSAGES.getString("stringDidNotContainExpectedSubstring"), 147 MSG_stringContainedUnexpectedSubstring = MESSAGES.getString("stringContainedUnexpectedSubstring"), 148 MSG_stringWasNotEmpty = MESSAGES.getString("stringWasNotEmpty"), 149 MSG_stringWasNull = MESSAGES.getString("stringWasNull"), 150 MSG_stringWasEmpty = MESSAGES.getString("stringWasEmpty"), 151 MSG_stringDidNotMatchExpectedPattern = MESSAGES.getString("stringDidNotMatchExpectedPattern"), 152 MSG_stringDidNotStartWithExpected = MESSAGES.getString("stringDidNotStartWithExpected"), 153 MSG_stringDidNotEndWithExpected = MESSAGES.getString("stringDidNotEndWithExpected"); 154 155 //----------------------------------------------------------------------------------------------------------------- 156 // Instance 157 //----------------------------------------------------------------------------------------------------------------- 158 159 private boolean javaStrings; 160 161 /** 162 * Constructor. 163 * 164 * @param value 165 * The object being tested. 166 * <br>Can be <jk>null</jk>. 167 * @param returns 168 * The object to return after a test method is called. 169 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 170 * used on the same assertion. 171 */ 172 public FluentStringAssertion(String value, R returns) { 173 this(null, value, returns); 174 } 175 176 /** 177 * Chained constructor. 178 * 179 * <p> 180 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 181 * 182 * @param creator 183 * The assertion that created this assertion. 184 * <br>Should be <jk>null</jk> if this is the top-level assertion. 185 * @param value 186 * The object being tested. 187 * <br>Can be <jk>null</jk>. 188 * @param returns 189 * The object to return after a test method is called. 190 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 191 * used on the same assertion. 192 */ 193 public FluentStringAssertion(Assertion creator, String value, R returns) { 194 super(creator, value, returns); 195 } 196 197 //----------------------------------------------------------------------------------------------------------------- 198 // Config methods 199 //----------------------------------------------------------------------------------------------------------------- 200 201 /** 202 * When enabled, text in the message is converted to valid Java strings. 203 * 204 * <p class='bjava'> 205 * <jv>value</jv>.replaceAll(<js>"\\\\"</js>, <js>"\\\\\\\\"</js>).replaceAll(<js>"\n"</js>, <js>"\\\\n"</js>).replaceAll(<js>"\t"</js>, <js>"\\\\t"</js>); 206 * </p> 207 * 208 * @return This object. 209 */ 210 public FluentStringAssertion<R> asJavaStrings() { 211 this.javaStrings = true; 212 return this; 213 } 214 215 //----------------------------------------------------------------------------------------------------------------- 216 // Transform methods 217 //----------------------------------------------------------------------------------------------------------------- 218 219 @Override /* FluentObjectAssertion */ 220 public FluentStringAssertion<R> asTransformed(Function<String,String> function) { // NOSONAR - Intentional. 221 return new FluentStringAssertion<>(this, function.apply(orElse(null)), returns()); 222 } 223 224 /** 225 * Performs the specified regular expression replacement on the underlying string. 226 * 227 * @param regex The regular expression to which this string is to be matched. 228 * @param replacement The string to be substituted for each match. 229 * @return This object. 230 */ 231 public FluentStringAssertion<R> asReplaceAll(String regex, String replacement) { 232 Utils.assertArgNotNull("regex", regex); 233 Utils.assertArgNotNull("replacement", replacement); 234 return asTransformed(x -> x == null ? null : x.replaceAll(regex, replacement)); 235 } 236 237 /** 238 * Performs the specified substring replacement on the underlying string. 239 * 240 * @param target The sequence of char values to be replaced. 241 * @param replacement The replacement sequence of char values. 242 * @return This object. 243 */ 244 public FluentStringAssertion<R> asReplace(String target, String replacement) { 245 Utils.assertArgNotNull("target", target); 246 Utils.assertArgNotNull("replacement", replacement); 247 return asTransformed(x -> x == null ? null : x.replace(target, replacement)); 248 } 249 250 /** 251 * URL-decodes the text in this assertion. 252 * 253 * @return This object. 254 */ 255 public FluentStringAssertion<R> asUrlDecode() { 256 return asTransformed(StringUtils::urlDecode); 257 } 258 259 /** 260 * Converts the text to lowercase. 261 * 262 * @return This object. 263 */ 264 public FluentStringAssertion<R> asLc() { 265 return asTransformed(x->x == null ? null : x.toLowerCase()); 266 } 267 268 /** 269 * Converts the text to uppercase. 270 * 271 * @return This object. 272 */ 273 public FluentStringAssertion<R> asUc() { 274 return asTransformed(x->x == null ? null : x.toUpperCase()); 275 } 276 277 /** 278 * Splits the string into lines. 279 * 280 * @return This object. 281 */ 282 public FluentListAssertion<String,R> asLines() { 283 return asSplit("[\r\n]+"); 284 } 285 286 /** 287 * Splits the string into lines using the specified regular expression. 288 * 289 * @param regex The delimiting regular expression 290 * @return This object. 291 */ 292 public FluentListAssertion<String,R> asSplit(String regex) { 293 Utils.assertArgNotNull("regex", regex); 294 return new FluentListAssertion<>(this, valueIsNull() ? null : Arrays.asList(value().trim().split(regex)), returns()); 295 } 296 297 /** 298 * Returns the length of this string as an integer assertion. 299 * 300 * @return This object. 301 */ 302 public FluentIntegerAssertion<R> asLength() { 303 return new FluentIntegerAssertion<>(this, valueIsNull() ? null : value().length(), returns()); 304 } 305 306 /** 307 * Removes any newlines from the string. 308 * 309 * @return This object. 310 */ 311 public FluentStringAssertion<R> asOneLine() { 312 return asTransformed(x->x == null ? null : x.replaceAll("\\s*[\r\n]+\\s*"," ")); 313 } 314 315 /** 316 * Removes any leading/trailing whitespace from the string. 317 * 318 * @return This object. 319 */ 320 public FluentStringAssertion<R> asTrimmed() { 321 return new FluentStringAssertion<>(this, valueIsNull() ? null : value().trim(), returns()); 322 } 323 324 //----------------------------------------------------------------------------------------------------------------- 325 // Test methods 326 //----------------------------------------------------------------------------------------------------------------- 327 328 /** 329 * Asserts that the text equals the specified value. 330 * 331 * <p> 332 * Similar to {@link #is(String)} except error message states diff position. 333 * 334 * <h5 class='section'>Example:</h5> 335 * <p class='bjava'> 336 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 337 * <jv>client</jv> 338 * .get(<jsf>URL</jsf>) 339 * .run() 340 * .assertContent().is(<js>"OK"</js>); 341 * </p> 342 * 343 * @param value 344 * The value to check against. 345 * <br>If multiple values are specified, they are concatenated with newlines. 346 * @return The fluent return object. 347 * @throws AssertionError If assertion failed. 348 */ 349 @Override 350 public R is(String value) throws AssertionError { 351 var s = orElse(null); 352 if (Utils.ne(value, s)) 353 throw error(MSG_stringDifferedAtPosition, diffPosition(value, s), fix(value), fix(s)); 354 return returns(); 355 } 356 357 /** 358 * Asserts that the text equals the specified value. 359 * 360 * @param value The value to check against. 361 * @return The fluent return object. 362 * @throws AssertionError If assertion failed. 363 */ 364 @Override 365 public R isNot(String value) throws AssertionError { 366 var s = orElse(null); 367 if (Utils.eq(value, s)) 368 throw error(MSG_stringEqualedUnexpected, fix(s)); 369 return returns(); 370 } 371 372 /** 373 * Asserts that the lines of text equals the specified value. 374 * 375 * <h5 class='section'>Example:</h5> 376 * <p class='bjava'> 377 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 378 * <jv>client</jv> 379 * .get(<jsf>URL</jsf>) 380 * .run() 381 * .assertContent().isLines( 382 * <js>"Line 1"</js>, 383 * <js>"Line 2"</js>, 384 * <js>"Line 3"</js> 385 * ); 386 * </p> 387 * 388 * @param lines 389 * The value to check against. 390 * <br>If multiple values are specified, they are concatenated with newlines. 391 * @return The fluent return object. 392 * @throws AssertionError If assertion failed. 393 */ 394 public R isLines(String...lines) throws AssertionError { 395 Utils.assertArgNotNull("lines", lines); 396 var v = Utils.join(lines, '\n'); 397 var s = value(); 398 if (Utils.ne(v, s)) 399 throw error(MSG_stringDifferedAtPosition, diffPosition(v, s), fix(v), fix(s)); 400 return returns(); 401 } 402 403 /** 404 * Asserts that the text equals the specified value after splitting both by newlines and sorting the rows. 405 * 406 * <h5 class='section'>Example:</h5> 407 * <p class='bjava'> 408 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 409 * <jv>client</jv> 410 * .get(<jsf>URL</jsf>) 411 * .run() 412 * .assertContent().isSortedLines( 413 * <js>"Line 1"</js>, 414 * <js>"Line 2"</js>, 415 * <js>"Line 3"</js> 416 * ); 417 * </p> 418 * 419 * @param lines 420 * The value to check against. 421 * <br>If multiple values are specified, they are concatenated with newlines. 422 * @return The fluent return object. 423 * @throws AssertionError If assertion failed. 424 */ 425 public R isSortedLines(String...lines) { 426 Utils.assertArgNotNull("lines", lines); 427 428 // Must work for windows too. 429 var e = Utils.join(lines, '\n').trim().split("[\r\n]+"); 430 var a = value().trim().split("[\r\n]+"); 431 432 if (e.length != a.length) 433 throw error(MSG_expectedStringHadDifferentNumbersOfLines, e.length, a.length); 434 435 Arrays.sort(e); 436 Arrays.sort(a); 437 438 for (var i = 0; i < e.length; i++) 439 if (! e[i].equals(a[i])) 440 throw error(MSG_expectedStringHadDifferentValuesAtLine, i+1, e[i], a[i]); 441 442 return returns(); 443 } 444 445 /** 446 * Asserts that the text equals the specified value ignoring case. 447 * 448 * @param value The value to check against. 449 * @return The fluent return object. 450 * @throws AssertionError If assertion failed. 451 */ 452 public R isIc(String value) throws AssertionError { 453 var s = orElse(null); 454 if (Utils.neic(value, s)) 455 throw error(MSG_stringDifferedAtPosition, diffPositionIc(value, s), fix(value), fix(s)); 456 return returns(); 457 } 458 459 /** 460 * Asserts that the text does not equal the specified value ignoring case. 461 * 462 * @param value The value to check against. 463 * @return The fluent return object. 464 * @throws AssertionError If assertion failed. 465 */ 466 public R isNotIc(String value) throws AssertionError { 467 var s = orElse(null); 468 if (Utils.eqic(value, s)) 469 throw error(MSG_stringEqualedUnexpected, fix(s)); 470 return returns(); 471 } 472 473 /** 474 * Asserts that the text contains all of the specified substrings. 475 * 476 * @param values The values to check against. 477 * @return The fluent return object. 478 * @throws AssertionError If assertion failed. 479 */ 480 public R isContains(String...values) throws AssertionError { 481 Utils.assertArgNotNull("values", values); 482 var s = orElse(null); 483 for (var substring : values) 484 if (substring != null && ! StringUtils.contains(s, substring)) 485 throw error(MSG_stringDidNotContainExpectedSubstring, fix(substring), fix(s)); 486 return returns(); 487 } 488 489 /** 490 * Asserts that the text doesn't contain any of the specified substrings. 491 * 492 * @param values The values to check against. 493 * @return The fluent return object. 494 * @throws AssertionError If assertion failed. 495 */ 496 public R isNotContains(String...values) throws AssertionError { 497 Utils.assertArgNotNull("values", values); 498 var s = orElse(null); 499 for (var substring : values) 500 if (substring != null && StringUtils.contains(s, substring)) 501 throw error(MSG_stringContainedUnexpectedSubstring, fix(substring), fix(s)); 502 return returns(); 503 } 504 505 /** 506 * Asserts that the text is empty. 507 * 508 * @return The fluent return object. 509 * @throws AssertionError If assertion failed. 510 */ 511 public R isEmpty() throws AssertionError { 512 var s = orElse(null); 513 if (s != null && ! s.isEmpty()) 514 throw error(MSG_stringWasNotEmpty, fix(s)); 515 return returns(); 516 } 517 518 /** 519 * Asserts that the text is not null or empty. 520 * 521 * @return The fluent return object. 522 * @throws AssertionError If assertion failed. 523 */ 524 public R isNotEmpty() throws AssertionError { 525 var s = orElse(null); 526 if (s == null) 527 throw error(MSG_stringWasNull); 528 if (s.isEmpty()) 529 throw error(MSG_stringWasEmpty); 530 return returns(); 531 } 532 533 /** 534 * Asserts that the text matches the specified pattern containing <js>"*"</js> meta characters. 535 * 536 * <p> 537 * The <js>"*"</js> meta character can be used to represent zero or more characters.. 538 * 539 * @param searchPattern The search pattern. 540 * @return The fluent return object. 541 * @throws AssertionError If assertion failed. 542 */ 543 public R isMatches(String searchPattern) throws AssertionError { 544 Utils.assertArgNotNull("searchPattern", searchPattern); 545 return isPattern(Utils.getMatchPattern3(searchPattern)); 546 } 547 548 /** 549 * Asserts that the text matches the specified regular expression. 550 * 551 * @param regex The pattern to test for. 552 * @return The fluent return object. 553 * @throws AssertionError If assertion failed. 554 */ 555 public R isPattern(String regex) throws AssertionError { 556 return isPattern(regex, 0); 557 } 558 559 /** 560 * Asserts that the text matches the specified regular expression. 561 * 562 * @param regex The pattern to test for. 563 * @param flags Pattern match flags. See {@link Pattern#compile(String, int)}. 564 * @return The fluent return object. 565 * @throws AssertionError If assertion failed. 566 */ 567 public R isPattern(String regex, int flags) throws AssertionError { 568 Utils.assertArgNotNull("regex", regex); 569 var p = Pattern.compile(regex, flags); 570 var s = value(); 571 if (! p.matcher(s).matches()) 572 throw error(MSG_stringDidNotMatchExpectedPattern, fix(regex), fix(s)); 573 return returns(); 574 } 575 576 /** 577 * Asserts that the text matches the specified regular expression pattern. 578 * 579 * @param pattern The pattern to test for. 580 * @return The fluent return object. 581 * @throws AssertionError If assertion failed. 582 */ 583 public R isPattern(Pattern pattern) throws AssertionError { 584 Utils.assertArgNotNull("pattern", pattern); 585 var s = value(); 586 if (! pattern.matcher(s).matches()) 587 throw error(MSG_stringDidNotMatchExpectedPattern, fix(pattern.pattern()), fix(s)); 588 return returns(); 589 } 590 591 /** 592 * Asserts that the text starts with the specified string. 593 * 594 * @param string The string to test for. 595 * @return The fluent return object. 596 * @throws AssertionError If assertion failed. 597 */ 598 public R isStartsWith(String string) { 599 Utils.assertArgNotNull("string", string); 600 var s = value(); 601 if (! s.startsWith(string)) 602 throw error(MSG_stringDidNotStartWithExpected, fix(string), fix(s)); 603 return returns(); 604 } 605 606 /** 607 * Asserts that the text ends with the specified string. 608 * 609 * @param string The string to test for. 610 * @return The fluent return object. 611 * @throws AssertionError If assertion failed. 612 */ 613 public R isEndsWith(String string) { 614 Utils.assertArgNotNull("string", string); 615 var s = value(); 616 if (! s.endsWith(string)) 617 throw error(MSG_stringDidNotEndWithExpected, fix(string), fix(s)); 618 return returns(); 619 } 620 621 /** 622 * Asserts that the text equals the specified object after calling {@link #toString()} on the object. 623 * 624 * @param value The value to check against. 625 * @return The fluent return object. 626 */ 627 public R isString(Object value) { 628 return is(value == null ? null : toString()); 629 } 630 631 //----------------------------------------------------------------------------------------------------------------- 632 // Fluent setters 633 //----------------------------------------------------------------------------------------------------------------- 634 @Override /* Overridden from Assertion */ 635 public FluentStringAssertion<R> setMsg(String msg, Object...args) { 636 super.setMsg(msg, args); 637 return this; 638 } 639 640 @Override /* Overridden from Assertion */ 641 public FluentStringAssertion<R> setOut(PrintStream value) { 642 super.setOut(value); 643 return this; 644 } 645 646 @Override /* Overridden from Assertion */ 647 public FluentStringAssertion<R> setSilent() { 648 super.setSilent(); 649 return this; 650 } 651 652 @Override /* Overridden from Assertion */ 653 public FluentStringAssertion<R> setStdOut() { 654 super.setStdOut(); 655 return this; 656 } 657 658 @Override /* Overridden from Assertion */ 659 public FluentStringAssertion<R> setThrowable(Class<? extends java.lang.RuntimeException> value) { 660 super.setThrowable(value); 661 return this; 662 } 663 //------------------------------------------------------------------------------------------------------------------ 664 // Utility methods 665 //------------------------------------------------------------------------------------------------------------------ 666 667 private String fix(String text) { 668 if (javaStrings) 669 text = text.replace("\\", "\\\\").replace("\n", "\\n").replace("\t", "\\t"); 670 return text; 671 } 672}