View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.lang3;
20  
21  import static org.apache.commons.lang3.LangAssertions.assertNullPointerException;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotEquals;
25  import static org.junit.jupiter.api.Assertions.assertNotNull;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  
29  import java.lang.reflect.Modifier;
30  import java.util.Iterator;
31  import java.util.NoSuchElementException;
32  
33  import org.junit.jupiter.api.Test;
34  
35  /**
36   * Tests {@link CharRange}.
37   */
38  class CharRangeTest extends AbstractLangTest {
39  
40      @Test
41      void testClass() {
42          // class changed to non-public in 3.0
43          assertFalse(Modifier.isPublic(CharRange.class.getModifiers()));
44          assertTrue(Modifier.isFinal(CharRange.class.getModifiers()));
45      }
46  
47      @Test
48      void testConstructorAccessors_is() {
49          final CharRange rangea = CharRange.is('a');
50          assertEquals('a', rangea.getStart());
51          assertEquals('a', rangea.getEnd());
52          assertFalse(rangea.isNegated());
53          assertEquals("a", rangea.toString());
54      }
55  
56      @Test
57      void testConstructorAccessors_isIn_Normal() {
58          final CharRange rangea = CharRange.isIn('a', 'e');
59          assertEquals('a', rangea.getStart());
60          assertEquals('e', rangea.getEnd());
61          assertFalse(rangea.isNegated());
62          assertEquals("a-e", rangea.toString());
63      }
64  
65      @Test
66      void testConstructorAccessors_isIn_Reversed() {
67          final CharRange rangea = CharRange.isIn('e', 'a');
68          assertEquals('a', rangea.getStart());
69          assertEquals('e', rangea.getEnd());
70          assertFalse(rangea.isNegated());
71          assertEquals("a-e", rangea.toString());
72      }
73  
74      @Test
75      void testConstructorAccessors_isIn_Same() {
76          final CharRange rangea = CharRange.isIn('a', 'a');
77          assertEquals('a', rangea.getStart());
78          assertEquals('a', rangea.getEnd());
79          assertFalse(rangea.isNegated());
80          assertEquals("a", rangea.toString());
81      }
82  
83      @Test
84      void testConstructorAccessors_isNot() {
85          final CharRange rangea = CharRange.isNot('a');
86          assertEquals('a', rangea.getStart());
87          assertEquals('a', rangea.getEnd());
88          assertTrue(rangea.isNegated());
89          assertEquals("^a", rangea.toString());
90      }
91  
92      @Test
93      void testConstructorAccessors_isNotIn_Normal() {
94          final CharRange rangea = CharRange.isNotIn('a', 'e');
95          assertEquals('a', rangea.getStart());
96          assertEquals('e', rangea.getEnd());
97          assertTrue(rangea.isNegated());
98          assertEquals("^a-e", rangea.toString());
99      }
100 
101     @Test
102     void testConstructorAccessors_isNotIn_Reversed() {
103         final CharRange rangea = CharRange.isNotIn('e', 'a');
104         assertEquals('a', rangea.getStart());
105         assertEquals('e', rangea.getEnd());
106         assertTrue(rangea.isNegated());
107         assertEquals("^a-e", rangea.toString());
108     }
109 
110     @Test
111     void testConstructorAccessors_isNotIn_Same() {
112         final CharRange rangea = CharRange.isNotIn('a', 'a');
113         assertEquals('a', rangea.getStart());
114         assertEquals('a', rangea.getEnd());
115         assertTrue(rangea.isNegated());
116         assertEquals("^a", rangea.toString());
117     }
118 
119     @Test
120     void testContains_Char() {
121         CharRange range = CharRange.is('c');
122         assertFalse(range.contains('b'));
123         assertTrue(range.contains('c'));
124         assertFalse(range.contains('d'));
125         assertFalse(range.contains('e'));
126 
127         range = CharRange.isIn('c', 'd');
128         assertFalse(range.contains('b'));
129         assertTrue(range.contains('c'));
130         assertTrue(range.contains('d'));
131         assertFalse(range.contains('e'));
132 
133         range = CharRange.isIn('d', 'c');
134         assertFalse(range.contains('b'));
135         assertTrue(range.contains('c'));
136         assertTrue(range.contains('d'));
137         assertFalse(range.contains('e'));
138 
139         range = CharRange.isNotIn('c', 'd');
140         assertTrue(range.contains('b'));
141         assertFalse(range.contains('c'));
142         assertFalse(range.contains('d'));
143         assertTrue(range.contains('e'));
144         assertTrue(range.contains((char) 0));
145         assertTrue(range.contains(Character.MAX_VALUE));
146     }
147 
148     @Test
149     void testContains_Charrange() {
150         final CharRange a = CharRange.is('a');
151         final CharRange b = CharRange.is('b');
152         final CharRange c = CharRange.is('c');
153         final CharRange c2 = CharRange.is('c');
154         final CharRange d = CharRange.is('d');
155         final CharRange e = CharRange.is('e');
156         final CharRange cd = CharRange.isIn('c', 'd');
157         final CharRange bd = CharRange.isIn('b', 'd');
158         final CharRange bc = CharRange.isIn('b', 'c');
159         final CharRange ab = CharRange.isIn('a', 'b');
160         final CharRange de = CharRange.isIn('d', 'e');
161         final CharRange ef = CharRange.isIn('e', 'f');
162         final CharRange ae = CharRange.isIn('a', 'e');
163 
164         // normal/normal
165         assertFalse(c.contains(b));
166         assertTrue(c.contains(c));
167         assertTrue(c.contains(c2));
168         assertFalse(c.contains(d));
169 
170         assertFalse(c.contains(cd));
171         assertFalse(c.contains(bd));
172         assertFalse(c.contains(bc));
173         assertFalse(c.contains(ab));
174         assertFalse(c.contains(de));
175 
176         assertTrue(cd.contains(c));
177         assertTrue(bd.contains(c));
178         assertTrue(bc.contains(c));
179         assertFalse(ab.contains(c));
180         assertFalse(de.contains(c));
181 
182         assertTrue(ae.contains(b));
183         assertTrue(ae.contains(ab));
184         assertTrue(ae.contains(bc));
185         assertTrue(ae.contains(cd));
186         assertTrue(ae.contains(de));
187 
188         final CharRange notb = CharRange.isNot('b');
189         final CharRange notc = CharRange.isNot('c');
190         final CharRange notd = CharRange.isNot('d');
191         final CharRange notab = CharRange.isNotIn('a', 'b');
192         final CharRange notbc = CharRange.isNotIn('b', 'c');
193         final CharRange notbd = CharRange.isNotIn('b', 'd');
194         final CharRange notcd = CharRange.isNotIn('c', 'd');
195         final CharRange notde = CharRange.isNotIn('d', 'e');
196         final CharRange notae = CharRange.isNotIn('a', 'e');
197         final CharRange all = CharRange.isIn((char) 0, Character.MAX_VALUE);
198         final CharRange allbutfirst = CharRange.isIn((char) 1, Character.MAX_VALUE);
199 
200         // normal/negated
201         assertFalse(c.contains(notc));
202         assertFalse(c.contains(notbd));
203         assertTrue(all.contains(notc));
204         assertTrue(all.contains(notbd));
205         assertFalse(allbutfirst.contains(notc));
206         assertFalse(allbutfirst.contains(notbd));
207 
208         // negated/normal
209         assertTrue(notc.contains(a));
210         assertTrue(notc.contains(b));
211         assertFalse(notc.contains(c));
212         assertTrue(notc.contains(d));
213         assertTrue(notc.contains(e));
214 
215         assertTrue(notc.contains(ab));
216         assertFalse(notc.contains(bc));
217         assertFalse(notc.contains(bd));
218         assertFalse(notc.contains(cd));
219         assertTrue(notc.contains(de));
220         assertFalse(notc.contains(ae));
221         assertFalse(notc.contains(all));
222         assertFalse(notc.contains(allbutfirst));
223 
224         assertTrue(notbd.contains(a));
225         assertFalse(notbd.contains(b));
226         assertFalse(notbd.contains(c));
227         assertFalse(notbd.contains(d));
228         assertTrue(notbd.contains(e));
229 
230         assertTrue(notcd.contains(ab));
231         assertFalse(notcd.contains(bc));
232         assertFalse(notcd.contains(bd));
233         assertFalse(notcd.contains(cd));
234         assertFalse(notcd.contains(de));
235         assertFalse(notcd.contains(ae));
236         assertTrue(notcd.contains(ef));
237         assertFalse(notcd.contains(all));
238         assertFalse(notcd.contains(allbutfirst));
239 
240         // negated/negated
241         assertFalse(notc.contains(notb));
242         assertTrue(notc.contains(notc));
243         assertFalse(notc.contains(notd));
244 
245         assertFalse(notc.contains(notab));
246         assertTrue(notc.contains(notbc));
247         assertTrue(notc.contains(notbd));
248         assertTrue(notc.contains(notcd));
249         assertFalse(notc.contains(notde));
250 
251         assertFalse(notbd.contains(notb));
252         assertFalse(notbd.contains(notc));
253         assertFalse(notbd.contains(notd));
254 
255         assertFalse(notbd.contains(notab));
256         assertFalse(notbd.contains(notbc));
257         assertTrue(notbd.contains(notbd));
258         assertFalse(notbd.contains(notcd));
259         assertFalse(notbd.contains(notde));
260         assertTrue(notbd.contains(notae));
261     }
262 
263     @Test
264     void testContains_Charrange_negatedArgumentTouchingBounds() {
265         // A negated argument denotes [0, start - 1] union [end + 1, MAX_VALUE]. When its excluded range touches 0 or
266         // MAX_VALUE that set collapses to the empty set or a single contiguous interval, so a non-full range can contain it.
267         final CharRange emptySet = CharRange.isNotIn((char) 0, Character.MAX_VALUE);
268         assertTrue(CharRange.is('x').contains(emptySet));
269         assertTrue(CharRange.isIn((char) 0, Character.MAX_VALUE).contains(emptySet));
270 
271         // isNotIn(0, 'a' - 1) denotes ['a', MAX_VALUE]
272         final CharRange fromA = CharRange.isNotIn((char) 0, (char) ('a' - 1));
273         assertTrue(CharRange.isIn('a', Character.MAX_VALUE).contains(fromA));
274         assertTrue(CharRange.isIn('Z', Character.MAX_VALUE).contains(fromA));
275         assertFalse(CharRange.isIn('b', Character.MAX_VALUE).contains(fromA));
276         assertFalse(CharRange.isIn('a', 'z').contains(fromA));
277 
278         // isNotIn('b', MAX_VALUE) denotes [0, 'a']
279         final CharRange upToA = CharRange.isNotIn('b', Character.MAX_VALUE);
280         assertTrue(CharRange.isIn((char) 0, 'a').contains(upToA));
281         assertTrue(CharRange.isIn((char) 0, 'b').contains(upToA));
282         assertFalse(CharRange.isIn((char) 1, 'a').contains(upToA));
283         assertFalse(CharRange.isIn((char) 0, 'Z').contains(upToA));
284 
285         // A fully interior excluded range is still only contained by the full range.
286         assertFalse(CharRange.is('c').contains(CharRange.isNot('c')));
287         assertTrue(CharRange.isIn((char) 0, Character.MAX_VALUE).contains(CharRange.isNot('c')));
288     }
289 
290     @Test
291     void testContainsNullArg() {
292         final CharRange range = CharRange.is('a');
293         final NullPointerException e = assertNullPointerException(() -> range.contains(null));
294         assertEquals("range", e.getMessage());
295     }
296 
297     @Test
298     void testEmptySet() {
299         // isNotIn(MIN_VALUE, MAX_VALUE) denotes the empty set: it excludes the entire character space.
300         final CharRange emptySet = CharRange.isNotIn(Character.MIN_VALUE, Character.MAX_VALUE);
301         assertTrue(emptySet.isNegated());
302         assertEquals(Character.MIN_VALUE, emptySet.getStart());
303         assertEquals(Character.MAX_VALUE, emptySet.getEnd());
304         // The constructor reverses reversed endpoints, so the reversed construction is the same empty set.
305         assertEquals(emptySet, CharRange.isNotIn(Character.MAX_VALUE, Character.MIN_VALUE));
306         assertEquals(emptySet.hashCode(), CharRange.isNotIn(Character.MAX_VALUE, Character.MIN_VALUE).hashCode());
307 
308         // It contains no characters, including both extremes of the character space.
309         assertFalse(emptySet.contains(Character.MIN_VALUE));
310         assertFalse(emptySet.contains('a'));
311         assertFalse(emptySet.contains('x'));
312         assertFalse(emptySet.contains('z'));
313         assertFalse(emptySet.contains(Character.MAX_VALUE));
314 
315         // It is contained in every range, plain or negated, single character or not.
316         assertTrue(CharRange.is('a').contains(emptySet));
317         assertTrue(CharRange.is('x').contains(emptySet));
318         assertTrue(CharRange.isIn('a', 'z').contains(emptySet));
319         assertTrue(CharRange.isIn(Character.MIN_VALUE, Character.MAX_VALUE).contains(emptySet));
320         assertTrue(CharRange.isNot('a').contains(emptySet));
321         assertTrue(CharRange.isNotIn('a', 'z').contains(emptySet));
322         assertTrue(CharRange.isNotIn(Character.MIN_VALUE, 'z').contains(emptySet));
323         assertTrue(CharRange.isNotIn('a', Character.MAX_VALUE).contains(emptySet));
324 
325         // Iterating over it yields no characters.
326         final Iterator<Character> emptySetIt = emptySet.iterator();
327         assertNotNull(emptySetIt);
328         assertFalse(emptySetIt.hasNext());
329         assertThrows(NoSuchElementException.class, emptySetIt::next);
330 
331         // Ranges that exclude a boundary but not the entire character space are NOT empty ...
332         final CharRange fromB = CharRange.isNotIn(Character.MIN_VALUE, 'a'); // denotes ['b', MAX_VALUE]
333         final CharRange upToA = CharRange.isNotIn('a', Character.MAX_VALUE); // denotes [MIN_VALUE, 'a']
334         assertTrue(fromB.contains(Character.MAX_VALUE));
335         assertTrue(upToA.contains(Character.MIN_VALUE));
336         // ... so, unlike the empty set, they are not contained in every range.
337         assertFalse(CharRange.is('a').contains(fromB));
338         assertFalse(CharRange.is('b').contains(upToA));
339         assertFalse(upToA.contains(fromB));
340     }
341 
342     @Test
343     void testEquals_Object() {
344         final CharRange rangea = CharRange.is('a');
345         final CharRange rangeae = CharRange.isIn('a', 'e');
346         final CharRange rangenotbf = CharRange.isIn('b', 'f');
347 
348         assertNotEquals(null, rangea);
349 
350         assertEquals(rangea, rangea);
351         assertEquals(rangea, CharRange.is('a'));
352         assertEquals(rangeae, rangeae);
353         assertEquals(rangeae, CharRange.isIn('a', 'e'));
354         assertEquals(rangenotbf, rangenotbf);
355         assertEquals(rangenotbf, CharRange.isIn('b', 'f'));
356 
357         assertNotEquals(rangea, rangeae);
358         assertNotEquals(rangea, rangenotbf);
359         assertNotEquals(rangeae, rangea);
360         assertNotEquals(rangeae, rangenotbf);
361         assertNotEquals(rangenotbf, rangea);
362         assertNotEquals(rangenotbf, rangeae);
363     }
364 
365     @Test
366     void testHashCode() {
367         final CharRange rangea = CharRange.is('a');
368         final CharRange rangeae = CharRange.isIn('a', 'e');
369         final CharRange rangenotbf = CharRange.isIn('b', 'f');
370 
371         assertEquals(rangea.hashCode(), rangea.hashCode());
372         assertEquals(rangea.hashCode(), CharRange.is('a').hashCode());
373         assertEquals(rangeae.hashCode(), rangeae.hashCode());
374         assertEquals(rangeae.hashCode(), CharRange.isIn('a', 'e').hashCode());
375         assertEquals(rangenotbf.hashCode(), rangenotbf.hashCode());
376         assertEquals(rangenotbf.hashCode(), CharRange.isIn('b', 'f').hashCode());
377 
378         assertNotEquals(rangea.hashCode(), rangeae.hashCode());
379         assertNotEquals(rangea.hashCode(), rangenotbf.hashCode());
380         assertNotEquals(rangeae.hashCode(), rangea.hashCode());
381         assertNotEquals(rangeae.hashCode(), rangenotbf.hashCode());
382         assertNotEquals(rangenotbf.hashCode(), rangea.hashCode());
383         assertNotEquals(rangenotbf.hashCode(), rangeae.hashCode());
384     }
385 
386     /**
387      * Tests https://issues.apache.org/jira/browse/LANG-1802
388      */
389     @Test
390     void testHashCodeLang1802() {
391         // Test various combinations of different ranges
392         final CharRange range1 = CharRange.is('a');
393         final CharRange range2 = CharRange.is('b');
394         final CharRange range3 = CharRange.isIn('a', 'z');
395         final CharRange range4 = CharRange.isIn('b', 'z');
396         final CharRange range5 = CharRange.isNot('a');
397         final CharRange range6 = CharRange.isNotIn('a', 'z');
398         final CharRange range7 = CharRange.isNotIn('b', 'z');
399         final CharRange range8 = CharRange.isIn((char) 1, (char) 2);
400         final CharRange range9 = CharRange.isNotIn((char) 1, (char) 2);
401         // Previously problematic cases from LANG-1802 should now have different hash codes
402         final CharRange a1 = CharRange.isNotIn((char) 1, (char) 2);
403         final CharRange a2 = CharRange.isIn((char) 2, (char) 2);
404         assertNotEquals(a1, a2, "Different ranges should not be equal");
405         assertNotEquals(a1.hashCode(), a2.hashCode(), "Different ranges should have different hash codes");
406         final CharRange b1 = CharRange.isIn((char) 5, (char) 5);
407         final CharRange b2 = CharRange.isNotIn((char) 4, (char) 5);
408         assertNotEquals(b1, b2, "Different ranges should not be equal");
409         assertNotEquals(b1.hashCode(), b2.hashCode(), "Different ranges should have different hash codes");
410         // Test that negated and non-negated ranges with same bounds have different hash codes
411         final CharRange normal = CharRange.isIn('x', 'y');
412         final CharRange negated = CharRange.isNotIn('x', 'y');
413         assertNotEquals(normal, negated, "Negated and normal ranges should not be equal");
414         assertNotEquals(normal.hashCode(), negated.hashCode(), "Negated and normal ranges should have different hash codes");
415         // Test that ranges with different start/end produce different hash codes
416         assertNotEquals(range1.hashCode(), range2.hashCode(), "is('a') vs is('b')");
417         assertNotEquals(range1.hashCode(), range3.hashCode(), "is('a') vs isIn('a', 'z')");
418         assertNotEquals(range3.hashCode(), range4.hashCode(), "isIn('a', 'z') vs isIn('b', 'z')");
419         assertNotEquals(range1.hashCode(), range5.hashCode(), "is('a') vs isNot('a')");
420         assertNotEquals(range3.hashCode(), range6.hashCode(), "isIn('a', 'z') vs isNotIn('a', 'z')");
421         assertNotEquals(range6.hashCode(), range7.hashCode(), "isNotIn('a', 'z') vs isNotIn('b', 'z')");
422         assertNotEquals(range8.hashCode(), range9.hashCode(), "isIn(1, 2) vs isNotIn(1, 2)");
423         // Test that equal ranges have equal hash codes
424         final CharRange sameAsRange1 = CharRange.is('a');
425         assertEquals(range1, sameAsRange1, "Equal ranges should be equal");
426         assertEquals(range1.hashCode(), sameAsRange1.hashCode(), "Equal ranges should have equal hash codes");
427     }
428 
429     @Test
430     void testIsEmpty() {
431         // Only a negated range over the entire character space is empty.
432         assertTrue(CharRange.isNotIn(Character.MIN_VALUE, Character.MAX_VALUE).isEmpty());
433         // The constructor reverses reversed endpoints, so this is the same empty set.
434         assertTrue(CharRange.isNotIn(Character.MAX_VALUE, Character.MIN_VALUE).isEmpty());
435 
436         // Negated ranges that exclude a boundary or an interior but not the entire character space are not empty.
437         assertFalse(CharRange.isNotIn(Character.MIN_VALUE, 'a').isEmpty());
438         assertFalse(CharRange.isNotIn('a', Character.MAX_VALUE).isEmpty());
439         assertFalse(CharRange.isNot('a').isEmpty());
440         assertFalse(CharRange.isNotIn('a', 'z').isEmpty());
441 
442         // Plain ranges always contain at least one character, even the full character space.
443         assertFalse(CharRange.is('a').isEmpty());
444         assertFalse(CharRange.isIn('a', 'z').isEmpty());
445         assertFalse(CharRange.isIn(Character.MIN_VALUE, Character.MAX_VALUE).isEmpty());
446     }
447 
448     @Test
449     void testIterator() {
450         final CharRange a = CharRange.is('a');
451         final CharRange ad = CharRange.isIn('a', 'd');
452         final CharRange nota = CharRange.isNot('a');
453         final CharRange emptySet = CharRange.isNotIn((char) 0, Character.MAX_VALUE);
454         final CharRange notFirst = CharRange.isNotIn((char) 1, Character.MAX_VALUE);
455         final CharRange notLast = CharRange.isNotIn((char) 0, (char) (Character.MAX_VALUE - 1));
456 
457         final Iterator<Character> aIt = a.iterator();
458         assertNotNull(aIt);
459         assertTrue(aIt.hasNext());
460         assertEquals(Character.valueOf('a'), aIt.next());
461         assertFalse(aIt.hasNext());
462 
463         final Iterator<Character> adIt = ad.iterator();
464         assertNotNull(adIt);
465         assertTrue(adIt.hasNext());
466         assertEquals(Character.valueOf('a'), adIt.next());
467         assertEquals(Character.valueOf('b'), adIt.next());
468         assertEquals(Character.valueOf('c'), adIt.next());
469         assertEquals(Character.valueOf('d'), adIt.next());
470         assertFalse(adIt.hasNext());
471 
472         final Iterator<Character> notaIt = nota.iterator();
473         assertNotNull(notaIt);
474         assertTrue(notaIt.hasNext());
475         while (notaIt.hasNext()) {
476             final Character c = notaIt.next();
477             assertNotEquals('a', c.charValue());
478         }
479 
480         final Iterator<Character> emptySetIt = emptySet.iterator();
481         assertNotNull(emptySetIt);
482         assertFalse(emptySetIt.hasNext());
483         assertThrows(NoSuchElementException.class, emptySetIt::next);
484 
485         final Iterator<Character> notFirstIt = notFirst.iterator();
486         assertNotNull(notFirstIt);
487         assertTrue(notFirstIt.hasNext());
488         assertEquals(Character.valueOf((char) 0), notFirstIt.next());
489         assertFalse(notFirstIt.hasNext());
490         assertThrows(NoSuchElementException.class, notFirstIt::next);
491 
492         final Iterator<Character> notLastIt = notLast.iterator();
493         assertNotNull(notLastIt);
494         assertTrue(notLastIt.hasNext());
495         assertEquals(Character.valueOf(Character.MAX_VALUE), notLastIt.next());
496         assertFalse(notLastIt.hasNext());
497         assertThrows(NoSuchElementException.class, notLastIt::next);
498     }
499 
500     @Test
501     void testIteratorRemove() {
502         final CharRange a = CharRange.is('a');
503         final Iterator<Character> aIt = a.iterator();
504         assertThrows(UnsupportedOperationException.class, aIt::remove);
505     }
506 
507     @Test
508     void testSerialization() {
509         CharRange range = CharRange.is('a');
510         assertEquals(range, SerializationUtils.clone(range));
511         range = CharRange.isIn('a', 'e');
512         assertEquals(range, SerializationUtils.clone(range));
513         range = CharRange.isNotIn('a', 'e');
514         assertEquals(range, SerializationUtils.clone(range));
515     }
516 }