View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.text;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotEquals;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.UnsupportedEncodingException;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.commons.lang3.ArrayUtils;
33  import org.junit.jupiter.api.Test;
34  
35  /**
36   * Tests {@link AlphabetConverter}.
37   */
38  class AlphabetConverterTest {
39  
40      private static final Character[] LOWER_CASE_ENGLISH = { ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
41              't', 'u', 'v', 'w', 'x', 'y', 'z' };
42  
43      private static final Character[] ENGLISH_AND_NUMBERS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
44              'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
45              'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ' };
46  
47      private static final Character[] LOWER_CASE_ENGLISH_AND_NUMBERS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
48              'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ' };
49  
50      private static final Character[] NUMBERS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
51  
52      private static final Character[] BINARY = { '0', '1' };
53  
54      private static final Character[] HEBREW = { '_', ' ', '\u05e7', '\u05e8', '\u05d0', '\u05d8', '\u05d5', '\u05df', '\u05dd', '\u05e4', '\u05e9', '\u05d3',
55              '\u05d2', '\u05db', '\u05e2', '\u05d9', '\u05d7', '\u05dc', '\u05da', '\u05e3', '\u05d6', '\u05e1', '\u05d1', '\u05d4', '\u05e0', '\u05de',
56              '\u05e6', '\u05ea', '\u05e5' };
57  
58      private static final Integer[] UNICODE = { 32, 35395, 35397, 36302, 36291, 35203, 35201, 35215, 35219, 35268, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
59              107, 108, 109, 110, 1001, 1002, 1003, 1004, 1005 };
60  
61      private static final Integer[] LOWER_CASE_ENGLISH_CODEPOINTS = { 32, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
62              115, 116, 117, 118, 119, 120, 121, 122 };
63  
64      private static final Integer[] DO_NOT_ENCODE_CODEPOINTS = { 32, 97, 98, 99 }; // space, a, b, c
65  
66      private AlphabetConverter createJavadocExample() {
67          final Character[] original = { 'a', 'b', 'c', 'd' };
68          final Character[] encoding = { '0', '1', 'd' };
69          final Character[] doNotEncode = { 'd' };
70  
71          return AlphabetConverter.createConverterFromChars(original, encoding, doNotEncode);
72      }
73  
74      private void test(final Character[] originalChars, final Character[] encodingChars, final Character[] doNotEncodeChars, final String... strings)
75              throws UnsupportedEncodingException {
76  
77          final AlphabetConverter ac = AlphabetConverter.createConverterFromChars(originalChars, encodingChars, doNotEncodeChars);
78  
79          final AlphabetConverter reconstructedAlphabetConverter = AlphabetConverter.createConverterFromMap(ac.getOriginalToEncoded());
80  
81          assertEquals(ac, reconstructedAlphabetConverter);
82          assertEquals(ac.hashCode(), reconstructedAlphabetConverter.hashCode());
83          assertEquals(ac.toString(), reconstructedAlphabetConverter.toString());
84          assertNull(ac.encode(null)); // test null conversions
85          assertEquals("", ac.encode("")); // test empty conversion
86  
87          // test all the trial strings
88          for (final String s : strings) {
89              final String encoded = ac.encode(s);
90  
91              // test that only encoding chars are used
92              final List<Character> originalEncodingChars = Arrays.asList(encodingChars);
93              for (int i = 0; i < encoded.length(); i++) {
94                  assertTrue(originalEncodingChars.contains(encoded.charAt(i)));
95              }
96  
97              final String decoded = ac.decode(encoded);
98  
99              // test that only the original alphabet is used after decoding
100             final List<Character> originalCharsList = Arrays.asList(originalChars);
101             for (int i = 0; i < decoded.length(); i++) {
102                 assertTrue(originalCharsList.contains(decoded.charAt(i)));
103             }
104 
105             assertEquals(s, decoded, () -> "Encoded '" + s + "' into '" + encoded + "', but decoded into '" + decoded + "'");
106         }
107     }
108 
109     @Test
110     void testBinaryTest() throws UnsupportedEncodingException {
111         test(BINARY, NUMBERS, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "0", "1", "10", "11");
112         test(NUMBERS, BINARY, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "12345", "0");
113         test(LOWER_CASE_ENGLISH, BINARY, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "abc", "a");
114     }
115 
116     @Test
117     void testCreateConverterFromCharsAndEquals() {
118         final Character[] characterArray = new Character[2];
119         final char charOne = '+';
120         final char character = '+';
121         characterArray[0] = character;
122         characterArray[1] = characterArray[0];
123         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromChars(characterArray, characterArray, characterArray);
124 
125         assertNotEquals(alphabetConverter, charOne);
126     }
127 
128     @Test
129     void testCreateConverterFromCharsOne() {
130         final Character[] characterArray = new Character[2];
131         characterArray[0] = '5';
132         characterArray[1] = characterArray[0];
133         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromChars(characterArray, characterArray, characterArray);
134 
135         assertEquals(1, alphabetConverter.getEncodedCharLength());
136     }
137 
138     @Test
139     void testCreateConverterFromCharsWithNullAndNull() {
140         assertThrows(IllegalArgumentException.class, () -> {
141             final Character[] characterArray = new Character[2];
142             characterArray[0] = '$';
143             characterArray[1] = characterArray[0];
144             AlphabetConverter.createConverterFromChars(characterArray, null, null);
145         });
146     }
147 
148     @Test
149     void testCreateConverterFromMapAndEquals() {
150         final Map<Integer, String> hashMap = new HashMap<>();
151         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromMap(hashMap);
152         hashMap.put(0, "CtDs");
153         final AlphabetConverter alphabetConverterTwo = AlphabetConverter.createConverterFromMap(hashMap);
154         assertNotEquals(alphabetConverter, alphabetConverterTwo);
155         assertEquals(1, alphabetConverter.getEncodedCharLength());
156     }
157 
158     @Test
159     void testDecodeReturningNull() throws UnsupportedEncodingException {
160         final Map<Integer, String> map = new HashMap<>();
161         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromMap(map);
162         alphabetConverter.decode(null);
163         assertEquals(1, alphabetConverter.getEncodedCharLength());
164     }
165 
166     @Test
167     void testDoNotEncodeTest() throws UnsupportedEncodingException {
168         test(ENGLISH_AND_NUMBERS, LOWER_CASE_ENGLISH_AND_NUMBERS, LOWER_CASE_ENGLISH, "1", "456", "abc", "ABC", "this will not be converted but THIS WILL");
169         test(ENGLISH_AND_NUMBERS, LOWER_CASE_ENGLISH_AND_NUMBERS, NUMBERS, "1", "456", "abc", "ABC", "this will be converted but 12345 and this will be");
170     }
171 
172     @Test
173     void testEncodeFailureTest() {
174         assertEquals("Couldn't find encoding for '3' in 3",
175                 assertThrows(UnsupportedEncodingException.class, () -> test(BINARY, NUMBERS, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "3")).getMessage());
176     }
177 
178     @Test
179     void testEquals() {
180         final Character[] characterArray = new Character[2];
181         final char character = 'R';
182         characterArray[0] = character;
183         characterArray[1] = character;
184         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromChars(characterArray, characterArray, characterArray);
185         final Map<Integer, String> map = new HashMap<>();
186         final AlphabetConverter alphabetConverterTwo = AlphabetConverter.createConverterFromMap(map);
187 
188         assertEquals(1, alphabetConverterTwo.getEncodedCharLength());
189         assertNotEquals(alphabetConverter, alphabetConverterTwo);
190     }
191 
192     @Test
193     void testEqualsWithNull() {
194         final Character[] characterArray = ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY;
195         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromChars(characterArray, null, null);
196 
197         assertFalse(alphabetConverter.equals(null));
198     }
199 
200     @Test
201     void testEqualsWithSameObject() {
202         final Character[] characterArray = new Character[2];
203         final char character = 'R';
204         characterArray[0] = character;
205         characterArray[1] = character;
206         final AlphabetConverter alphabetConverter = AlphabetConverter.createConverterFromChars(characterArray, characterArray, characterArray);
207 
208         assertEquals(alphabetConverter, alphabetConverter);
209     }
210 
211     @Test
212     void testHebrewTest() throws UnsupportedEncodingException {
213         test(HEBREW, BINARY, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "\u05d0", "\u05e2",
214                 "\u05d0\u05dc\u05e3_\u05d0\u05d5\u05d4\u05d1\u05dc_\u05d1\u05d9\u05ea_\u05d6\u05d4_\u05d1\u05d9\u05ea_"
215               + "\u05d2\u05d9\u05de\u05dc_\u05d6\u05d4_\u05db\u05de\u05dc_\u05d2\u05d3\u05d5\u05dc");
216         test(HEBREW, NUMBERS, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "\u05d0", "\u05e2",
217                 "\u05d0\u05dc\u05e3_\u05d0\u05d5\u05d4\u05d1\u05dc_\u05d1\u05d9\u05ea_\u05d6\u05d4_\u05d1\u05d9\u05ea_"
218               + "\u05d2\u05d9\u05de\u05dc_\u05d6\u05d4_\u05db\u05de\u05dc_\u05d2\u05d3\u05d5\u05dc");
219         test(NUMBERS, HEBREW, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "123456789", "1", "5");
220         test(LOWER_CASE_ENGLISH, HEBREW, ArrayUtils.EMPTY_CHARACTER_OBJECT_ARRAY, "this is a test");
221     }
222 
223     /*
224      * Test example in javadocs for consistency
225      */
226     @Test
227     void testJavadocExampleTest() throws UnsupportedEncodingException {
228         final AlphabetConverter ac = createJavadocExample();
229 
230         assertEquals("00", ac.encode("a"));
231         assertEquals("01", ac.encode("b"));
232         assertEquals("0d", ac.encode("c"));
233         assertEquals("d", ac.encode("d"));
234         assertEquals("00010dd", ac.encode("abcd"));
235     }
236 
237     @Test
238     void testMissingDoNotEncodeLettersFromEncodingTest() {
239         assertEquals("Can not use 'do not encode' list because encoding alphabet does not contain '0'",
240                 assertThrows(IllegalArgumentException.class, () -> AlphabetConverter.createConverterFromChars(ENGLISH_AND_NUMBERS, LOWER_CASE_ENGLISH, NUMBERS))
241                         .getMessage());
242     }
243 
244     @Test
245     void testMissingDoNotEncodeLettersFromOriginalTest() {
246         assertEquals("Can not use 'do not encode' list because original alphabet does not contain '0'",
247                 assertThrows(IllegalArgumentException.class, () -> AlphabetConverter.createConverterFromChars(LOWER_CASE_ENGLISH, ENGLISH_AND_NUMBERS, NUMBERS))
248                         .getMessage());
249     }
250 
251     @Test
252     void testNoEncodingLettersTest() {
253         assertEquals("Must have at least two encoding characters (excluding those in the 'do not encode' list), but has 0",
254                 assertThrows(IllegalArgumentException.class, () -> AlphabetConverter.createConverterFromChars(ENGLISH_AND_NUMBERS, NUMBERS, NUMBERS))
255                         .getMessage());
256     }
257 
258     @Test
259     void testOnlyOneEncodingLettersTest() {
260         assertEquals("Must have at least two encoding characters (excluding those in the 'do not encode' list), but has 1",
261                 assertThrows(IllegalArgumentException.class, () -> {
262                     final Character[] numbersPlusUnderscore = Arrays.copyOf(NUMBERS, NUMBERS.length + 1);
263                     numbersPlusUnderscore[numbersPlusUnderscore.length - 1] = '_';
264 
265                     AlphabetConverter.createConverterFromChars(ENGLISH_AND_NUMBERS, numbersPlusUnderscore, NUMBERS);
266                 }).getMessage());
267     }
268 
269     @Test
270     void testUnexpectedEndWhileDecodingTest() {
271         final String toDecode = "00d01d0";
272         assertEquals("Unexpected end of string while decoding " + toDecode,
273                 assertThrows(UnsupportedEncodingException.class, () -> createJavadocExample().decode(toDecode)).getMessage());
274     }
275 
276     @Test
277     void testUnexpectedStringWhileDecodingTest() {
278         final String toDecode = "00XX";
279         assertEquals("Unexpected string without decoding (XX) in " + toDecode,
280                 assertThrows(UnsupportedEncodingException.class, () -> createJavadocExample().decode(toDecode)).getMessage());
281     }
282 
283     /**
284      * Test constructor from code points
285      */
286     @Test
287     void testUnicodeTest() throws UnsupportedEncodingException {
288         final AlphabetConverter ac = AlphabetConverter.createConverter(UNICODE, LOWER_CASE_ENGLISH_CODEPOINTS, DO_NOT_ENCODE_CODEPOINTS);
289         assertEquals(2, ac.getEncodedCharLength());
290         final String original = "\u8a43\u8a45 \u8dce ab \u8dc3 c \u8983";
291         final String encoded = ac.encode(original);
292         final String decoded = ac.decode(encoded);
293         assertEquals(original, decoded, () -> "Encoded '" + original + "' into '" + encoded + "', but decoded into '" + decoded + "'");
294     }
295 
296 }