Skip to content

Commit 5936b7c

Browse files
authored
Merge pull request #104 from IBM-Cloud/fix-numeric-keys
Fix numeric keys
2 parents c40e8ad + 8bcd488 commit 5936b7c

5 files changed

Lines changed: 632 additions & 49 deletions

File tree

gp-res-filter/src/main/java/com/ibm/g11n/pipeline/resfilter/impl/JsonResource.java

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.util.Collections;
2828
import java.util.List;
2929
import java.util.Map;
30-
import java.util.regex.Matcher;
3130
import java.util.regex.Pattern;
3231

3332
import com.google.gson.GsonBuilder;
@@ -54,7 +53,7 @@
5453
*/
5554
public class JsonResource extends ResourceFilter {
5655

57-
private class KeyPiece {
56+
static class KeyPiece {
5857
String keyValue;
5958
JsonToken keyType;
6059

@@ -248,55 +247,59 @@ public void write(OutputStream outStream, LanguageBundle languageBundle,
248247
}
249248
}
250249

251-
private List<KeyPiece> splitKeyPieces(String key) {
250+
static List<KeyPiece> splitKeyPieces(String key) {
252251
if (USE_JSONPATH_PATTERN.matcher(key).matches()) {
253252
List<KeyPiece> result = new ArrayList<KeyPiece>();
254-
Matcher onlyDigits = Pattern.compile("^\\d+$").matcher("");
253+
boolean inQuotes = false;
254+
StringBuilder currentToken = new StringBuilder();
255255
// Disregard $ at the beginning - it's not really part of the key...
256-
List<String> tokens = findTokens(key.substring(JSONPATH_ROOT.length()));
257-
for (String s : tokens) {
258-
if (s.startsWith("'")) {
259-
// Turn any "\u0027" in the key back into '
260-
String modifiedKeyPiece = s.substring(1, s.length() - 1).replaceAll("\\\\u0027", "'");
261-
result.add(new KeyPiece(modifiedKeyPiece, JsonToken.BEGIN_OBJECT));
262-
} else if (onlyDigits.reset(s).matches()) {
263-
result.add(new KeyPiece(s, JsonToken.BEGIN_ARRAY));
264-
} else {
265-
for (String s2 : s.split("\\.")) {
266-
if (!s2.isEmpty()) {
267-
result.add(new KeyPiece(s2, JsonToken.BEGIN_OBJECT));
256+
StringCharacterIterator i = new StringCharacterIterator(key.substring(JSONPATH_ROOT.length()));
257+
boolean inSubscript = false;
258+
while (i.current() != StringCharacterIterator.DONE) {
259+
char c = i.current();
260+
if (c == '\'') {
261+
inQuotes = !inQuotes;
262+
}
263+
if (!inQuotes && (c == '.' || c == '[' || c == ']')) {
264+
if (currentToken.length() > 0) {
265+
addToken(result, currentToken.toString(), inSubscript);
266+
currentToken.setLength(0);
267+
if (inSubscript) {
268+
inSubscript = false;
268269
}
269270
}
271+
if (c == '[') {
272+
inSubscript = true; // Record that the next token had an
273+
// array subscript on it.
274+
}
275+
} else {
276+
currentToken.append(c);
270277
}
278+
i.next();
271279
}
280+
addToken(result, currentToken.toString(), inSubscript);
281+
272282
return Collections.unmodifiableList(result);
273283
}
274284
// Otherwise, this is a plain JSON object label
275285
return Collections.singletonList(new KeyPiece(key, JsonToken.BEGIN_OBJECT));
276286
}
277287

278-
private static List<String> findTokens(String data) {
279-
List<String> tokens = new ArrayList<String>();
280-
boolean inQuotes = false;
281-
StringBuilder currentToken = new StringBuilder();
282-
StringCharacterIterator i = new StringCharacterIterator(data);
283-
while (i.current() != StringCharacterIterator.DONE) {
284-
char c = i.current();
285-
if (c == '\'') {
286-
inQuotes = !inQuotes;
287-
}
288-
if (!inQuotes && (c == '.' || c == '[' || c == ']')) {
289-
if (currentToken.length() > 0) {
290-
tokens.add(currentToken.toString());
291-
currentToken.setLength(0);
288+
static void addToken(List<KeyPiece> result, String s, boolean inSubscript) {
289+
if (s.startsWith("'")) {
290+
// Turn any "\u0027" in the key back into '
291+
String modifiedKeyPiece = s.substring(1, s.length() - 1).replaceAll("\\\\u0027", "'");
292+
result.add(new KeyPiece(modifiedKeyPiece, JsonToken.BEGIN_OBJECT));
293+
} else if (inSubscript) {
294+
// [0] produces an array
295+
result.add(new KeyPiece(s, JsonToken.BEGIN_ARRAY));
296+
} else {
297+
for (String s2 : s.split("\\.")) {
298+
if (!s2.isEmpty()) {
299+
result.add(new KeyPiece(s2, JsonToken.BEGIN_OBJECT));
292300
}
293-
} else {
294-
currentToken.append(c);
295301
}
296-
i.next();
297302
}
298-
tokens.add(currentToken.toString());
299-
return Collections.unmodifiableList(tokens);
300303
}
301304

302305
// TODO: Implement merge method

gp-res-filter/src/test/java/com/ibm/g11n/pipeline/resfilter/impl/JsonResourceTest.java

Lines changed: 125 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package com.ibm.g11n.pipeline.resfilter.impl;
1717

18+
import static org.junit.Assert.assertArrayEquals;
1819
import static org.junit.Assert.assertEquals;
1920
import static org.junit.Assert.assertTrue;
2021

2122
import java.io.File;
2223
import java.io.FileInputStream;
24+
import java.io.FileNotFoundException;
2325
import java.io.FileOutputStream;
2426
import java.io.IOException;
2527
import java.io.InputStream;
@@ -29,21 +31,28 @@
2931
import java.util.Collections;
3032
import java.util.LinkedList;
3133
import java.util.List;
34+
import java.util.Map.Entry;
3235

3336
import org.junit.Test;
3437

38+
import com.google.gson.JsonArray;
39+
import com.google.gson.JsonElement;
40+
import com.google.gson.JsonObject;
3541
import com.ibm.g11n.pipeline.resfilter.LanguageBundle;
3642
import com.ibm.g11n.pipeline.resfilter.LanguageBundleBuilder;
3743
import com.ibm.g11n.pipeline.resfilter.ResourceFilterException;
3844
import com.ibm.g11n.pipeline.resfilter.ResourceString;
3945
import com.ibm.g11n.pipeline.resfilter.ResourceString.ResourceStringComparator;
46+
import com.ibm.g11n.pipeline.resfilter.impl.JsonResource.KeyPiece;
4047

4148
/**
4249
* @author farhan
4350
*
4451
*/
4552
public class JsonResourceTest {
4653
private static final File INPUT_FILE = new File("src/test/resource/resfilter/json/input.json");
54+
private static final File INPUT_FILE2 = new File("src/test/resource/resfilter/json/other-input.json");
55+
private static final File SPLITKEYS = new File("src/test/resource/resfilter/json/testSplitKeys.json");
4756

4857
private static final File EXPECTED_WRITE_FILE = new File("src/test/resource/resfilter/json/write-output.json");
4958

@@ -62,8 +71,10 @@ public class JsonResourceTest {
6271
lst.add(ResourceString.with("$.countries[1].Asia[1]", "Japan").sequenceNumber(9).build());
6372
lst.add(ResourceString.with("$.countries[1].Asia[2]", "India").sequenceNumber(10).build());
6473
lst.add(ResourceString.with("$.countries[2].Americas['S. America'][0]", "Brazil").sequenceNumber(11).build());
65-
lst.add(ResourceString.with("$.countries[2].Americas['S. America'][1]", "Venezuela").sequenceNumber(12).build());
66-
lst.add(ResourceString.with("$.countries[2].Americas['N. America'][0]", "United States [USA]").sequenceNumber(13).build());
74+
lst.add(ResourceString.with("$.countries[2].Americas['S. America'][1]", "Venezuela").sequenceNumber(12)
75+
.build());
76+
lst.add(ResourceString.with("$.countries[2].Americas['N. America'][0]", "United States [USA]")
77+
.sequenceNumber(13).build());
6778
lst.add(ResourceString.with("$.countries[2].Americas['N. America'][1]", "Canada").sequenceNumber(14).build());
6879
lst.add(ResourceString.with("$.countries[2].Americas['N. America'][2]", "Mexico").sequenceNumber(15).build());
6980
lst.add(ResourceString.with("$.countries[3].Africa[0]", "Egypt").sequenceNumber(16).build());
@@ -77,10 +88,14 @@ public class JsonResourceTest {
7788
lst.add(ResourceString.with("another.text", "Another plain old string").sequenceNumber(24).build());
7889
lst.add(ResourceString.with("frog['2']", "Red-eyed Tree Frog").sequenceNumber(25).build());
7990
lst.add(ResourceString.with("owl[3]", "Great Horned Owl").sequenceNumber(26).build());
80-
lst.add(ResourceString.with("$['$.xxx']", "Looks like JSONPATH, but actually plain old string").sequenceNumber(27).build());
81-
lst.add(ResourceString.with("$['$.']", "Looks like JSONPATH prefix, but actually plain old string").sequenceNumber(28).build());
82-
lst.add(ResourceString.with("$abc", "Starts with JSONPATH root char, but just a string").sequenceNumber(29).build());
83-
lst.add(ResourceString.with("$['ibm.com']['g11n.pipeline.title']", "Globalization Pipeline").sequenceNumber(30).build());
91+
lst.add(ResourceString.with("$['$.xxx']", "Looks like JSONPATH, but actually plain old string")
92+
.sequenceNumber(27).build());
93+
lst.add(ResourceString.with("$['$.']", "Looks like JSONPATH prefix, but actually plain old string")
94+
.sequenceNumber(28).build());
95+
lst.add(ResourceString.with("$abc", "Starts with JSONPATH root char, but just a string").sequenceNumber(29)
96+
.build());
97+
lst.add(ResourceString.with("$['ibm.com']['g11n.pipeline.title']", "Globalization Pipeline").sequenceNumber(30)
98+
.build());
8499

85100
Collections.sort(lst, new ResourceStringComparator());
86101
EXPECTED_INPUT_RES_LIST = lst;
@@ -117,7 +132,8 @@ public class JsonResourceTest {
117132
bundleBuilder.addResourceString("frog['2']", "Red-eyed Tree Frog - XL", 25);
118133
bundleBuilder.addResourceString("owl[3]", "Great Horned Owl - XL", 26);
119134
bundleBuilder.addResourceString("$['$.xxx']", "Looks like JSONPATH, but actually plain old string - XL", 27);
120-
bundleBuilder.addResourceString("$['$.']", "Looks like JSONPATH prefix, but actually plain old string - XL", 28);
135+
bundleBuilder.addResourceString("$['$.']", "Looks like JSONPATH prefix, but actually plain old string - XL",
136+
28);
121137
bundleBuilder.addResourceString("$abc", "Starts with JSONPATH root char, but just a string - XL", 29);
122138
bundleBuilder.addResourceString("$['ibm.com']['g11n.pipeline.title']", "Globalization Pipeline - XL", 30);
123139
WRITE_BUNDLE = bundleBuilder.build();
@@ -133,7 +149,8 @@ public void testParse() throws IOException, ResourceFilterException {
133149
LanguageBundle bundle = res.parse(is, null);
134150
List<ResourceString> resStrList = new ArrayList<>(bundle.getResourceStrings());
135151
Collections.sort(resStrList, new ResourceStringComparator());
136-
assertEquals("ResourceStrings did not match.", EXPECTED_INPUT_RES_LIST, resStrList);
152+
assertArrayEquals("ResourceStrings did not match.", EXPECTED_INPUT_RES_LIST.toArray(),
153+
resStrList.toArray());
137154
}
138155
}
139156

@@ -145,12 +162,108 @@ public void testWrite() throws IOException, ResourceFilterException {
145162
try (OutputStream os = new FileOutputStream(tempFile)) {
146163
res.write(os, WRITE_BUNDLE, null);
147164
os.flush();
148-
assertTrue(ResourceTestUtil.compareFiles(EXPECTED_WRITE_FILE, tempFile));
165+
ResourceTestUtil.compareFilesJson(EXPECTED_WRITE_FILE, tempFile);
166+
}
167+
}
168+
169+
// @Test
170+
// public void testTestFiles() throws IOException, ResourceFilterException {
171+
// // just test the test files
172+
// ResourceTestUtil.compareFilesJson(INPUT_FILE, EXPECTED_WRITE_FILE);
173+
// }
174+
175+
@Test
176+
public void testReWrite() throws IOException, ResourceFilterException {
177+
// First parse
178+
assertTrue("The input test file <" + INPUT_FILE + "> does not exist.", INPUT_FILE.exists());
179+
180+
try (InputStream is = new FileInputStream(INPUT_FILE)) {
181+
JsonResource res2 = new JsonResource();
182+
LanguageBundle bundle = res2.parse(is, null);
183+
List<ResourceString> resStrList = new ArrayList<>(bundle.getResourceStrings());
184+
Collections.sort(resStrList, new ResourceStringComparator());
185+
assertArrayEquals("ResourceStrings did not match.", EXPECTED_INPUT_RES_LIST.toArray(),
186+
resStrList.toArray());
187+
188+
// Now write
189+
File tempFile = File.createTempFile(this.getClass().getSimpleName(), "2.json");
190+
// File tempFile = new File("/tmp/2.json");
191+
tempFile.deleteOnExit();
192+
193+
try (OutputStream os = new FileOutputStream(tempFile)) {
194+
res.write(os, bundle, null);
195+
os.flush();
196+
ResourceTestUtil.compareFilesJson(INPUT_FILE, tempFile);
197+
}
198+
}
199+
}
200+
201+
@Test
202+
public void testReWriteOther() throws IOException, ResourceFilterException {
203+
// First parse
204+
assertTrue("The input test file <" + INPUT_FILE + "> does not exist.", INPUT_FILE2.exists());
205+
206+
try (InputStream is = new FileInputStream(INPUT_FILE2)) {
207+
JsonResource res2 = new JsonResource();
208+
LanguageBundle bundle = res2.parse(is, null);
209+
List<ResourceString> resStrList = new ArrayList<>(bundle.getResourceStrings());
210+
Collections.sort(resStrList, new ResourceStringComparator());
211+
// assertEquals("ResourceStrings did not match.",
212+
// EXPECTED_INPUT_RES_LIST, resStrList);
213+
214+
// Now write
215+
File tempFile = File.createTempFile(this.getClass().getSimpleName(), "3.json");
216+
// File tempFile = new File("/tmp/3.json");
217+
tempFile.deleteOnExit();
218+
219+
try (OutputStream os = new FileOutputStream(tempFile)) {
220+
res.write(os, bundle, null);
221+
os.flush();
222+
System.out.println(ResourceTestUtil.fileToString(tempFile));
223+
ResourceTestUtil.compareFilesJson(INPUT_FILE2, tempFile);
224+
}
225+
}
226+
}
227+
228+
/**
229+
* @throws IOException
230+
* @throws FileNotFoundException
231+
* @See {@link JsonResource#splitKeyPieces(String)}
232+
*/
233+
@Test
234+
public void testSplitKeyPieces() throws FileNotFoundException, IOException {
235+
/*
236+
* Initial data generated with: Gson g = new GsonBuilder().create();
237+
* TreeMap<String, List<KeyPiece>> tm = new TreeMap<String,
238+
* List<KeyPiece>>();
239+
*
240+
* for (final ResourceString s : EXPECTED_INPUT_RES_LIST) {
241+
* tm.put(s.getKey(), JsonResource.splitKeyPieces(s.getKey())); }
242+
* System.out.println(g.toJson(tm));
243+
*/
244+
245+
JsonObject SPLITKEY_DATA = ResourceTestUtil.parseJson(SPLITKEYS).getAsJsonObject();
246+
for (final Entry<String, JsonElement> e : SPLITKEY_DATA.entrySet()) {
247+
final String key = e.getKey();
248+
final JsonArray expectList = e.getValue().getAsJsonArray();
249+
250+
List<KeyPiece> actualList = JsonResource.splitKeyPieces(key);
251+
252+
String prefix = "‘" + key + "’: ";
253+
assertEquals(prefix + "key count", expectList.size(), actualList.size());
254+
for (int n = 0; n < expectList.size(); n++) {
255+
JsonObject expectObject = expectList.get(n).getAsJsonObject();
256+
KeyPiece actualObject = actualList.get(n);
257+
String subPrefix = prefix + " key " + n;
258+
assertEquals(subPrefix + " value ", expectObject.get("keyValue").getAsString(), actualObject.keyValue);
259+
assertEquals(subPrefix + " type ", expectObject.get("keyType").getAsString(),
260+
actualObject.keyType.name());
261+
}
149262
}
150263
}
151264

152265
// TODO: Not ready yet
153-
// @Test
154-
// public void testMerge() throws IOException, ResourceFilterException {
155-
// }
266+
// @Test
267+
// public void testMerge() throws IOException, ResourceFilterException {
268+
// }
156269
}

gp-res-filter/src/test/java/com/ibm/g11n/pipeline/resfilter/impl/ResourceTestUtil.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@
1616

1717
package com.ibm.g11n.pipeline.resfilter.impl;
1818

19+
import static org.junit.Assert.assertEquals;
1920
import static org.junit.Assert.fail;
2021

2122
import java.io.BufferedReader;
2223
import java.io.File;
2324
import java.io.FileNotFoundException;
2425
import java.io.FileReader;
2526
import java.io.IOException;
27+
import java.io.Reader;
2628
import java.nio.file.Files;
2729
import java.nio.file.Paths;
2830

31+
import com.google.gson.JsonElement;
32+
import com.google.gson.JsonParser;
33+
2934
/**
3035
* @author farhan, JCEmmons
3136
*
@@ -76,11 +81,13 @@ public static boolean compareFiles(File expected, File actual, int n) throws Fil
7681

7782
return true;
7883
}
84+
7985
/**
8086
* Returns true if the two files match exactly up to the number of lines
81-
* specified in n
87+
* specified in n
8288
*/
83-
public static boolean compareFilesUpTo(File expected, File actual, int n) throws FileNotFoundException, IOException {
89+
public static boolean compareFilesUpTo(File expected, File actual, int n)
90+
throws FileNotFoundException, IOException {
8491
try (BufferedReader expectedRdr = new BufferedReader(new FileReader(expected));
8592
BufferedReader actualRdr = new BufferedReader(new FileReader(actual))) {
8693

@@ -124,4 +131,26 @@ public static String fileToString(File file) throws FileNotFoundException, IOExc
124131
byte[] encoded = Files.readAllBytes(Paths.get(file.getPath()));
125132
return new String(encoded, "UTF-8");
126133
}
134+
135+
/**
136+
* @param expectedWriteFile
137+
* @param tempFile
138+
* @return
139+
* @throws IOException
140+
* @throws FileNotFoundException
141+
*/
142+
public static void compareFilesJson(File expectedWriteFile, File tempFile)
143+
throws FileNotFoundException, IOException {
144+
JsonElement expected = parseJson(expectedWriteFile);
145+
JsonElement actual = parseJson(tempFile);
146+
assertEquals("JSON mismatch: " + tempFile.getName() + " did not match " + expectedWriteFile.getName(), expected,
147+
actual);
148+
}
149+
150+
public static JsonElement parseJson(final File f) throws FileNotFoundException, IOException {
151+
try (final Reader reader = new FileReader(f)) {
152+
JsonElement parse = new JsonParser().parse(reader);
153+
return parse;
154+
}
155+
}
127156
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"hello": {
3+
"1": "Firstly",
4+
"two": "Secondly"
5+
}
6+
}

0 commit comments

Comments
 (0)