Skip to content

Commit de93b6c

Browse files
authored
Merge pull request #5 from wsw-stack/milestone5-jiacheng
Milestone5 finished
2 parents fafc52d + 6d6f21a commit de93b6c

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

README-M5.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Milestone 5 – Async support for **JSONObject**
2+
3+
## What is Added
4+
5+
| Item | Description |
6+
|------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
7+
| `Future<JSONObject> XMLUtils.toJSONObject(Reader reader, Consumer<JSONObject> after, Consumer<Exception> error)` | **Non-blocking** conversion. Parses the XML read from `reader` into a `JSONObject` on a background thread. When parsing finishes it invokes `after.accept(result)`; when failure it calls `error.accept(ex)` and throws the exception into the returned `Future`. |
8+
| `AsyncRunner` | Tiny task aggregator. Call `add(Future<JSONObject> task)` to collect jobs, then wait for them all (e.g. `forEach(Future::get)`). |
9+
| `ExecutorService` | The default thread pool (size = available CPU cores). If you prefer a custom pool you can swap it out before calling the API (e.g. add `XMLUtils.setExecutor(...)`). |
10+
11+
Input: XML of different sizes (where they will be parsed concurrently)
12+
13+
---
14+
15+
## Run the test class
16+
```bash
17+
mvn -Dtest=org.json.junit.milestone5.tests.JSONObjectAsyncTest test

src/main/java/org/json/XML.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.math.BigInteger;
1111
import java.util.*;
1212
import java.io.BufferedReader;
13+
import java.util.concurrent.*;
14+
import java.util.function.Consumer;
1315
import java.util.function.Function;
1416
import java.util.stream.Collectors;
1517

@@ -2237,4 +2239,66 @@ private static final String indent(int indent) {
22372239
}
22382240
return sb.toString();
22392241
}
2242+
2243+
// milestone 5
2244+
private static final ExecutorService executor = Executors.newFixedThreadPool(
2245+
Runtime.getRuntime().availableProcessors()
2246+
);
2247+
2248+
public static Future<JSONObject> toJSONObject(
2249+
Reader reader,
2250+
Consumer<JSONObject> after,
2251+
Consumer<Exception> error
2252+
) {
2253+
FutureTask<JSONObject> task = new FutureTask<>(new FutureTaskCallable(reader, after, error));
2254+
executor.execute(task);
2255+
return task;
2256+
}
2257+
2258+
private static class FutureTaskCallable implements Callable<JSONObject> {
2259+
private final Reader reader;
2260+
private final Consumer<JSONObject> after;
2261+
private final Consumer<Exception> error;
2262+
2263+
public FutureTaskCallable(
2264+
Reader reader,
2265+
Consumer<JSONObject> after,
2266+
Consumer<Exception> error
2267+
) {
2268+
this.reader = reader;
2269+
this.after = after;
2270+
this.error = error;
2271+
}
2272+
2273+
@Override
2274+
public JSONObject call() throws Exception {
2275+
JSONObject jo = new JSONObject();
2276+
try {
2277+
XMLTokener x = new XMLTokener(reader);
2278+
while (x.more()) {
2279+
x.skipPast("<");
2280+
if (x.more()) {
2281+
parse(x, jo, null, XMLParserConfiguration.ORIGINAL, 0);
2282+
}
2283+
}
2284+
after.accept(jo);
2285+
return jo;
2286+
} catch (Exception e) {
2287+
error.accept(e);
2288+
throw e;
2289+
}
2290+
}
2291+
}
2292+
2293+
public static class AsyncRunner {
2294+
private List<Future<JSONObject>> tasks = new ArrayList<>();
2295+
2296+
public AsyncRunner() {
2297+
}
2298+
2299+
public void add(Future<JSONObject> task) {
2300+
this.tasks.add(task);
2301+
}
2302+
2303+
}
22402304
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package org.json.junit.milestone5;
2+
3+
import org.json.JSONArray;
4+
import org.json.JSONObject;
5+
import org.json.XML;
6+
import org.junit.Test;
7+
8+
import java.io.Reader;
9+
import java.io.StringReader;
10+
import java.util.concurrent.CountDownLatch;
11+
import java.util.concurrent.Future;
12+
import java.util.concurrent.TimeUnit;
13+
14+
import static org.junit.Assert.*;
15+
16+
public class JSONObjectAsyncTest {
17+
18+
private final XML.AsyncRunner runner = new XML.AsyncRunner();
19+
20+
public JSONObjectAsyncTest() {
21+
// Default constructor
22+
}
23+
24+
private final Reader medReader = new StringReader(
25+
"<catalog>"
26+
+ " <book id=\"bk101\">"
27+
+ " <author>Gambardella, Matthew</author>"
28+
+ " <title>XML Developer's Guide</title>"
29+
+ " <genre>Computer</genre>"
30+
+ " <price>44.95</price>"
31+
+ " <publish_date>2000-10-01</publish_date>"
32+
+ " <description>An in-depth look at creating applications with XML.</description>"
33+
+ " </book>"
34+
+ " <book id=\"bk102\">"
35+
+ " <author>Ralls, Kim</author>"
36+
+ " <title>Midnight Rain</title>"
37+
+ " <genre>Fantasy</genre>"
38+
+ " <price>5.95</price>"
39+
+ " <publish_date>2000-12-16</publish_date>"
40+
+ " <description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.</description>"
41+
+ " </book>"
42+
+ "</catalog>"
43+
);
44+
45+
private final Reader smallReader = new StringReader(
46+
"<root>"
47+
+ " <message>Hello</message>"
48+
+ "</root>"
49+
);
50+
51+
@Test
52+
public void testAsyncParsing() throws Exception {
53+
CountDownLatch latch = new CountDownLatch(2);
54+
JSONObject[] results = new JSONObject[2];
55+
Exception[] errors = new Exception[2];
56+
long[] timeElapsed = new long[2];
57+
58+
final long startTime = System.nanoTime();
59+
60+
Future<JSONObject> task1 = XML.toJSONObject(
61+
medReader,
62+
jo -> {
63+
results[0] = jo;
64+
long endNano = System.nanoTime();
65+
timeElapsed[0] = TimeUnit.NANOSECONDS.toMillis(endNano - startTime);
66+
latch.countDown();
67+
},
68+
e -> {
69+
errors[0] = e;
70+
latch.countDown();
71+
}
72+
);
73+
74+
Future<JSONObject> task2 = XML.toJSONObject(
75+
smallReader,
76+
jo -> {
77+
results[1] = jo;
78+
long endNano = System.nanoTime();
79+
timeElapsed[1] = TimeUnit.NANOSECONDS.toMillis(endNano - startTime);
80+
latch.countDown();
81+
},
82+
e -> {
83+
errors[1] = e;
84+
latch.countDown();
85+
}
86+
);
87+
88+
boolean completed = latch.await(10, TimeUnit.SECONDS);
89+
assertTrue("Both callbacks should complete within 10 seconds", completed);
90+
91+
assertNull("No error expected for medReader", errors[0]);
92+
assertNull("No error expected for smallReader", errors[1]);
93+
94+
JSONObject expectedMed = new JSONObject()
95+
.put("catalog", new JSONObject()
96+
.put("book", new JSONArray()
97+
.put(new JSONObject()
98+
.put("author", "Gambardella, Matthew")
99+
.put("title", "XML Developer's Guide")
100+
.put("genre", "Computer")
101+
.put("price", 44.95)
102+
.put("publish_date", "2000-10-01")
103+
.put("description", "An in-depth look at creating applications with XML.")
104+
.put("id", "bk101")
105+
)
106+
.put(new JSONObject()
107+
.put("author", "Ralls, Kim")
108+
.put("title", "Midnight Rain")
109+
.put("genre", "Fantasy")
110+
.put("price", 5.95)
111+
.put("publish_date", "2000-12-16")
112+
.put("description", "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.")
113+
.put("id", "bk102")
114+
)
115+
)
116+
);
117+
118+
JSONObject expectedSmall = new JSONObject()
119+
.put("root", new JSONObject()
120+
.put("message", "Hello")
121+
);
122+
123+
assertTrue("medReader JSON should match expected", expectedMed.similar(results[0]));
124+
assertTrue("smallReader JSON should match expected", expectedSmall.similar(results[1]));
125+
126+
assertTrue("smallReader must be faster than medReader", timeElapsed[1] < timeElapsed[0]);
127+
128+
// Optional debug output
129+
System.out.println("medReader elapsed: " + timeElapsed[0] + " ms");
130+
System.out.println("smallReader elapsed: " + timeElapsed[1] + " ms");
131+
}
132+
133+
@Test
134+
public void testAsyncParsingWithInvalidXML() throws Exception {
135+
CountDownLatch latch = new CountDownLatch(1);
136+
Exception[] errors = new Exception[1];
137+
138+
Reader invalidReader = new StringReader(
139+
"<root><tag>Unclosed tag</root>"
140+
);
141+
142+
Future<JSONObject> task = org.json.XML.toJSONObject(
143+
invalidReader,
144+
jo -> fail("Should not succeed with invalid XML"),
145+
e -> {
146+
errors[0] = e;
147+
latch.countDown();
148+
}
149+
);
150+
151+
boolean completed = latch.await(5, TimeUnit.SECONDS);
152+
assertTrue("Callback should complete within 5 seconds", completed);
153+
assertNotNull("Error should be captured for invalid XML", errors[0]);
154+
assertTrue("Error should be a JSONException", errors[0] instanceof org.json.JSONException);
155+
}
156+
157+
}

0 commit comments

Comments
 (0)