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