View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.AvoidEscapedUnicodeCharactersCheck.MSG_KEY;
24  
25  import java.util.Arrays;
26  import java.util.List;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  import java.util.stream.IntStream;
30  
31  import org.junit.jupiter.api.Test;
32  
33  import com.google.common.base.Splitter;
34  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
35  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
37  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38  
39  public class AvoidEscapedUnicodeCharactersCheckTest extends AbstractModuleTestSupport {
40  
41      // C0 (ASCII and derivatives)
42      // https://en.wiktionary.org/wiki/Appendix:Control_characters#C0_.28ASCII_and_derivatives.29
43      private static final int[] C0_CONTROL_CHARACTER_INDICES = {
44          0x0000,
45          0x0001,
46          0x0002,
47          0x0003,
48          0x0004,
49          0x0005,
50          0x0006,
51          0x0007,
52          0x0008,
53          0x0009,
54          0x000A,
55          0x000B,
56          0x000C,
57          0x000D,
58          0x000E,
59          0x000F,
60          0x0010,
61          0x0011,
62          0x0012,
63          0x0013,
64          0x0014,
65          0x0015,
66          0x0016,
67          0x0017,
68          0x0018,
69          0x0019,
70          0x001A,
71          0x001B,
72          0x001C,
73          0x001D,
74          0x001E,
75          0x001F,
76      };
77  
78      // C1 set
79      // https://en.wiktionary.org/wiki/Appendix:Control_characters#C1_set
80      private static final int[] C1_CONTROL_CHARACTER_INDICES = {
81          0x0080,
82          0x0081,
83          0x0082,
84          0x0083,
85          0x0084,
86          0x0085,
87          0x0086,
88          0x0087,
89          0x0088,
90          0x0089,
91          0x008A,
92          0x008B,
93          0x008C,
94          0x008D,
95          0x008E,
96          0x008F,
97          0x0090,
98          0x0091,
99          0x0092,
100         0x0093,
101         0x0094,
102         0x0095,
103         0x0096,
104         0x0097,
105         0x0098,
106         0x0099,
107         0x009A,
108         0x009B,
109         0x009C,
110         0x009D,
111         0x009E,
112         0x009F,
113     };
114 
115     // Other control characters which do not occur in the C0 or C1 sets
116     // https://en.wiktionary.org/wiki/Appendix:Control_characters#Unicode_control_characters
117     private static final int[] OTHER_CONTROL_CHARACTER_INDICES = {
118         0x00AD,
119         0x034F,
120         0x070F,
121         0x180E,
122         0x200B,
123         0x200C,
124         0x200D,
125         0x200E,
126         0x200F,
127         0x202A,
128         0x202B,
129         0x202C,
130         0x202D,
131         0x202E,
132         0x2060,
133         0x2061,
134         0x2062,
135         0x2063,
136         0x2064,
137         0x206A,
138         0x206B,
139         0x206C,
140         0x206D,
141         0x206E,
142         0x206F,
143         0xFEFF,
144         0xFFF9,
145         0xFFFA,
146         0xFFFB,
147     };
148 
149     @Override
150     public String getPackageLocation() {
151         return "com/puppycrawl/tools/checkstyle/checks/avoidescapedunicodecharacters";
152     }
153 
154     @Test
155     public void testGetRequiredTokens() {
156         final AvoidEscapedUnicodeCharactersCheck checkObj =
157             new AvoidEscapedUnicodeCharactersCheck();
158         final int[] expected = {
159             TokenTypes.STRING_LITERAL,
160             TokenTypes.CHAR_LITERAL,
161             TokenTypes.TEXT_BLOCK_CONTENT,
162             TokenTypes.SINGLE_LINE_COMMENT,
163             TokenTypes.BLOCK_COMMENT_BEGIN,
164         };
165         assertWithMessage("Required tokens differ from expected")
166             .that(checkObj.getRequiredTokens())
167             .isEqualTo(expected);
168     }
169 
170     @Test
171     public void testDefaultOne() throws Exception {
172         final String[] expected = {
173             "17:38: " + getCheckMessage(MSG_KEY),
174             "19:38: " + getCheckMessage(MSG_KEY),
175             "21:38: " + getCheckMessage(MSG_KEY),
176             "25:38: " + getCheckMessage(MSG_KEY),
177             "27:38: " + getCheckMessage(MSG_KEY),
178             "32:24: " + getCheckMessage(MSG_KEY),
179             "37:36: " + getCheckMessage(MSG_KEY),
180             "39:36: " + getCheckMessage(MSG_KEY),
181             "42:24: " + getCheckMessage(MSG_KEY),
182             "47:38: " + getCheckMessage(MSG_KEY),
183             "49:38: " + getCheckMessage(MSG_KEY),
184             "51:38: " + getCheckMessage(MSG_KEY),
185             "53:47: " + getCheckMessage(MSG_KEY),
186             "62:32: " + getCheckMessage(MSG_KEY),
187             "80:35: " + getCheckMessage(MSG_KEY),
188             "82:35: " + getCheckMessage(MSG_KEY),
189             "84:35: " + getCheckMessage(MSG_KEY),
190             "86:35: " + getCheckMessage(MSG_KEY),
191             "97:24: " + getCheckMessage(MSG_KEY),
192             "98:24: " + getCheckMessage(MSG_KEY),
193             "99:24: " + getCheckMessage(MSG_KEY),
194             "100:24: " + getCheckMessage(MSG_KEY),
195             "101:24: " + getCheckMessage(MSG_KEY),
196             "102:24: " + getCheckMessage(MSG_KEY),
197             "104:24: " + getCheckMessage(MSG_KEY),
198             "107:31: " + getCheckMessage(MSG_KEY),
199             "107:48: " + getCheckMessage(MSG_KEY),
200         };
201         verifyWithInlineConfigParser(
202                 getPath("InputAvoidEscapedUnicodeCharactersOne.java"), expected);
203     }
204 
205     @Test
206     public void testDefaultTwo() throws Exception {
207         final String[] expected = {
208             "15:38: " + getCheckMessage(MSG_KEY),
209             "17:38: " + getCheckMessage(MSG_KEY),
210             "19:38: " + getCheckMessage(MSG_KEY),
211             "21:38: " + getCheckMessage(MSG_KEY),
212             "25:31: " + getCheckMessage(MSG_KEY),
213             "25:45: " + getCheckMessage(MSG_KEY),
214             "29:34: " + getCheckMessage(MSG_KEY),
215             "31:46: " + getCheckMessage(MSG_KEY),
216             "36:38: " + getCheckMessage(MSG_KEY),
217             "43:38: " + getCheckMessage(MSG_KEY),
218             "46:46: " + getCheckMessage(MSG_KEY),
219             "48:55: " + getCheckMessage(MSG_KEY),
220             "50:46: " + getCheckMessage(MSG_KEY),
221             "52:55: " + getCheckMessage(MSG_KEY),
222             "54:46: " + getCheckMessage(MSG_KEY),
223             "56:55: " + getCheckMessage(MSG_KEY),
224             "58:46: " + getCheckMessage(MSG_KEY),
225             "60:55: " + getCheckMessage(MSG_KEY),
226             "62:46: " + getCheckMessage(MSG_KEY),
227             "64:55: " + getCheckMessage(MSG_KEY),
228             "66:48: " + getCheckMessage(MSG_KEY),
229             "68:57: " + getCheckMessage(MSG_KEY),
230         };
231         verifyWithInlineConfigParser(
232                 getPath("InputAvoidEscapedUnicodeCharactersTwo.java"), expected);
233     }
234 
235     @Test
236     public void testAllowEscapesForControlCharacterSetOne() throws Exception {
237         final String[] expected = {
238             "17:38: " + getCheckMessage(MSG_KEY),
239             "19:38: " + getCheckMessage(MSG_KEY),
240             "21:38: " + getCheckMessage(MSG_KEY),
241             "25:38: " + getCheckMessage(MSG_KEY),
242             "27:38: " + getCheckMessage(MSG_KEY),
243             "36:36: " + getCheckMessage(MSG_KEY),
244             "38:36: " + getCheckMessage(MSG_KEY),
245             "45:38: " + getCheckMessage(MSG_KEY),
246             "47:38: " + getCheckMessage(MSG_KEY),
247             "49:38: " + getCheckMessage(MSG_KEY),
248             "51:47: " + getCheckMessage(MSG_KEY),
249             "60:32: " + getCheckMessage(MSG_KEY),
250             "78:35: " + getCheckMessage(MSG_KEY),
251             "80:35: " + getCheckMessage(MSG_KEY),
252             "82:35: " + getCheckMessage(MSG_KEY),
253             "84:35: " + getCheckMessage(MSG_KEY),
254             "96:24: " + getCheckMessage(MSG_KEY),
255             "97:24: " + getCheckMessage(MSG_KEY),
256             "98:24: " + getCheckMessage(MSG_KEY),
257             "99:24: " + getCheckMessage(MSG_KEY),
258             "100:24: " + getCheckMessage(MSG_KEY),
259             "102:24: " + getCheckMessage(MSG_KEY),
260             "105:31: " + getCheckMessage(MSG_KEY),
261             "105:48: " + getCheckMessage(MSG_KEY),
262         };
263         verifyWithInlineConfigParser(
264                 getPath("InputAvoidEscapedUnicodeCharacters1One.java"), expected);
265     }
266 
267     @Test
268     public void testAllowEscapesForControlCharacterSetTwo() throws Exception {
269         final String[] expected = {
270             "16:38: " + getCheckMessage(MSG_KEY),
271             "17:38: " + getCheckMessage(MSG_KEY),
272             "19:38: " + getCheckMessage(MSG_KEY),
273             "21:38: " + getCheckMessage(MSG_KEY),
274             "25:45: " + getCheckMessage(MSG_KEY),
275             "28:46: " + getCheckMessage(MSG_KEY),
276             "33:38: " + getCheckMessage(MSG_KEY),
277             "40:38: " + getCheckMessage(MSG_KEY),
278             "42:46: " + getCheckMessage(MSG_KEY),
279             "44:55: " + getCheckMessage(MSG_KEY),
280             "46:46: " + getCheckMessage(MSG_KEY),
281             "48:55: " + getCheckMessage(MSG_KEY),
282             "50:46: " + getCheckMessage(MSG_KEY),
283             "52:55: " + getCheckMessage(MSG_KEY),
284             "54:46: " + getCheckMessage(MSG_KEY),
285             "56:55: " + getCheckMessage(MSG_KEY),
286             "58:46: " + getCheckMessage(MSG_KEY),
287             "60:55: " + getCheckMessage(MSG_KEY),
288             "62:48: " + getCheckMessage(MSG_KEY),
289             "64:57: " + getCheckMessage(MSG_KEY),
290         };
291         verifyWithInlineConfigParser(
292                 getPath("InputAvoidEscapedUnicodeCharacters1Two.java"), expected);
293     }
294 
295     @Test
296     public void testAllowByTailCommentOne() throws Exception {
297         final String[] expected = {
298             "17:38: " + getCheckMessage(MSG_KEY),
299             "26:38: " + getCheckMessage(MSG_KEY),
300             "36:36: " + getCheckMessage(MSG_KEY),
301             "44:38: " + getCheckMessage(MSG_KEY),
302             "47:38: " + getCheckMessage(MSG_KEY),
303             "49:47: " + getCheckMessage(MSG_KEY),
304             "75:35: " + getCheckMessage(MSG_KEY),
305             "77:35: " + getCheckMessage(MSG_KEY),
306             "79:35: " + getCheckMessage(MSG_KEY),
307             "81:35: " + getCheckMessage(MSG_KEY),
308             "93:24: " + getCheckMessage(MSG_KEY),
309             "95:24: " + getCheckMessage(MSG_KEY),
310             "97:24: " + getCheckMessage(MSG_KEY),
311             "99:24: " + getCheckMessage(MSG_KEY),
312             "101:24: " + getCheckMessage(MSG_KEY),
313             "104:24: " + getCheckMessage(MSG_KEY),
314             "108:31: " + getCheckMessage(MSG_KEY),
315             "108:48: " + getCheckMessage(MSG_KEY),
316         };
317         verifyWithInlineConfigParser(
318                 getPath("InputAvoidEscapedUnicodeCharacters2One.java"), expected);
319     }
320 
321     @Test
322     public void testAllowByTailCommentTwo() throws Exception {
323         final String[] expected = {
324             "21:31: " + getCheckMessage(MSG_KEY),
325             "21:45: " + getCheckMessage(MSG_KEY),
326             "30:38: " + getCheckMessage(MSG_KEY),
327             "36:38: " + getCheckMessage(MSG_KEY),
328             "38:46: " + getCheckMessage(MSG_KEY),
329             "42:46: " + getCheckMessage(MSG_KEY),
330             "45:46: " + getCheckMessage(MSG_KEY),
331             "48:46: " + getCheckMessage(MSG_KEY),
332             "51:46: " + getCheckMessage(MSG_KEY),
333             "54:48: " + getCheckMessage(MSG_KEY),
334         };
335         verifyWithInlineConfigParser(
336                 getPath("InputAvoidEscapedUnicodeCharacters2Two.java"), expected);
337     }
338 
339     @Test
340     public void testAllowAllCharactersEscapedOne() throws Exception {
341         final String[] expected = {
342             "17:38: " + getCheckMessage(MSG_KEY),
343             "19:38: " + getCheckMessage(MSG_KEY),
344             "21:38: " + getCheckMessage(MSG_KEY),
345             "26:38: " + getCheckMessage(MSG_KEY),
346             "28:38: " + getCheckMessage(MSG_KEY),
347             "44:38: " + getCheckMessage(MSG_KEY),
348             "46:38: " + getCheckMessage(MSG_KEY),
349             "48:38: " + getCheckMessage(MSG_KEY),
350             "58:32: " + getCheckMessage(MSG_KEY),
351         };
352         verifyWithInlineConfigParser(
353                 getPath("InputAvoidEscapedUnicodeCharacters3One.java"), expected);
354     }
355 
356     @Test
357     public void testAllowAllCharactersEscapedTwo() throws Exception {
358         final String[] expected = {
359             "15:38: " + getCheckMessage(MSG_KEY),
360             "17:38: " + getCheckMessage(MSG_KEY),
361             "19:38: " + getCheckMessage(MSG_KEY),
362             "21:38: " + getCheckMessage(MSG_KEY),
363             "31:38: " + getCheckMessage(MSG_KEY),
364             "39:46: " + getCheckMessage(MSG_KEY),
365             "41:55: " + getCheckMessage(MSG_KEY),
366             "43:46: " + getCheckMessage(MSG_KEY),
367             "45:55: " + getCheckMessage(MSG_KEY),
368             "47:46: " + getCheckMessage(MSG_KEY),
369             "49:55: " + getCheckMessage(MSG_KEY),
370             "51:46: " + getCheckMessage(MSG_KEY),
371             "53:55: " + getCheckMessage(MSG_KEY),
372             "55:46: " + getCheckMessage(MSG_KEY),
373             "57:55: " + getCheckMessage(MSG_KEY),
374             "59:48: " + getCheckMessage(MSG_KEY),
375             "61:57: " + getCheckMessage(MSG_KEY),
376         };
377         verifyWithInlineConfigParser(
378                 getPath("InputAvoidEscapedUnicodeCharacters3Two.java"), expected);
379     }
380 
381     @Test
382     public void allowNonPrintableEscapesOne() throws Exception {
383         final String[] expected = {
384             "17:38: " + getCheckMessage(MSG_KEY),
385             "19:38: " + getCheckMessage(MSG_KEY),
386             "21:38: " + getCheckMessage(MSG_KEY),
387             "26:38: " + getCheckMessage(MSG_KEY),
388             "28:38: " + getCheckMessage(MSG_KEY),
389             "37:36: " + getCheckMessage(MSG_KEY),
390             "39:36: " + getCheckMessage(MSG_KEY),
391             "46:38: " + getCheckMessage(MSG_KEY),
392             "48:38: " + getCheckMessage(MSG_KEY),
393             "50:38: " + getCheckMessage(MSG_KEY),
394             "52:47: " + getCheckMessage(MSG_KEY),
395             "61:32: " + getCheckMessage(MSG_KEY),
396         };
397         verifyWithInlineConfigParser(
398                 getPath("InputAvoidEscapedUnicodeCharacters4One.java"), expected);
399     }
400 
401     @Test
402     public void allowNonPrintableEscapesTwo() throws Exception {
403         final String[] expected = {
404             "15:38: " + getCheckMessage(MSG_KEY),
405             "17:38: " + getCheckMessage(MSG_KEY),
406             "19:38: " + getCheckMessage(MSG_KEY),
407             "21:38: " + getCheckMessage(MSG_KEY),
408             "26:34: " + getCheckMessage(MSG_KEY),
409             "28:46: " + getCheckMessage(MSG_KEY),
410             "33:38: " + getCheckMessage(MSG_KEY),
411             "40:38: " + getCheckMessage(MSG_KEY),
412             "43:46: " + getCheckMessage(MSG_KEY),
413             "45:55: " + getCheckMessage(MSG_KEY),
414             "47:46: " + getCheckMessage(MSG_KEY),
415             "49:55: " + getCheckMessage(MSG_KEY),
416             "51:46: " + getCheckMessage(MSG_KEY),
417             "53:55: " + getCheckMessage(MSG_KEY),
418             "55:46: " + getCheckMessage(MSG_KEY),
419             "57:55: " + getCheckMessage(MSG_KEY),
420             "59:46: " + getCheckMessage(MSG_KEY),
421             "61:55: " + getCheckMessage(MSG_KEY),
422             "63:48: " + getCheckMessage(MSG_KEY),
423             "65:57: " + getCheckMessage(MSG_KEY),
424         };
425         verifyWithInlineConfigParser(
426                 getPath("InputAvoidEscapedUnicodeCharacters4Two.java"), expected);
427     }
428 
429     @Test
430     public void testAllowByTailCommentWithEmoji() throws Exception {
431         final String[] expected = {
432             "15:24: " + getCheckMessage(MSG_KEY),
433             "18:24: " + getCheckMessage(MSG_KEY),
434             "23:30: " + getCheckMessage(MSG_KEY),
435             "33:18: " + getCheckMessage(MSG_KEY),
436             "36:18: " + getCheckMessage(MSG_KEY),
437             "38:18: " + getCheckMessage(MSG_KEY),
438             "41:18: " + getCheckMessage(MSG_KEY),
439         };
440         verifyWithInlineConfigParser(
441                 getPath("InputAvoidEscapedUnicodeCharacters5.java"), expected);
442     }
443 
444     @Test
445     public void testAvoidEscapedUnicodeCharactersTextBlocksAllowByComment() throws Exception {
446         final String[] expected = {
447             "18:30: " + getCheckMessage(MSG_KEY),
448             "20:30: " + getCheckMessage(MSG_KEY),
449             "22:30: " + getCheckMessage(MSG_KEY),
450             "25:39: " + getCheckMessage(MSG_KEY),
451             "30:33: " + getCheckMessage(MSG_KEY),
452             "33:33: " + getCheckMessage(MSG_KEY),
453             "36:33: " + getCheckMessage(MSG_KEY),
454             "41:42: " + getCheckMessage(MSG_KEY),
455         };
456         verifyWithInlineConfigParser(
457                 getPath(
458                 "InputAvoidEscapedUnicodeCharactersTextBlocksAllowByComment.java"),
459             expected);
460     }
461 
462     @Test
463     public void testAvoidEscapedUnicodeCharactersTextBlocks() throws Exception {
464         final String[] expected = {
465             "17:30: " + getCheckMessage(MSG_KEY),
466             "19:30: " + getCheckMessage(MSG_KEY),
467             "21:30: " + getCheckMessage(MSG_KEY),
468             "23:39: " + getCheckMessage(MSG_KEY),
469             "29:33: " + getCheckMessage(MSG_KEY),
470             "32:33: " + getCheckMessage(MSG_KEY),
471             "35:33: " + getCheckMessage(MSG_KEY),
472             "38:42: " + getCheckMessage(MSG_KEY),
473         };
474         verifyWithInlineConfigParser(
475                 getPath("InputAvoidEscapedUnicodeCharactersTextBlocks.java"),
476             expected);
477     }
478 
479     @Test
480     public void testAvoidEscapedUnicodeCharactersEscapedS() throws Exception {
481         final String[] expected = {
482             "17:21: " + getCheckMessage(MSG_KEY),
483             "19:22: " + getCheckMessage(MSG_KEY),
484             "31:33: " + getCheckMessage(MSG_KEY),
485             "35:33: " + getCheckMessage(MSG_KEY),
486             "39:33: " + getCheckMessage(MSG_KEY),
487             "43:22: " + getCheckMessage(MSG_KEY),
488         };
489         verifyWithInlineConfigParser(
490                 getPath("InputAvoidEscapedUnicodeCharactersEscapedS.java"),
491                 expected);
492     }
493 
494     @Test
495     public void testBlockCommentAtAbsoluteEndOfFile() throws Exception {
496         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
497         verifyWithInlineConfigParser(
498             getPath("InputAvoidEscapedUnicodeCharactersCommentEnd.java"),
499             expected);
500     }
501 
502     @Test
503     public void testBeginTreeClearsPendingViolations() throws Exception {
504         final String file1 = getPath("InputAvoidEscapedUnicodeCharactersBeginTree1.java");
505         final String file2 = getPath("InputAvoidEscapedUnicodeCharactersBeginTree2.java");
506 
507         final List<String> expected1 = List.of(
508                 "15:17: " + getCheckMessage(MSG_KEY));
509         final List<String> expected2 = Arrays.asList(CommonUtil.EMPTY_STRING_ARRAY);
510 
511         verifyWithInlineConfigParser(file1, file2, expected1, expected2);
512     }
513 
514     @Test
515     public void testGetAcceptableTokens() {
516         final AvoidEscapedUnicodeCharactersCheck check = new AvoidEscapedUnicodeCharactersCheck();
517         final int[] actual = check.getAcceptableTokens();
518         final int[] expected = {
519             TokenTypes.STRING_LITERAL,
520             TokenTypes.CHAR_LITERAL,
521             TokenTypes.TEXT_BLOCK_CONTENT,
522             TokenTypes.SINGLE_LINE_COMMENT,
523             TokenTypes.BLOCK_COMMENT_BEGIN,
524         };
525         assertWithMessage("Acceptable tokens differ from expected")
526             .that(actual)
527             .isEqualTo(expected);
528     }
529 
530     @Test
531     public void testAllowEscapesForControlCharacterSetForAllCharacters() throws Exception {
532 
533         final int indexOfStartLineInInputFile = 16;
534         final String message = getCheckMessage(MSG_KEY);
535         final String[] expected = IntStream.rangeClosed(0, 0xFFFF)
536                 .parallel()
537                 .filter(val -> !isControlCharacter(val))
538                 .mapToObj(msg -> indexOfStartLineInInputFile + msg + ":54: " + message)
539                 .toArray(String[]::new);
540         verifyWithInlineConfigParser(
541                 getPath("InputAvoidEscapedUnicodeCharactersAllEscapedUnicodeCharacters.java"),
542                 expected);
543     }
544 
545     /**
546      * Method countMatches is used only inside isOnlyUnicodeValidChars method, and when
547      * pitest mutates 316:13 countMatches++ to countMatches-- it makes no difference for
548      * isOnlyUnicodeValidChars method as it applies countMatches to both cases in comparison.
549      * It is possible to kill mutation in countMatches method by changing code in
550      * isOnlyUnicodeValidChars, but it creates new uncoverable mutations and makes code harder
551      * to understand.
552      *
553      * @throws Exception when code tested throws some exception
554      */
555     @Test
556     public void testCountMatches() throws Exception {
557         final AvoidEscapedUnicodeCharactersCheck check = new AvoidEscapedUnicodeCharactersCheck();
558         final int actual = TestUtil.invokeMethod(check, "countMatches", Integer.class,
559                 Pattern.compile("\\\\u[a-fA-F\\d]{4}"), "\\u1234");
560         assertWithMessage("Unexpected matches count")
561             .that(actual)
562             .isEqualTo(1);
563     }
564 
565     /**
566      * Testing, that all elements in the constant NON_PRINTABLE_CHARS are sorted.
567      * This is very convenient for the sake of maintainability.
568      */
569     @Test
570     public void testNonPrintableCharsAreSorted() {
571         String expression = TestUtil.getInternalStaticState(
572                 AvoidEscapedUnicodeCharactersCheck.class,
573                 "NON_PRINTABLE_CHARS", Pattern.class).pattern();
574 
575         // Replacing expressions like "\\u000[bB]" with "\\u000B"
576         final String[] charExpressions = {"Aa", "Bb", "Cc", "Dd", "Ee", "Ff"};
577         for (String charExpression : charExpressions) {
578             final String regex = "\\[[" + charExpression + "]{2}]";
579             expression = expression.replaceAll(regex, charExpression.substring(0, 1));
580         }
581 
582         // Replacing duplications like "\\uF{3}9" with "\\uFFF9"
583         for (int i = 4; i > 1; i--) {
584             final String regex = "([A-F])\\{" + i + "}";
585             String replacement = "$1$1{" + (i - 1) + "}";
586             if (i == 2) {
587                 replacement = "$1$1";
588             }
589             expression = expression.replaceAll(regex, replacement);
590         }
591 
592         // Verifying character order
593         final List<String> expressionParts = Splitter.on("|").splitToList(expression);
594         final Pattern unicodeCharPattern = Pattern.compile("^\\\\\\\\u[\\dA-F]{4}$");
595         String lastChar = null;
596         for (int i = 0; i < expressionParts.size(); i++) {
597             final String currentChar = expressionParts.get(i);
598             final Matcher matcher = unicodeCharPattern.matcher(currentChar);
599             if (!matcher.matches()) {
600                 final String message = "Character '" + currentChar + "' (at position " + i
601                         + ") doesn't match the pattern";
602                 assertWithMessage(message)
603                         .that(matcher.matches())
604                         .isTrue();
605             }
606             if (lastChar != null) {
607                 final String message = "Character '" + lastChar + "' should be after '"
608                         + currentChar + "', position: " + i;
609                 assertWithMessage(message)
610                         .that(lastChar.compareTo(currentChar) < 0)
611                         .isTrue();
612             }
613             lastChar = currentChar;
614         }
615     }
616 
617     private static boolean isControlCharacter(final int character) {
618         return Arrays.binarySearch(C0_CONTROL_CHARACTER_INDICES, character) >= 0
619                 || Arrays.binarySearch(C1_CONTROL_CHARACTER_INDICES, character) >= 0
620                 || Arrays.binarySearch(OTHER_CONTROL_CHARACTER_INDICES, character) >= 0;
621     }
622 
623 }