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