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.internal.StringUtils.*;
016
017import java.util.*;
018import java.util.function.*;
019import java.util.regex.*;
020import java.util.stream.*;
021
022import org.apache.juneau.internal.*;
023
024/**
025 * Used for fluent assertion calls against strings.
026 *
027 * <h5 class='section'>Example:</h5>
028 * <p class='bcode w800'>
029 *    <jc>// Validates the response body of an HTTP call is the text "OK".</jc>
030 *    <jv>client</jv>
031 *       .get(<jsf>URL</jsf>)
032 *       .run()
033 *       .assertBody().is(<js>"OK"</js>);
034 * </p>
035 *
036 * @param <R> The return type.
037 */
038@FluentSetters(returns="FluentStringAssertion<R>")
039public class FluentStringAssertion<R> extends FluentObjectAssertion<R> {
040
041   private String text;
042   private boolean javaStrings;
043
044   /**
045    * Constructor.
046    *
047    * @param text The text being tested.
048    * @param returns The object to return after the test.
049    */
050   public FluentStringAssertion(String text, R returns) {
051      this(null, text, returns);
052   }
053
054   /**
055    * Constructor.
056    *
057    * @param creator The assertion that created this assertion.
058    * @param text The text being tested.
059    * @param returns The object to return after the test.
060    */
061   public FluentStringAssertion(Assertion creator, String text, R returns) {
062      super(creator, text, returns);
063      this.text = text;
064   }
065
066   /**
067    * When enabled, text in the message is converted to valid Java strings.
068    *
069    * <p class='bcode w800'>
070    *    value.replaceAll(<js>"\\\\"</js>, <js>"\\\\\\\\"</js>).replaceAll(<js>"\n"</js>, <js>"\\\\n"</js>).replaceAll(<js>"\t"</js>, <js>"\\\\t"</js>);
071    * </p>
072    *
073    * @return This object (for method chaining).
074    */
075   @FluentSetter
076   public FluentStringAssertion<R> javaStrings() {
077      this.javaStrings = true;
078      return this;
079   }
080
081   /**
082    * Performs the specified regular expression replacement on the underlying string.
083    *
084    * @param regex The regular expression to which this string is to be matched.
085    * @param replacement The string to be substituted for each match.
086    * @return This object (for method chaining).
087    */
088   public FluentStringAssertion<R> replaceAll(String regex, String replacement) {
089      assertNotNull("regex", regex);
090      assertNotNull("replacement", replacement);
091      return apply(x -> x == null ? null : text.replaceAll(regex, replacement));
092   }
093
094   /**
095    * Performs the specified substring replacement on the underlying string.
096    *
097    * @param target The sequence of char values to be replaced.
098    * @param replacement The replacement sequence of char values.
099    * @return This object (for method chaining).
100    */
101   public FluentStringAssertion<R> replace(String target, String replacement) {
102      assertNotNull("target", target);
103      assertNotNull("replacement", replacement);
104      return apply(x -> x == null ? null : text.replace(target, replacement));
105   }
106
107   /**
108    * URL-decodes the text in this assertion.
109    *
110    * @return The response object (for method chaining).
111    */
112   public FluentStringAssertion<R> urlDecode() {
113      return apply(x->StringUtils.urlDecode(x));
114   }
115
116   /**
117    * Sorts the contents of the text by lines.
118    *
119    * @return The response object (for method chaining).
120    */
121   public FluentStringAssertion<R> sort() {
122      return apply(x->x == null ? null : Arrays.asList(x.trim().split("[\r\n]+")).stream().sorted().collect(Collectors.joining("\n")));
123   }
124
125   /**
126    * Converts the text to lowercase.
127    *
128    * @return The response object (for method chaining).
129    */
130   public FluentStringAssertion<R> lc() {
131      return apply(x->x == null ? null : x.toLowerCase());
132   }
133
134   /**
135    * Converts the text to uppercase.
136    *
137    * @return The response object (for method chaining).
138    */
139   public FluentStringAssertion<R> uc() {
140      return apply(x->x == null ? null : x.toUpperCase());
141   }
142
143   /**
144    * Applies an abitrary function against the text in this assertion.
145    *
146    * @param f The function to apply.
147    * @return The response object (for method chaining).
148    */
149   public FluentStringAssertion<R> apply(Function<String,String> f) {
150      return new FluentStringAssertion<>(this, f.apply(text), returns());
151   }
152
153   /**
154    * Asserts that the text equals the specified value.
155    *
156    * <p>
157    * Similar to {@link #is(String)} except error message states diff position.
158    *
159    * <h5 class='section'>Example:</h5>
160    * <p class='bcode w800'>
161    *    <jc>// Validates the response body of an HTTP call is the text "OK".</jc>
162    *    client
163    *       .get(<jsf>URL</jsf>)
164    *       .run()
165    *       .assertBody().isEquals(<js>"OK"</js>);
166    * </p>
167    *
168    * @param value
169    *    The value to check against.
170    *    <br>If multiple values are specified, they are concatenated with newlines.
171    * @return The response object (for method chaining).
172    * @throws AssertionError If assertion failed.
173    */
174   public R isEqual(String value) throws AssertionError {
175      if (! StringUtils.isEquals(value, text))
176         throw error("Text differed at position {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", diffPosition(value, text), fix(value), fix(text));
177      return returns();
178   }
179
180   /**
181    * Asserts that the lines of text equals the specified value.
182    *
183    * <h5 class='section'>Example:</h5>
184    * <p class='bcode w800'>
185    *    <jc>// Validates the response body of an HTTP call is the text "OK".</jc>
186    *    client
187    *       .get(<jsf>URL</jsf>)
188    *       .run()
189    *       .assertBody().isEqualLines(
190    *          <js>"Line 1"</js>,
191    *          <js>"Line 2"</js>,
192    *          <js>"Line 3"</js>
193    *       );
194    * </p>
195    *
196    * @param lines
197    *    The value to check against.
198    *    <br>If multiple values are specified, they are concatenated with newlines.
199    * @return The response object (for method chaining).
200    * @throws AssertionError If assertion failed.
201    */
202   public R isEqualLines(String...lines) throws AssertionError {
203      assertNotNull("lines", lines);
204      String v = join(lines, '\n');
205      if (! StringUtils.isEquals(v, text))
206         throw error("Text differed at position {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", diffPosition(v, text), fix(v), fix(text));
207      return returns();
208   }
209
210   /**
211    * Asserts that the text equals the specified value after splitting both by newlines and sorting the rows.
212    *
213    * <h5 class='section'>Example:</h5>
214    * <p class='bcode w800'>
215    *    <jc>// Validates the response body of an HTTP call is the text "OK".</jc>
216    *    client
217    *       .get(<jsf>URL</jsf>)
218    *       .run()
219    *       .assertBody().isEqualSortedLines(
220    *          <js>"Line 1"</js>,
221    *          <js>"Line 2"</js>,
222    *          <js>"Line 3"</js>
223    *       );
224    * </p>
225    *
226    * @param lines
227    *    The value to check against.
228    *    <br>If multiple values are specified, they are concatenated with newlines.
229    * @return The response object (for method chaining).
230    * @throws AssertionError If assertion failed.
231    */
232   public R isEqualSortedLines(String...lines) {
233      assertNotNull("lines", lines);
234      exists();
235
236      // Must work for windows too.
237      String[] e = StringUtils.join(lines, '\n').trim().split("[\r\n]+"), a = this.text.trim().split("[\r\n]+");
238
239      if (e.length != a.length)
240         throw error("Expected text had different numbers of lines.\n\tExpected=[{0}]\n\tActual=[{1}]", e.length, a.length);
241
242      Arrays.sort(e);
243      Arrays.sort(a);
244
245      for (int i = 0; i < e.length; i++)
246         if (! e[i].equals(a[i]))
247            throw error("Expected text had different values at line {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", i+1, e[i], a[i]);
248
249      return returns();
250   }
251
252   /**
253    * Asserts that the text equals the specified value.
254    *
255    * <p>
256    * Similar to {@link #isEqual(String)} except error message doesn't state diff position.
257    *
258    * <h5 class='section'>Example:</h5>
259    * <p class='bcode w800'>
260    *    <jc>// Validates the response body of an HTTP call is the text "OK".</jc>
261    *    client
262    *       .get(<jsf>URL</jsf>)
263    *       .run()
264    *       .assertBody().is(<js>"OK"</js>);
265    * </p>
266    *
267    * @param value
268    *    The value to check against.
269    *    <br>If multiple values are specified, they are concatenated with newlines.
270    * @return The response object (for method chaining).
271    * @throws AssertionError If assertion failed.
272    */
273   public R is(String value) throws AssertionError {
274      if (! StringUtils.isEquals(value, text))
275         throw error("Unexpected value.\n\tExpected=[{0}]\n\tActual=[{1}]", fix(value), fix(text));
276      return isEqual(value);
277   }
278
279   /**
280    * Asserts that the text equals the specified value ignoring case.
281    *
282    * @param value The value to check against.
283    * @return The response object (for method chaining).
284    * @throws AssertionError If assertion failed.
285    */
286   public R isEqualIc(String value) throws AssertionError {
287      if (! StringUtils.isEqualsIc(value, text))
288         throw error("Text differed at position {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", diffPositionIc(value, text), fix(value), fix(text));
289      return returns();
290   }
291
292   /**
293    * Asserts that the text equals the specified value.
294    *
295    * @param value The value to check against.
296    * @return The response object (for method chaining).
297    * @throws AssertionError If assertion failed.
298    */
299   public R doesNotEqual(String value) throws AssertionError {
300      if (StringUtils.isEquals(value, text))
301         throw error("Text equaled unexpected.\n\tText=[{0}]", fix(text));
302      return returns();
303   }
304
305   /**
306    * Asserts that the text equals the specified value.
307    *
308    * <p>
309    * Equivalent to {@link #doesNotEqual(String)}.
310    *
311    * @param value The value to check against.
312    * @return The response object (for method chaining).
313    * @throws AssertionError If assertion failed.
314    */
315   public R isNot(String value) throws AssertionError {
316      return doesNotEqual(value);
317   }
318
319   /**
320    * Asserts that the text does not equal the specified value ignoring case.
321    *
322    * @param value The value to check against.
323    * @return The response object (for method chaining).
324    * @throws AssertionError If assertion failed.
325    */
326   public R doesNotEqualIc(String value) throws AssertionError {
327      if (StringUtils.isEqualsIc(value, text))
328         throw error("Text equaled unexpected.\n\tText=[{0}]", fix(text));
329      return returns();
330   }
331
332   /**
333    * Asserts that the text contains all of the specified substrings.
334    *
335    * @param values The values to check against.
336    * @return The response object (for method chaining).
337    * @throws AssertionError If assertion failed.
338    */
339   public R contains(String...values) throws AssertionError {
340      assertNotNull("values", values);
341      for (String substring : values)
342         if (substring != null && ! StringUtils.contains(text, substring))
343            throw error("Text did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(text));
344      return returns();
345   }
346
347   /**
348    * Asserts that the text doesn't contain any of the specified substrings.
349    *
350    * @param values The values to check against.
351    * @return The response object (for method chaining).
352    * @throws AssertionError If assertion failed.
353    */
354   public R doesNotContain(String...values) throws AssertionError {
355      assertNotNull("values", values);
356      for (String substring : values)
357         if (substring != null && StringUtils.contains(text, substring))
358            throw error("Text contained unexpected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(text));
359      return returns();
360   }
361
362   /**
363    * Asserts that the text is not empty.
364    *
365    * @return The response object (for method chaining).
366    * @throws AssertionError If assertion failed.
367    */
368   public R isEmpty() throws AssertionError {
369      if (text != null && ! text.isEmpty())
370         throw error("Text was not empty.\n\tText=[{0}]", fix(text));
371      return returns();
372   }
373
374   /**
375    * Asserts that the text is not null or empty.
376    *
377    * @return The response object (for method chaining).
378    * @throws AssertionError If assertion failed.
379    */
380   public R isNotEmpty() throws AssertionError {
381      if (text == null)
382         throw error("Text was null.");
383      if (text.isEmpty())
384         throw error("Text was empty.");
385      return returns();
386   }
387
388   /**
389    * Asserts that the text matches the specified regular expression.
390    *
391    * @param regex The pattern to test for.
392    * @return The response object (for method chaining).
393    * @throws AssertionError If assertion failed.
394    */
395   public R matches(String regex) throws AssertionError {
396      return matches(regex, 0);
397   }
398
399   /**
400    * Asserts that the text matches the specified pattern containing <js>"*"</js> meta characters.
401    *
402    * <p>
403    * The <js>"*"</js> meta character can be used to represent zero or more characters..
404    *
405    * @param searchPattern The search pattern.
406    * @return The response object (for method chaining).
407    * @throws AssertionError If assertion failed.
408    */
409   public R matchesSimple(String searchPattern) throws AssertionError {
410      assertNotNull("searchPattern", searchPattern);
411      return matches(getMatchPattern(searchPattern));
412   }
413
414   /**
415    * Asserts that the text doesn't match the specified regular expression.
416    *
417    * @param regex The pattern to test for.
418    * @return The response object (for method chaining).
419    * @throws AssertionError If assertion failed.
420    */
421   public R doesNotMatch(String regex) throws AssertionError {
422      assertNotNull("regex", regex);
423      return doesNotMatch(regex, 0);
424   }
425
426   /**
427    * Asserts that the text matches the specified regular expression.
428    *
429    * @param regex The pattern to test for.
430    * @param flags Pattern match flags.  See {@link Pattern#compile(String, int)}.
431    * @return The response object (for method chaining).
432    * @throws AssertionError If assertion failed.
433    */
434   public R matches(String regex, int flags) throws AssertionError {
435      assertNotNull("regex", regex);
436      exists();
437      Pattern p = Pattern.compile(regex, flags);
438      if (! p.matcher(text).matches())
439         throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(regex), fix(text));
440      return returns();
441   }
442
443   /**
444    * Asserts that the text doesn't match the specified regular expression.
445    *
446    * @param regex The pattern to test for.
447    * @param flags Pattern match flags.  See {@link Pattern#compile(String, int)}.
448    * @return The response object (for method chaining).
449    * @throws AssertionError If assertion failed.
450    */
451   public R doesNotMatch(String regex, int flags) throws AssertionError {
452      assertNotNull("regex", regex);
453      return doesNotMatch(Pattern.compile(regex, flags));
454   }
455
456   /**
457    * Asserts that the text matches the specified regular expression pattern.
458    *
459    * @param pattern The pattern to test for.
460    * @return The response object (for method chaining).
461    * @throws AssertionError If assertion failed.
462    */
463   public R matches(Pattern pattern) throws AssertionError {
464      assertNotNull("pattern", pattern);
465      exists();
466      if (! pattern.matcher(text).matches())
467         throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(text));
468      return returns();
469   }
470
471   /**
472    * Asserts that the text doesn't match the specified regular expression pattern.
473    *
474    * @param pattern The pattern to test for.
475    * @return The response object (for method chaining).
476    * @throws AssertionError If assertion failed.
477    */
478   public R doesNotMatch(Pattern pattern) throws AssertionError {
479      assertNotNull("pattern", pattern);
480      if (text != null && pattern.matcher(text).matches())
481         throw error("Text matched unexpected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(text));
482      return returns();
483   }
484
485   /**
486    * Asserts that the text starts with the specified string.
487    *
488    * @param string The string to test for.
489    * @return The response object (for method chaining).
490    * @throws AssertionError If assertion failed.
491    */
492   public R startsWith(String string) {
493      exists();
494      assertNotNull("string", string);
495      if (! text.startsWith(string))
496         throw error("Text did not start with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(text));
497      return returns();
498   }
499
500   /**
501    * Asserts that the text ends with the specified string.
502    *
503    * @param string The string to test for.
504    * @return The response object (for method chaining).
505    * @throws AssertionError If assertion failed.
506    */
507   public R endsWith(String string) {
508      exists();
509      assertNotNull("string", string);
510      if (! text.endsWith(string))
511         throw error("Text did not end with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(text));
512      return returns();
513   }
514
515   //------------------------------------------------------------------------------------------------------------------
516   // Utility methods
517   //------------------------------------------------------------------------------------------------------------------
518
519   private String fix(String text) {
520      if (javaStrings)
521         text = text.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t");
522      return text;
523   }
524
525   // <FluentSetters>
526
527   @Override /* GENERATED - Assertion */
528   public FluentStringAssertion<R> msg(String msg, Object...args) {
529      super.msg(msg, args);
530      return this;
531   }
532
533   @Override /* GENERATED - Assertion */
534   public FluentStringAssertion<R> stderr() {
535      super.stderr();
536      return this;
537   }
538
539   @Override /* GENERATED - Assertion */
540   public FluentStringAssertion<R> stdout() {
541      super.stdout();
542      return this;
543   }
544
545   // </FluentSetters>
546}