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 testContainsNullArg() {
265         final CharRange range = CharRange.is('a');
266         final NullPointerException e = assertNullPointerException(() -> range.contains(null));
267         assertEquals("range", e.getMessage());
268     }
269 
270     @Test
271     void testEquals_Object() {
272         final CharRange rangea = CharRange.is('a');
273         final CharRange rangeae = CharRange.isIn('a', 'e');
274         final CharRange rangenotbf = CharRange.isIn('b', 'f');
275 
276         assertNotEquals(null, rangea);
277 
278         assertEquals(rangea, rangea);
279         assertEquals(rangea, CharRange.is('a'));
280         assertEquals(rangeae, rangeae);
281         assertEquals(rangeae, CharRange.isIn('a', 'e'));
282         assertEquals(rangenotbf, rangenotbf);
283         assertEquals(rangenotbf, CharRange.isIn('b', 'f'));
284 
285         assertNotEquals(rangea, rangeae);
286         assertNotEquals(rangea, rangenotbf);
287         assertNotEquals(rangeae, rangea);
288         assertNotEquals(rangeae, rangenotbf);
289         assertNotEquals(rangenotbf, rangea);
290         assertNotEquals(rangenotbf, rangeae);
291     }
292 
293     @Test
294     void testHashCode() {
295         final CharRange rangea = CharRange.is('a');
296         final CharRange rangeae = CharRange.isIn('a', 'e');
297         final CharRange rangenotbf = CharRange.isIn('b', 'f');
298 
299         assertEquals(rangea.hashCode(), rangea.hashCode());
300         assertEquals(rangea.hashCode(), CharRange.is('a').hashCode());
301         assertEquals(rangeae.hashCode(), rangeae.hashCode());
302         assertEquals(rangeae.hashCode(), CharRange.isIn('a', 'e').hashCode());
303         assertEquals(rangenotbf.hashCode(), rangenotbf.hashCode());
304         assertEquals(rangenotbf.hashCode(), CharRange.isIn('b', 'f').hashCode());
305 
306         assertNotEquals(rangea.hashCode(), rangeae.hashCode());
307         assertNotEquals(rangea.hashCode(), rangenotbf.hashCode());
308         assertNotEquals(rangeae.hashCode(), rangea.hashCode());
309         assertNotEquals(rangeae.hashCode(), rangenotbf.hashCode());
310         assertNotEquals(rangenotbf.hashCode(), rangea.hashCode());
311         assertNotEquals(rangenotbf.hashCode(), rangeae.hashCode());
312     }
313 
314     /**
315      * Tests https://issues.apache.org/jira/browse/LANG-1802
316      */
317     @Test
318     void testHashCodeLang1802() {
319         // Test various combinations of different ranges
320         final CharRange range1 = CharRange.is('a');
321         final CharRange range2 = CharRange.is('b');
322         final CharRange range3 = CharRange.isIn('a', 'z');
323         final CharRange range4 = CharRange.isIn('b', 'z');
324         final CharRange range5 = CharRange.isNot('a');
325         final CharRange range6 = CharRange.isNotIn('a', 'z');
326         final CharRange range7 = CharRange.isNotIn('b', 'z');
327         final CharRange range8 = CharRange.isIn((char) 1, (char) 2);
328         final CharRange range9 = CharRange.isNotIn((char) 1, (char) 2);
329         // Previously problematic cases from LANG-1802 should now have different hash codes
330         final CharRange a1 = CharRange.isNotIn((char) 1, (char) 2);
331         final CharRange a2 = CharRange.isIn((char) 2, (char) 2);
332         assertNotEquals(a1, a2, "Different ranges should not be equal");
333         assertNotEquals(a1.hashCode(), a2.hashCode(), "Different ranges should have different hash codes");
334         final CharRange b1 = CharRange.isIn((char) 5, (char) 5);
335         final CharRange b2 = CharRange.isNotIn((char) 4, (char) 5);
336         assertNotEquals(b1, b2, "Different ranges should not be equal");
337         assertNotEquals(b1.hashCode(), b2.hashCode(), "Different ranges should have different hash codes");
338         // Test that negated and non-negated ranges with same bounds have different hash codes
339         final CharRange normal = CharRange.isIn('x', 'y');
340         final CharRange negated = CharRange.isNotIn('x', 'y');
341         assertNotEquals(normal, negated, "Negated and normal ranges should not be equal");
342         assertNotEquals(normal.hashCode(), negated.hashCode(), "Negated and normal ranges should have different hash codes");
343         // Test that ranges with different start/end produce different hash codes
344         assertNotEquals(range1.hashCode(), range2.hashCode(), "is('a') vs is('b')");
345         assertNotEquals(range1.hashCode(), range3.hashCode(), "is('a') vs isIn('a', 'z')");
346         assertNotEquals(range3.hashCode(), range4.hashCode(), "isIn('a', 'z') vs isIn('b', 'z')");
347         assertNotEquals(range1.hashCode(), range5.hashCode(), "is('a') vs isNot('a')");
348         assertNotEquals(range3.hashCode(), range6.hashCode(), "isIn('a', 'z') vs isNotIn('a', 'z')");
349         assertNotEquals(range6.hashCode(), range7.hashCode(), "isNotIn('a', 'z') vs isNotIn('b', 'z')");
350         assertNotEquals(range8.hashCode(), range9.hashCode(), "isIn(1, 2) vs isNotIn(1, 2)");
351         // Test that equal ranges have equal hash codes
352         final CharRange sameAsRange1 = CharRange.is('a');
353         assertEquals(range1, sameAsRange1, "Equal ranges should be equal");
354         assertEquals(range1.hashCode(), sameAsRange1.hashCode(), "Equal ranges should have equal hash codes");
355     }
356 
357     @Test
358     void testIterator() {
359         final CharRange a = CharRange.is('a');
360         final CharRange ad = CharRange.isIn('a', 'd');
361         final CharRange nota = CharRange.isNot('a');
362         final CharRange emptySet = CharRange.isNotIn((char) 0, Character.MAX_VALUE);
363         final CharRange notFirst = CharRange.isNotIn((char) 1, Character.MAX_VALUE);
364         final CharRange notLast = CharRange.isNotIn((char) 0, (char) (Character.MAX_VALUE - 1));
365 
366         final Iterator<Character> aIt = a.iterator();
367         assertNotNull(aIt);
368         assertTrue(aIt.hasNext());
369         assertEquals(Character.valueOf('a'), aIt.next());
370         assertFalse(aIt.hasNext());
371 
372         final Iterator<Character> adIt = ad.iterator();
373         assertNotNull(adIt);
374         assertTrue(adIt.hasNext());
375         assertEquals(Character.valueOf('a'), adIt.next());
376         assertEquals(Character.valueOf('b'), adIt.next());
377         assertEquals(Character.valueOf('c'), adIt.next());
378         assertEquals(Character.valueOf('d'), adIt.next());
379         assertFalse(adIt.hasNext());
380 
381         final Iterator<Character> notaIt = nota.iterator();
382         assertNotNull(notaIt);
383         assertTrue(notaIt.hasNext());
384         while (notaIt.hasNext()) {
385             final Character c = notaIt.next();
386             assertNotEquals('a', c.charValue());
387         }
388 
389         final Iterator<Character> emptySetIt = emptySet.iterator();
390         assertNotNull(emptySetIt);
391         assertFalse(emptySetIt.hasNext());
392         assertThrows(NoSuchElementException.class, emptySetIt::next);
393 
394         final Iterator<Character> notFirstIt = notFirst.iterator();
395         assertNotNull(notFirstIt);
396         assertTrue(notFirstIt.hasNext());
397         assertEquals(Character.valueOf((char) 0), notFirstIt.next());
398         assertFalse(notFirstIt.hasNext());
399         assertThrows(NoSuchElementException.class, notFirstIt::next);
400 
401         final Iterator<Character> notLastIt = notLast.iterator();
402         assertNotNull(notLastIt);
403         assertTrue(notLastIt.hasNext());
404         assertEquals(Character.valueOf(Character.MAX_VALUE), notLastIt.next());
405         assertFalse(notLastIt.hasNext());
406         assertThrows(NoSuchElementException.class, notLastIt::next);
407     }
408 
409     @Test
410     void testIteratorRemove() {
411         final CharRange a = CharRange.is('a');
412         final Iterator<Character> aIt = a.iterator();
413         assertThrows(UnsupportedOperationException.class, aIt::remove);
414     }
415 
416     @Test
417     void testSerialization() {
418         CharRange range = CharRange.is('a');
419         assertEquals(range, SerializationUtils.clone(range));
420         range = CharRange.isIn('a', 'e');
421         assertEquals(range, SerializationUtils.clone(range));
422         range = CharRange.isNotIn('a', 'e');
423         assertEquals(range, SerializationUtils.clone(range));
424     }
425 }