From 81f159d9045effe1f23de689538702fcfdbba120 Mon Sep 17 00:00:00 2001 From: wsw-stack <67959337+wsw-stack@users.noreply.github.com> Date: Mon, 2 Jun 2025 12:25:58 -0700 Subject: [PATCH 1/3] add milestone5 class --- src/main/java/org/json/XML.java | 156 ++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index a02ccf15d..6e100b8f6 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -10,6 +10,8 @@ import java.math.BigInteger; import java.util.*; import java.io.BufferedReader; +import java.util.concurrent.*; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -2237,4 +2239,158 @@ private static final String indent(int indent) { } return sb.toString(); } + +// public static class asyncRunner { +// private List> tasks; +// private boolean running; +// +// public asyncRunner() { +// this.tasks = new ArrayList<>(); +// running = false; +// } +// +// public void add(Future task) { +// this.tasks.add(task); +// } +// +// +// public void run() { +// running = true; +// while(running) { +// List> nextTasks = new ArrayList<>(); +// for(Future task: tasks) { +// if(!task.isDone()) { +// nextTasks.add(task); +// } +// } +// running = !nextTasks.isEmpty(); +// tasks = nextTasks; +// } +// } +// } +// +// public static Future toJSONObject(Reader reader, Consumer after, Consumer error){ +// +// ExecutorService executor = Executors.newSingleThreadExecutor(); +// futureTask task = new futureTask(reader, after, error); +// Future future = executor.submit(task); +// +// return future; +// } +// +// private static class futureTask implements Callable { +// +// Reader reader; +// Consumer after; +// Consumer error; +// public futureTask(Reader reader, Consumer after, Consumer error) { +// this.reader = reader; +// this.after = after; +// this.error = error; +// } +// +// @Override +// public JSONObject call() throws Exception { +// JSONObject jo = new JSONObject(); +// try { +// XMLTokener x = new XMLTokener(reader); +// while (x.more()) { +// x.skipPast("<"); +// if (x.more()) { +// parse(x, jo, null, XMLParserConfiguration.ORIGINAL, 0); +// } +// } +// after.accept(jo); +// } catch (Exception e) { +// error.accept(e); +// } +// return jo; +// } +// } +private static final ExecutorService executor = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors() +); + + public static Future toJSONObject( + Reader reader, + Consumer after, + Consumer error + ) { + FutureTask task = new FutureTask<>(new FutureTaskCallable(reader, after, error)); + executor.execute(task); + return task; + } + + public static void shutdownExecutor() { + executor.shutdown(); + } + + private static class FutureTaskCallable implements Callable { + private final Reader reader; + private final Consumer after; + private final Consumer error; + + public FutureTaskCallable( + Reader reader, + Consumer after, + Consumer error + ) { + this.reader = reader; + this.after = after; + this.error = error; + } + + @Override + public JSONObject call() throws Exception { + JSONObject jo = new JSONObject(); + try { + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parse(x, jo, null, XMLParserConfiguration.ORIGINAL, 0); + } + } + after.accept(jo); + return jo; + } catch (Exception e) { + error.accept(e); + throw e; + } + } + } + + public static class AsyncRunner { + private List> tasks = new ArrayList<>(); + + public AsyncRunner() { + } + + public void add(Future task) { + this.tasks.add(task); + } + + public void run() { + boolean running = true; + while (running) { + List> nextTasks = new ArrayList<>(); + for (Future task : tasks) { + if (!task.isDone()) { + nextTasks.add(task); + } + } + if (nextTasks.isEmpty()) { + running = false; + } else { + tasks = nextTasks; + try { + Thread.sleep(50); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + running = false; + } + } + } + } + } } From 08cf24389da2e55d58d747a43f2d06545732b34e Mon Sep 17 00:00:00 2001 From: jiachengzhuo <“richardjcheuk@gmail.com”> Date: Tue, 3 Jun 2025 00:44:55 -0700 Subject: [PATCH 2/3] Add async XML parsing tests for Milestone 5 --- .../junit/milestone5/JSONObjectAsyncTest.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/test/java/org/json/junit/milestone5/JSONObjectAsyncTest.java diff --git a/src/test/java/org/json/junit/milestone5/JSONObjectAsyncTest.java b/src/test/java/org/json/junit/milestone5/JSONObjectAsyncTest.java new file mode 100644 index 000000000..89df578f8 --- /dev/null +++ b/src/test/java/org/json/junit/milestone5/JSONObjectAsyncTest.java @@ -0,0 +1,157 @@ +package org.json.junit.milestone5; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.XML; +import org.junit.Test; + +import java.io.Reader; +import java.io.StringReader; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class JSONObjectAsyncTest { + + private final XML.AsyncRunner runner = new XML.AsyncRunner(); + + public JSONObjectAsyncTest() { + // Default constructor + } + + private final Reader medReader = new StringReader( + "" + + " " + + " Gambardella, Matthew" + + " XML Developer's Guide" + + " Computer" + + " 44.95" + + " 2000-10-01" + + " An in-depth look at creating applications with XML." + + " " + + " " + + " Ralls, Kim" + + " Midnight Rain" + + " Fantasy" + + " 5.95" + + " 2000-12-16" + + " A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world." + + " " + + "" + ); + + private final Reader smallReader = new StringReader( + "" + + " Hello" + + "" + ); + + @Test + public void testAsyncParsing() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + JSONObject[] results = new JSONObject[2]; + Exception[] errors = new Exception[2]; + long[] timeElapsed = new long[2]; + + final long startTime = System.nanoTime(); + + Future task1 = XML.toJSONObject( + medReader, + jo -> { + results[0] = jo; + long endNano = System.nanoTime(); + timeElapsed[0] = TimeUnit.NANOSECONDS.toMillis(endNano - startTime); + latch.countDown(); + }, + e -> { + errors[0] = e; + latch.countDown(); + } + ); + + Future task2 = XML.toJSONObject( + smallReader, + jo -> { + results[1] = jo; + long endNano = System.nanoTime(); + timeElapsed[1] = TimeUnit.NANOSECONDS.toMillis(endNano - startTime); + latch.countDown(); + }, + e -> { + errors[1] = e; + latch.countDown(); + } + ); + + boolean completed = latch.await(10, TimeUnit.SECONDS); + assertTrue("Both callbacks should complete within 10 seconds", completed); + + assertNull("No error expected for medReader", errors[0]); + assertNull("No error expected for smallReader", errors[1]); + + JSONObject expectedMed = new JSONObject() + .put("catalog", new JSONObject() + .put("book", new JSONArray() + .put(new JSONObject() + .put("author", "Gambardella, Matthew") + .put("title", "XML Developer's Guide") + .put("genre", "Computer") + .put("price", 44.95) + .put("publish_date", "2000-10-01") + .put("description", "An in-depth look at creating applications with XML.") + .put("id", "bk101") + ) + .put(new JSONObject() + .put("author", "Ralls, Kim") + .put("title", "Midnight Rain") + .put("genre", "Fantasy") + .put("price", 5.95) + .put("publish_date", "2000-12-16") + .put("description", "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.") + .put("id", "bk102") + ) + ) + ); + + JSONObject expectedSmall = new JSONObject() + .put("root", new JSONObject() + .put("message", "Hello") + ); + + assertTrue("medReader JSON should match expected", expectedMed.similar(results[0])); + assertTrue("smallReader JSON should match expected", expectedSmall.similar(results[1])); + + assertTrue("smallReader must be faster than medReader", timeElapsed[1] < timeElapsed[0]); + + // Optional debug output + System.out.println("medReader elapsed: " + timeElapsed[0] + " ms"); + System.out.println("smallReader elapsed: " + timeElapsed[1] + " ms"); + } + + @Test + public void testAsyncParsingWithInvalidXML() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + Exception[] errors = new Exception[1]; + + Reader invalidReader = new StringReader( + "Unclosed tag" + ); + + Future task = org.json.XML.toJSONObject( + invalidReader, + jo -> fail("Should not succeed with invalid XML"), + e -> { + errors[0] = e; + latch.countDown(); + } + ); + + boolean completed = latch.await(5, TimeUnit.SECONDS); + assertTrue("Callback should complete within 5 seconds", completed); + assertNotNull("Error should be captured for invalid XML", errors[0]); + assertTrue("Error should be a JSONException", errors[0] instanceof org.json.JSONException); + } + +} From 6d6f21a2184640d886affa389a490df877831c7a Mon Sep 17 00:00:00 2001 From: hai tong yan <1770115879@qq.com> Date: Tue, 3 Jun 2025 21:49:35 -0700 Subject: [PATCH 3/3] finalize milestone5 edit and readme --- README-M5.md | 17 ++++++ src/main/java/org/json/XML.java | 100 ++------------------------------ 2 files changed, 21 insertions(+), 96 deletions(-) create mode 100644 README-M5.md diff --git a/README-M5.md b/README-M5.md new file mode 100644 index 000000000..c069036f0 --- /dev/null +++ b/README-M5.md @@ -0,0 +1,17 @@ +# Milestone 5 – Async support for **JSONObject** + +## What is Added + +| Item | Description | +|------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Future XMLUtils.toJSONObject(Reader reader, Consumer after, Consumer 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`. | +| `AsyncRunner` | Tiny task aggregator. Call `add(Future task)` to collect jobs, then wait for them all (e.g. `forEach(Future::get)`). | +| `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(...)`). | + +Input: XML of different sizes (where they will be parsed concurrently) + +--- + +## Run the test class +```bash +mvn -Dtest=org.json.junit.milestone5.tests.JSONObjectAsyncTest test \ No newline at end of file diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 6e100b8f6..53ab77a62 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -2240,76 +2240,10 @@ private static final String indent(int indent) { return sb.toString(); } -// public static class asyncRunner { -// private List> tasks; -// private boolean running; -// -// public asyncRunner() { -// this.tasks = new ArrayList<>(); -// running = false; -// } -// -// public void add(Future task) { -// this.tasks.add(task); -// } -// -// -// public void run() { -// running = true; -// while(running) { -// List> nextTasks = new ArrayList<>(); -// for(Future task: tasks) { -// if(!task.isDone()) { -// nextTasks.add(task); -// } -// } -// running = !nextTasks.isEmpty(); -// tasks = nextTasks; -// } -// } -// } -// -// public static Future toJSONObject(Reader reader, Consumer after, Consumer error){ -// -// ExecutorService executor = Executors.newSingleThreadExecutor(); -// futureTask task = new futureTask(reader, after, error); -// Future future = executor.submit(task); -// -// return future; -// } -// -// private static class futureTask implements Callable { -// -// Reader reader; -// Consumer after; -// Consumer error; -// public futureTask(Reader reader, Consumer after, Consumer error) { -// this.reader = reader; -// this.after = after; -// this.error = error; -// } -// -// @Override -// public JSONObject call() throws Exception { -// JSONObject jo = new JSONObject(); -// try { -// XMLTokener x = new XMLTokener(reader); -// while (x.more()) { -// x.skipPast("<"); -// if (x.more()) { -// parse(x, jo, null, XMLParserConfiguration.ORIGINAL, 0); -// } -// } -// after.accept(jo); -// } catch (Exception e) { -// error.accept(e); -// } -// return jo; -// } -// } -private static final ExecutorService executor = Executors.newFixedThreadPool( - Runtime.getRuntime().availableProcessors() -); + // milestone 5 + private static final ExecutorService executor = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors() + ); public static Future toJSONObject( Reader reader, @@ -2321,10 +2255,6 @@ public static Future toJSONObject( return task; } - public static void shutdownExecutor() { - executor.shutdown(); - } - private static class FutureTaskCallable implements Callable { private final Reader reader; private final Consumer after; @@ -2370,27 +2300,5 @@ public void add(Future task) { this.tasks.add(task); } - public void run() { - boolean running = true; - while (running) { - List> nextTasks = new ArrayList<>(); - for (Future task : tasks) { - if (!task.isDone()) { - nextTasks.add(task); - } - } - if (nextTasks.isEmpty()) { - running = false; - } else { - tasks = nextTasks; - try { - Thread.sleep(50); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - running = false; - } - } - } - } } }