diff --git a/README.md b/README.md index 471bf439c..e912be602 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,7 @@ -![Json-Java logo](https://github.com/stleary/JSON-java/blob/master/images/JsonJava.png?raw=true) +[Milestone 2](README_M2.md) -image credit: Ismael Pérez Ortiz +[Milestone 3](README_M3.md) +[Milestone 4](README_M4.md) -JSON in Java [package org.json] -=============================== - -[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json) - -**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20211205/json-20211205.jar)** - - -# Overview - -[JSON](http://www.JSON.org/) is a light-weight language-independent data interchange format. - -The JSON-Java package is a reference implementation that demonstrates how to parse JSON documents into Java objects and how to generate new JSON documents from the Java classes. - -Project goals include: -* Reliable and consistent results -* Adherence to the JSON specification -* Easy to build, use, and include in other projects -* No external dependencies -* Fast execution and low memory footprint -* Maintain backward compatibility -* Designed and tested to use on Java versions 1.6 - 1.11 - -The files in this package implement JSON encoders and decoders. The package can also convert between JSON and XML, HTTP headers, Cookies, and CDL. - -The license includes this restriction: ["The software shall be used for good, not evil."](https://en.wikipedia.org/wiki/Douglas_Crockford#%22Good,_not_Evil%22) If your conscience cannot live with that, then choose a different package. - -# If you would like to contribute to this project - -For more information on contributions, please see [CONTRIBUTING.md](https://github.com/stleary/JSON-java/blob/master/docs/CONTRIBUTING.md) - -Bug fixes, code improvements, and unit test coverage changes are welcome! Because this project is currently in the maintenance phase, the kinds of changes that can be accepted are limited. For more information, please read the [FAQ](https://github.com/stleary/JSON-java/wiki/FAQ). - -# Build Instructions - -The org.json package can be built from the command line, Maven, and Gradle. The unit tests can be executed from Maven, Gradle, or individually in an IDE e.g. Eclipse. - -**Building from the command line** - -*Build the class files from the package root directory src/main/java* -```` -javac org/json/*.java -```` - -*Create the jar file in the current directory* -```` -jar cf json-java.jar org/json/*.class -```` - -*Compile a program that uses the jar (see example code below)* -```` -javac -cp .;json-java.jar Test.java (Windows) -javac -cp .:json-java.jar Test.java (Unix Systems) -```` - -*Test file contents* - -```` -import org.json.JSONObject; -public class Test { - public static void main(String args[]){ - JSONObject jo = new JSONObject("{ \"abc\" : \"def\" }"); - System.out.println(jo.toString()); - } -} -```` - -*Execute the Test file* -```` -java -cp .;json-java.jar Test (Windows) -java -cp .:json-java.jar Test (Unix Systems) -```` - -*Expected output* - -```` -{"abc":"def"} -```` - - -**Tools to build the package and execute the unit tests** - -Execute the test suite with Maven: -``` -mvn clean test -``` - -Execute the test suite with Gradlew: - -``` -gradlew clean build test -``` - -# Notes - -For more information, please see [NOTES.md](https://github.com/stleary/JSON-java/blob/master/docs/NOTES.md) - -# Files - -For more information on files, please see [FILES.md](https://github.com/stleary/JSON-java/blob/master/docs/FILES.md) - -# Release history: - -For the release history, please see [RELEASES.md](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md) +[Milestone 5](README_M5.md) diff --git a/README_M2.md b/README_M2.md new file mode 100644 index 000000000..1d2805136 --- /dev/null +++ b/README_M2.md @@ -0,0 +1,321 @@ +

Milestone 2

+ +

Summary of Support & Not Support

+ +

toJSONObject(Reader reader, JSONPointer path)

+ +

support path:

+ +``` +JSONObject: /clinical_study/required_header + +JSONArray: /clinical_study/condition_browse/mesh_term + +Index of JSONArray: /clinical_study/condition_browse/mesh_term/1 +``` + +

not support path:

+ +``` +Nested JSONArray: /clinical_study/1/condition_browse/mesh_term/1 +``` + +

toJSONObject(Reader reader, JSONPointer path, JSONObject replacement)

+ +

support path:

+ +``` +JSONObject: /clinical_study/required_header + +JSONArray: /clinical_study/condition_browse/mesh_term +``` + +

not support path:

+ +``` +Index of JSONArray: /clinical_study/condition_browse/mesh_term/1 +``` + +

List of Functions

+

org/json/XML.java:

+ + static Object toJSONObject(Reader reader, JSONPointer path) + static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) + static JSONObject getJsonObjectWithPath(Reader reader, JSONPointer path, JSONObject replace) + static boolean parseWithPath(XMLTokener x, JSONObject context, String name, + XMLParserConfiguration config, String[] paths, + int level, int pLevel, JSONObject replacement) + +

org/json/execption/JSONFoundExecption.java:

+ + JSONFoundExecption(Object jsonObject) + + +

Test Units (Junit)

+

org/json/junit/XMLMyTest.java:

+ + testToJSONWithReaderAndPointer1();//not using library + testToJSONWithReaderAndPointer2();//using library + //_testToJSONWithReaderAndPointer(boolean isInLibrary) + + testToJSONWithReaderAndPointerWithReplace1();//not using library + testToJSONWithReaderAndPointerWithReplace2();//using library + //_testToJSONWithReaderAndPointerWithReplace(boolean isInLibrary) + +

Test Result (Issue537.xml)

+ +

Task2 and task5 using library inside run less time than using library outside.

+ +``` +testToJSONWithReaderAndPointer1 Run time:8ms +testToJSONWithReaderAndPointer2 Run time:3ms +testToJSONWithReaderAndPointerWithReplace1 Run time:5ms +testToJSONWithReaderAndPointerWithReplace2 Run time:5ms +``` + + +

Part 1: Implementations functions

+ +

static JSONObject toJSONObject(Reader reader, JSONPointer path)

+ +``` + /** + * milestone 2 + * Read an XML file into a JSON object, and extract some smaller sub-object inside, + * given a certain path (use JSONPointer). + * @param reader + * The source string. + * @param path Configuration options for the parser. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static Object toJSONObject(Reader reader, JSONPointer path) { + try { + getJsonObjectWithPath(reader, path, null); + } catch (JSONFoundExecption e) { + System.out.println(e.getMessage()); + if (e.getCode().equals("200")) { + return e.getJsonObject(); + } + } + return null; + } +``` +``` + /* + * + * @param level: JSON level depth + * @param pLevel: path level depth + * @param replace: JSONObject + * */ + private static JSONObject getJsonObjectWithPath(Reader reader, JSONPointer path, JSONObject replace) throws JSONFoundExecption { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + String[] paths = path.toString().split("/"); + paths = Arrays.copyOfRange(paths, 1, paths.length); + + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parseWithPath(x, jo, null, XMLParserConfiguration.ORIGINAL, paths, 0, 0, replace); + } + } + return jo; + } +``` +``` + private static boolean parseWithPath(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, String[] paths, int level, int pLevel, JSONObject replacement) + throws JSONException, JSONFoundExecption{ + if (token == BANG) { + } else if (c == '[') { + } else if (token == QUEST) { + } else if (token == SLASH) { + } else if (token instanceof Character) { + } else { + + //add by Sunsheng Su + if(level == pLevel){ + if(paths[pLevel].equals(tagName)){ + if(pLevel < paths.length-1){ + pLevel++; + } + + //for toJSONObject with replacement + if(replacement != null && pLevel == level){ //paths.length -1 + x.skipPast(""); + //from professor Cristina test demo and responses to Justin, replacement has the last key of paths + context.put(tagName, replacement.get(tagName)); + return false; + } + } + else if(replacement == null){ + x.skipPast(""); + return false; + } + } + + for (;;){ + if (token == null) { + if (token instanceof String) { + } else if (token == SLASH) { + } else if (token == GT) { + for (;;) { + if (token == null) { + } else if (token instanceof String) { + } else if (token == LT) { + if (parseWithPath(x, jsonObject, tagName, config, paths, level+1, pLevel, replacement)) { + if (config.getForceList().contains(tagName)) { + } else { + } + + /*add by Sunsheng Su + pLevel == path.length-1 (no need) + pLevel not equal to level unless pLevel reach the last one + this block code only executed when tagName has been found totally, including children + */ + if(replacement == null && pLevel == paths.length-1 && level == pLevel-1){ + //for normal path, , like /a/b/c, this is b level and to get tagName c + if (paths[pLevel].split("[^0-9]").length != 1){ + throw new JSONFoundExecption( jsonObject.opt(paths[pLevel])); + } + //for path with index, like /a/b/c/1, this is c level, and accumulate until 1. + else{ + int index = Integer.parseInt(paths[pLevel]); + if(index == 0){ + throw new JSONFoundExecption( jsonObject.opt(config.getcDataTagName())); + } + else if(context.get(paths[pLevel-1]) instanceof JSONArray){ + if(context.getJSONArray(paths[pLevel-1]).length() == index+1) + throw new JSONFoundExecption( jsonObject.opt(config.getcDataTagName())); + } + } + } + return false; + } + } + } + } + } + } + } +``` + + +

static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement)

+ +``` + /** + * milestone 2 + * Read an XML file into a JSON object, replace a sub-object on a certain key path + * with another JSON object that you construct, + * + * @param reader + * The source string. + * @param path Configuration options for the parser. + * @param replacement Configuration options for the parser. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) { + + JSONObject jo = null; + try { + jo = getJsonObjectWithPath(reader, path, replacement); + } catch (JSONFoundExecption e) { + e.printStackTrace(); + } + return jo; + } +``` + +

Part 2: Test methods

+

We don't need to have a JSON docuemnt to work. This project also admits conversions from other type of files.

+

Secondly, we can also convert from JSON to those type of files.

+ +

Test toJSONObject(Reader reader, JSONPointer path)

+ +``` + /* + * static JSONObject toJSONObject(Reader reader, JSONPointer path) + */ + //not using library + @Test + public void testToJSONWithReaderAndPointer1() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointer(false); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointer1 Run time:" + (endTime - startTime) + "ms"); + } + //using library + @Test + public void testToJSONWithReaderAndPointer2() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointer(true); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointer2 Run time:" + (endTime - startTime) + "ms"); + } + + private void _testToJSONWithReaderAndPointer(boolean isInLibrary) throws IOException { + Object actual; + if (isInLibrary) { + //do query inside library + actual = XML.toJSONObject(xmlReader, path); + write2File(actual, "inLib"); + } else { + //do query outside library + actual = path.queryFrom(XML.toJSONObject(xmlReader)); + write2File(actual, "outLib"); + } +// System.out.println(actual == null ? actual : actual.toString()); + } +``` + + +

Test toJSONObject(Reader reader, JSONPointer path, JSONObject replacement)

+ +``` + /* + * static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) + */ + //not using library + @Test + public void testToJSONWithReaderAndPointerWithReplace1() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointerWithReplace(false); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointerWithReplace1 Run time:" + (endTime - startTime) + "ms"); + } + //using library + @Test + public void testToJSONWithReaderAndPointerWithReplace2() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointerWithReplace(true); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointerWithReplace2 Run time:" + (endTime - startTime) + "ms"); + } + + private void _testToJSONWithReaderAndPointerWithReplace(boolean isInLibrary) throws IOException { + Object actual; + if (isInLibrary) { + //do query inside library + actual = XML.toJSONObject(xmlReader, path, replacement); + write2File(actual, "replace_inLib"); + } else { + //do query outside library + String[] strs = path.toString().split("/"); + String keyName = strs[strs.length - 1]; + String parentPath = ""; + for(int i=1; iMilestone 3 + +Read an XML file into a JSON object, and add the prefix "swe262_" to all of its keys. +Do it by adding an overloaded static method to the XML class with the signature + +

Overload function "toJSONObject" and "parse".

+ +``` +public static JSONObject toJSONObject(Reader reader, Function keyTransformer); + +private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, Function keyTransformer); +``` + + +

Maven

+The project is based on Maven. +Since Milestone 3 uses Function as a parameter, it needs to adjust the jdk to 1.8. + +```xml + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + + + 1.8 + 1.8 + + +``` + +

Test Units (Junit)

+ +

Test two kind of key transformer: key replace and key reverse. And do a key replace in client code VS. inside library

+ +

Test file position: src/test/java/org/json/junit/XMLMyTestM3.java.

+ +``` +//replace key in client code +public void testToJSONKeyReplaceInClient(); + +//replace key in the library +public void testToJSONKeyReplaceWithKeyTransformer() throws IOException { + ... + Function func = cc-> { + return "swe262_"+cc; + }; + ... +} + +//key reverse in the library +public void testToJSONKeyReverseWithKeyTransformer() throws IOException { + ... + Function func = cc-> { + StringBuilder sb = new StringBuilder((String) cc); + sb.reverse(); + return sb.toString(); + }; + ... +} +``` + + +

Key replace in client VS. doing it inside the library

+ +| function \ file size | 8K | 250MB | 490MB | +| --- | --- | --- | --- | +| testToJSONKeyReplaceInClient | 18ms | 43,045ms | 103,838ms | +| testToJSONKeyReplaceWithKeyTransformer | 78ms | 33,132ms | 89,854ms | + +("testToJSONKeyReplaceInClient" does it in client code. +"testToJSONKeyReplaceWithKeyTransformer" does it in library.) + +We can see that for a small file, doing key replace in client code runs faster than doing it inside library. +But for big files, doing key replace inside library runs much faster than doing it in client code. + +I guess that passing a function of KeyTransformer and calling it may have additional calling consuming time. +For small files, this calling consuming time is significant. But for big files, it can be trivial. + + +

Implement

+ +To execute the task of key transformation, put the function of "keyTransformer" in three places. + +

1. deals with global tagName for most cases: put keyTransformer inside callback "parse" (tagName meet with pair end tagName).

+ +``` +if (parse(x, jsonObject, tagName, config, keyTransformer)) { + tagName = (String) keyTransformer.apply(tagName); + ... +} +``` + +

2. to deal with special xml format, like below

+ +```xml +September 12, 2019 +``` + +```json +{ + "swe262_last_update_posted": { + "swe262_type": "Actual", + "swe262_content": "September 12, 2019" + } +} +``` + +

2.1 When First token is a String, second token is also a String, and third token is a "=".

+ +Like below xml, first token is "last_update_posted", second token is "type", third token is "=" + +```xml +September 12, 2019 +``` + +If it comes across this case, it needs to put a keyTransformer here. + +``` +if (token instanceof String) { + string = (String) token; + //put keyTransformer + string = (String) keyTransformer.apply(string); + if (token == EQ) { + if (config.isConvertNilAttributeToNull() + } else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty() + } else if (!nilAttributeFound) { + jsonObject.accumulate(string, + config.isKeepStrings() + ? ((String) token) + : stringToValue((String) token)); + } + ... + } + ... +} +``` + +In this circumstance that it would have added "type" tag into "last_update_posted", like below: + +```json +{ + "swe262_last_update_posted": { + "swe262_type": "Actual" + } +} +``` + +

2.2 When it is going to add content of "September 12, 2019" directly to JsonObject "last_update_posted".

+ +Here is to deal with this kind of token tagName "content". + +``` +//add "if" predicate logic +if(jsonObject.length()>0){ + //put keyTransformer + String configTagName = (String) keyTransformer.apply(config.getcDataTagName()); + jsonObject.accumulate(configTagName, + config.isKeepStrings() ? string : stringToValue(string)); +} +else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); +} +``` + +By putting keyTransformer in these two places, it can successfully deal with this case and turn it into correct output. + +```json +{ + "swe262_last_update_posted": { + "swe262_type": "Actual", + "swe262_content": "September 12, 2019" + } +} +``` + diff --git a/README_M4.md b/README_M4.md new file mode 100644 index 000000000..04d491d36 --- /dev/null +++ b/README_M4.md @@ -0,0 +1,190 @@ +

Milestone 4

+ +Add streaming methods to the library that allow the client code to chain operations on JSON nodes. + +Streaming methods: src/main/java/org/json/JSONObject.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/main/java/org/json/JSONObject.java) + +Test Units Cases: src/test/java/org/json/junit/M4StreamTest.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/test/java/org/json/junit/M4StreamTest.java) + +Turn the JSONObject to the stream in the type of Map.Entry. +In this way, it keeps the original key of JSONObject and easily be filtered, sorted, and applied to other actions. + +For JSONArray in JSONObject, I add an index to each object in JSONArray as the key and add them into the stream. + +

Two ways of transforming JSONObject to stream

+ +

1. Recursive Stream

+ +```java +public Stream> stream(); +``` + +

2. Spliterator of Stream

+ +```java +public Stream> stream(); +public Spliterator> spliterator(); +static class JSONObjectSpliterator implements Spliterator>{ + public int characteristics(); + public long estimateSize(); + public boolean tryAdvance(Consumer> action); +} +``` + +

In my opinion, the first way is more abstract but the code is more elegant. +The second way is more comprehensive and helpful in understanding stream.

+ +

Maven

+The project is based on Maven. +Since Milestone 4 uses Stream, it needs to adjust the JDK to 1.8. + +```xml + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + + + 1.8 + 1.8 + + +``` + +

Test Units (Junit)

+ +Test Units Cases: src/test/java/org/json/junit/M4StreamTest.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/test/java/org/json/junit/M4StreamTest.java) + +

Test streams features:

+ +| methods | Params | +| --- | --- | +| forEach | System.out::println | +| filter | e.getKey() or e.getValue() | +| map | e.getKey().toUpperCase() or Map.Entry::getValue | +| collect | Collectors.toMap or Collectors.toList() | +| findFirst | | +| sorted | Comparator.comparing(Map.Entry::getKey) | + +

Test Unites Cases:

+ +```java +//JSONObject stream(): print key and value +@Test +public void testJSONObjectStreamForEach(); + + +//JSONObject stream(): filter and transform key to upper case +@Test +public void testJSONObjectStreamKeyToUpperCase(); + +//JSONObject stream() to Map +@Test +public void testJSONObjectStreamToMap(); + +//JSONObject stream(): +//(1) filter and get the first match value +//(2) filter value and get the first match key +@Test +public void testJSONStreamMatch(); + +//JSONObject stream(): filter key and turn values to list +@Test +public void testJSONStreamMatchToList() +``` + +

Implement

+ +Streaming methods: src/main/java/org/json/JSONObject.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/main/java/org/json/JSONObject.java) + +There are two ways to transform JSONObject into stream. + +

1. Recursive Stream

+ +```java +/* +* Recursive Stream +* */ +public Stream> stream(){ + Stream> resultingStream = null; + for(Map.Entry entry: map.entrySet()){ + //first, add new stuff to stream + if(resultingStream == null){ + resultingStream = Stream.of(entry); + } + else{ + resultingStream = Stream.concat(resultingStream, Stream.of(entry)); + } + //then, check whether to recursion + Object object = entry.getValue(); + if(object instanceof JSONObject){ + //recursive call + resultingStream = Stream.concat(resultingStream, ((JSONObject) object).stream()); + } + else if(object instanceof JSONArray){ + //transform a JSONArray to a JSONObject, then recursive call + JSONArray jsonArray = (JSONArray)(object); + JSONObject json = new JSONObject(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + json.put(i + "", jsonArray.opt(i)); + } + resultingStream = Stream.concat(resultingStream, json.stream()); + } + } + return resultingStream; +} +``` + +

2. Spliterator

+ +```java +static class JSONObjectSpliterator implements Spliterator> { + private final JSONObject root; + private JSONObject tree; + + public int characteristics() {...} + public long estimateSize() {...} + + @Override + public boolean tryAdvance(Consumer> action) { + JSONObject current = tree; + + for(Map.Entry entry : tree.entrySet()){ + action.accept(entry); + Object value = entry.getValue(); + if(value instanceof JSONObject){ + tree = (JSONObject) value; + tryAdvance(action); + } + else if(value instanceof JSONArray){ + //transform a JSONArray to a JSONObject, then recursive call + JSONArray jsonArray = (JSONArray)(value); + JSONObject json = new JSONObject(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + json.put(i + "", jsonArray.opt(i)); + } + tree = json; + tryAdvance(action); + } + } + + //Returns: false if no remaining elements existed upon entry to this method, else true. + tree = current; + + if (tree == root) + return false; + else + return true; + } +} + +``` diff --git a/README_M5.md b/README_M5.md new file mode 100644 index 000000000..14065e684 --- /dev/null +++ b/README_M5.md @@ -0,0 +1,128 @@ +

Milestone 5

+ +Add asynchronous methods to the library that allow the client code to proceed. + +Methods: src/main/java/org/json/XML.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/main/java/org/json/XML.java) + +CustomFuture: src/main/java/org/json/CustomFuture.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/main/java/org/json/CustomFuture.java) + +Test Units Cases: src/test/java/org/json/junit/XMLMyTestM5.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/test/java/org/json/junit/XMLMyTestM5.java) + +

Two ways of asynchronous implementation

+ +

1. Custom Future

+ +```java +public static CustomFuture toJSONObject(Reader reader, BiFunction fun, Function fail); +``` + +

2. Java Concurrent CompletableFuture

+ +```java +public static CompletableFuture toJSONObject(Reader reader, BiFunction fun, Function fail); +``` + +

Test Units (Junit)

+ +Test Units Cases: src/test/java/org/json/junit/XMLMyTestM5.java + +(https://github.com/JoshuaSunsheng/JSON-java/blob/master/src/test/java/org/json/junit/XMLMyTestM5.java) + +

Test functions:

+ +| test methods | description | +| --- | --- | +| testAsyncToJSON | read xml into JSONObject and store in file | +| testAsyncToJSONError | simulate failure case | + +

Implement

+ +There are two ways. + +

1. Custom Future

+ +```java +public class XML{ + /* + * Milestone 5 + * Add asynchronous methods to the library that allow the client code to proceed + * example: XML.toJSONObject(aReader, (JSONObject jo) -> {jo.write(aWriter);}, (Exception e) -> { something went wrong }); + * + * */ + static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);//LinkedBlockingQueue + public static CustomFuture toJSONObject(Reader reader, BiFunction fun, Function fail) throws ExecutionException, InterruptedException { + Function futureFun = (a) -> { + Object msg = null; + System.out.println("Async: " + Thread.currentThread().getName() + " is transforming xml to JSON in the background."); + try{ + JSONObject jsonObject = toJSONObject(reader, XMLParserConfiguration.ORIGINAL); + System.out.println("Async: xml to toJSONObject done"); + msg = fun.apply(jsonObject, "M5_inner"); + }catch (Exception e){ + System.out.println("Async: Exception happens!"); + msg = fail.apply(e); + }catch (Error e){ + System.out.println("Async: Error happens!"); + msg = fail.apply(e); + } + return msg; + }; + CustomFuture customFuture = new CustomFuture(futureFun); + executor.submit(customFuture); + return customFuture; + } +} + +public class CustomFuture implements Runnable { + boolean finished = false; + T result; + Function function; + + public CustomFuture(Function function) { + this.function = function; + } + + public T get() throws InterruptedException { + while (true) { + Thread.currentThread().sleep(50); + if (finished) break; + } + return result; + } + + @Override + public void run() { + result = function.apply(""); + finished = true; + } +} +``` + +

2. Java Concurrent CompletableFuture

+ +```java +public class XML{ + public static CompletableFuture toJSONObject(Reader reader, BiFunction fun, Function fail) throws ExecutionException, InterruptedException { + CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> { + String msg = null; + System.out.println("Async: " + Thread.currentThread().getName() + " is transforming xml to JSON in the background."); + try { + JSONObject jsonObject = toJSONObject(reader, XMLParserConfiguration.ORIGINAL); + System.out.println("Async: xml to toJSONObject done"); + msg = fun.apply(jsonObject, "M5_inner"); + } catch (Throwable e) { + System.out.println("Async: Something wrong happens!"); + msg = (String) fail.apply(e); + } + return msg; + }); + return completableFuture; + } +} +``` diff --git a/README_Origin.md b/README_Origin.md new file mode 100644 index 000000000..471bf439c --- /dev/null +++ b/README_Origin.md @@ -0,0 +1,109 @@ +![Json-Java logo](https://github.com/stleary/JSON-java/blob/master/images/JsonJava.png?raw=true) + +image credit: Ismael Pérez Ortiz + + +JSON in Java [package org.json] +=============================== + +[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json) + +**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20211205/json-20211205.jar)** + + +# Overview + +[JSON](http://www.JSON.org/) is a light-weight language-independent data interchange format. + +The JSON-Java package is a reference implementation that demonstrates how to parse JSON documents into Java objects and how to generate new JSON documents from the Java classes. + +Project goals include: +* Reliable and consistent results +* Adherence to the JSON specification +* Easy to build, use, and include in other projects +* No external dependencies +* Fast execution and low memory footprint +* Maintain backward compatibility +* Designed and tested to use on Java versions 1.6 - 1.11 + +The files in this package implement JSON encoders and decoders. The package can also convert between JSON and XML, HTTP headers, Cookies, and CDL. + +The license includes this restriction: ["The software shall be used for good, not evil."](https://en.wikipedia.org/wiki/Douglas_Crockford#%22Good,_not_Evil%22) If your conscience cannot live with that, then choose a different package. + +# If you would like to contribute to this project + +For more information on contributions, please see [CONTRIBUTING.md](https://github.com/stleary/JSON-java/blob/master/docs/CONTRIBUTING.md) + +Bug fixes, code improvements, and unit test coverage changes are welcome! Because this project is currently in the maintenance phase, the kinds of changes that can be accepted are limited. For more information, please read the [FAQ](https://github.com/stleary/JSON-java/wiki/FAQ). + +# Build Instructions + +The org.json package can be built from the command line, Maven, and Gradle. The unit tests can be executed from Maven, Gradle, or individually in an IDE e.g. Eclipse. + +**Building from the command line** + +*Build the class files from the package root directory src/main/java* +```` +javac org/json/*.java +```` + +*Create the jar file in the current directory* +```` +jar cf json-java.jar org/json/*.class +```` + +*Compile a program that uses the jar (see example code below)* +```` +javac -cp .;json-java.jar Test.java (Windows) +javac -cp .:json-java.jar Test.java (Unix Systems) +```` + +*Test file contents* + +```` +import org.json.JSONObject; +public class Test { + public static void main(String args[]){ + JSONObject jo = new JSONObject("{ \"abc\" : \"def\" }"); + System.out.println(jo.toString()); + } +} +```` + +*Execute the Test file* +```` +java -cp .;json-java.jar Test (Windows) +java -cp .:json-java.jar Test (Unix Systems) +```` + +*Expected output* + +```` +{"abc":"def"} +```` + + +**Tools to build the package and execute the unit tests** + +Execute the test suite with Maven: +``` +mvn clean test +``` + +Execute the test suite with Gradlew: + +``` +gradlew clean build test +``` + +# Notes + +For more information, please see [NOTES.md](https://github.com/stleary/JSON-java/blob/master/docs/NOTES.md) + +# Files + +For more information on files, please see [FILES.md](https://github.com/stleary/JSON-java/blob/master/docs/FILES.md) + +# Release history: + +For the release history, please see [RELEASES.md](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md) diff --git a/pom.xml b/pom.xml index fac7a2ea6..4cdc0926d 100644 --- a/pom.xml +++ b/pom.xml @@ -118,8 +118,10 @@ maven-compiler-plugin 2.3.2 - 1.6 - 1.6 + + + 1.8 + 1.8 diff --git a/src/main/java/org/json/CustomFuture.java b/src/main/java/org/json/CustomFuture.java new file mode 100644 index 000000000..76cd7d00c --- /dev/null +++ b/src/main/java/org/json/CustomFuture.java @@ -0,0 +1,26 @@ +package org.json; +import java.util.function.Function; + +public class CustomFuture implements Runnable { + boolean finished = false; + T result; + Function function; + + public CustomFuture(Function function) { + this.function = function; + } + + public T get() throws InterruptedException { + while (true) { + Thread.currentThread().sleep(50); + if (finished) break; + } + return result; + } + + @Override + public void run() { + result = function.apply(""); + finished = true; + } +} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 99a075069..ba435c54e 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -36,18 +36,12 @@ of this software and associated documentation files (the "Software"), to deal import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.ResourceBundle; -import java.util.Set; +import java.util.function.Consumer; import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * A JSONObject is an unordered collection of name/value pairs. Its external @@ -1169,7 +1163,7 @@ public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) { return objectToBigDecimal(val, defaultValue, true); } - + /** * @param val value to convert * @param defaultValue default value to return is the conversion doesn't work or is null. @@ -1552,12 +1546,12 @@ && isValidMethodName(method.getName())) { final Object result = method.invoke(bean); if (result != null) { // check cyclic dependency and throw error if needed - // the wrap and populateMap combination method is + // the wrap and populateMap combination method is // itself DFS recursive if (objectsRecord.contains(result)) { throw recursivelyDefinedObjectException(key); } - + objectsRecord.add(result); this.map.put(key, wrap(result, objectsRecord)); @@ -1566,7 +1560,7 @@ && isValidMethodName(method.getName())) { // we don't use the result anywhere outside of wrap // if it's a resource we should be sure to close it - // after calling toString + // after calling toString if (result instanceof Closeable) { try { ((Closeable) result).close(); @@ -2722,4 +2716,112 @@ private static JSONException recursivelyDefinedObjectException(String key) { "JavaBean object contains recursively defined member variable of key " + quote(key) ); } + + /* + * 1. Recursive Stream + * */ + public Stream> stream(){ + Stream> resultingStream = null; + for(Map.Entry entry: map.entrySet()){ + //first, add new stuff to stream + if(resultingStream == null){ + resultingStream = Stream.of(entry); + } + else{ + resultingStream = Stream.concat(resultingStream, Stream.of(entry)); + } + //then, check whether to recursion + Object object = entry.getValue(); + if(object instanceof JSONObject){ + //recursive call + resultingStream = Stream.concat(resultingStream, ((JSONObject) object).stream()); + } + else if(object instanceof JSONArray){ + //transform a JSONArray to a JSONObject, then recursive call + JSONArray jsonArray = (JSONArray)(object); + JSONObject json = new JSONObject(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + json.put(i + "", jsonArray.opt(i)); + } + resultingStream = Stream.concat(resultingStream, json.stream()); + } + } + return resultingStream; + } + + + /* + * 2. Spliterator of Stream + * */ + /* + + //Spliterator of Stream + public Stream> stream() { + return StreamSupport.stream(this.spliterator(), false); + } + + public Spliterator> spliterator() { + return new JSONObjectSpliterator(this); + } + + //Spliterator of Stream + static class JSONObjectSpliterator implements Spliterator> { + private final JSONObject root; + private JSONObject tree; + + JSONObjectSpliterator(JSONObject t) { + root = tree = t; + } + + @Override + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + //Returns: false if no remaining elements existed upon entry to this method, else true. + @Override + public boolean tryAdvance(Consumer> action) { + JSONObject current = tree; + + for(Map.Entry entry : tree.entrySet()){ + action.accept(entry); + Object value = entry.getValue(); + if(value instanceof JSONObject){ + tree = (JSONObject) value; + tryAdvance(action); + } + else if(value instanceof JSONArray){ + //transform a JSONArray to a JSONObject, then recursive call + JSONArray jsonArray = (JSONArray)(value); + JSONObject json = new JSONObject(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + json.put(i + "", jsonArray.opt(i)); + } + tree = json; + tryAdvance(action); + } + } + + tree = current; + + if (tree == root) + return false; + else + return true; + } + + @Override + public Spliterator> trySplit() { + return null; + } + } + + */ } + + diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 9b2ba8939..dd01ca3dc 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -24,12 +24,17 @@ of this software and associated documentation files (the "Software"), to deal SOFTWARE. */ +import org.json.execption.JSONFoundExecption; + import java.io.Reader; import java.io.StringReader; -import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; import java.util.Iterator; +import java.util.concurrent.*; +import java.util.function.BiFunction; +import java.util.function.Function; /** @@ -119,7 +124,7 @@ public void remove() { /** * Replace special characters with XML escapes: * - *
{@code 
+     * 
{@code
      * & (ampersand) is replaced by &amp;
      * < (less than) is replaced by &lt;
      * > (greater than) is replaced by &gt;
@@ -281,6 +286,8 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
             c = x.next();
             if (c == '-') {
                 if (x.next() == '-') {
+                    //denoting this line is annotation
+                    //ignore 
                     x.skipPast("-->");
                     return false;
                 }
@@ -335,7 +342,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
             throw x.syntaxError("Misshaped tag");
 
             // Open tag <
-
+            //if token is tagName
         } else {
             tagName = (String) token;
             token = null;
@@ -348,6 +355,8 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
                 }
                 // attribute = value
                 if (token instanceof String) {
+                    //why two consecutive String, then =
+                    //maybe is loop cause, last loop
                     string = (String) token;
                     token = x.nextToken();
                     if (token == EQ) {
@@ -385,6 +394,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
                         if (nilAttributeFound) {
                             context.append(tagName, JSONObject.NULL);
                         } else if (jsonObject.length() > 0) {
+                            //in loop
                             context.append(tagName, jsonObject);
                         } else {
                             context.put(tagName, new JSONArray());
@@ -402,7 +412,10 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
 
                 } else if (token == GT) {
                     // Content, between <...> and 
+                    // after a String, if it is >, then analysis inner content
                     for (;;) {
+                        //loop goal: first time temporally save String, second time put into parent JSONObject
+                        //if has nested element, do recursion
                         token = x.nextContent();
                         if (token == null) {
                             if (tagName != null) {
@@ -423,6 +436,9 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
 
                         } else if (token == LT) {
                             // Nested element
+                            // new <
+                            //when subStruct come across opposite one, subStruct's recursion return true to subStruct
+                            //when subStruct has been accumulated, return false to parentis done
                             if (parse(x, jsonObject, tagName, config)) {
                                 if (config.getForceList().contains(tagName)) {
                                     // Force the value to be an array
@@ -436,15 +452,308 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
                                     }
                                 } else {
                                     if (jsonObject.length() == 0) {
+                                        //let values of same tagName, save as JSONArray
+                                        context.accumulate(tagName, "");
+                                    } else if (jsonObject.length() == 1
+                                            && jsonObject.opt(config.getcDataTagName()) != null) {
+                                        //enter into subStruct and then return true, come here when subStruct is value
+                                        //config.getcDataTagName():"content" is temporally saving key
+                                        context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+                                    } else {
+                                        //when subStruct has nested element like 
111Ave
+ context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + + + + /** + * Scan the content following the named tag, attaching it to the context. + * + * @param x + * The XMLTokener containing the source string. + * @param context + * The JSONObject that will include the new material. + * @param name + * The tag name. + * @param paths + * paths of JSONPointer + * @param level + * depth level of xml/JSONObject + * @param pLevel + * depth level of paths + * @param replacement for Milestone 2 + * JSONObject that is going to replace the path's object + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parseWithPath(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, String[] paths, int level, int pLevel, JSONObject replacement) + throws JSONException, JSONFoundExecption { + char c; + int i; + JSONObject jsonObject = null; + String string; + String tagName; + Object token; + XMLXsiTypeConverter xmlXsiTypeConverter; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + // + x.skipPast("-->"); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate(config.getcDataTagName(), string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag "); + //from professor Cristina test demo and responses to Justin, replacement has the last key of paths + context.put(tagName, replacement.get(tagName)); + return false; + } + } + //for toJSONObject with extract object, skip other tagNames + else if(replacement == null){ + x.skipPast(""); + return false; + } + } + + for (;;) { + if (token == null) { + token = x.nextToken(); + } + // attribute = value + if (token instanceof String) { + //for this xml + //September 12, 2019 + string = (String) token; + token = x.nextToken(); + if (token == EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + + if (config.isConvertNilAttributeToNull() + && NULL_ATTR.equals(string) + && Boolean.parseBoolean((String) token)) { + nilAttributeFound = true; + } else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty() + && TYPE_ATTR.equals(string)) { + xmlXsiTypeConverter = config.getXsiTypeMap().get(token); + } else if (!nilAttributeFound) { + jsonObject.accumulate(string, + config.isKeepStrings() + ? ((String) token) + : stringToValue((String) token)); + } + token = null; + } else { + jsonObject.accumulate(string, ""); + } + + } else if (token == SLASH) { + // Empty tag <.../> + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (nilAttributeFound) { + context.append(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + //in loop + context.append(tagName, jsonObject); + } else { + context.put(tagName, new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(tagName, jsonObject); + } else { + context.accumulate(tagName, ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + // after a String, if it is >, then analysis inner content + for (;;) { + //loop goal: first time temporally save String, second time put into parent JSONObject + //if has nested element, do recursion + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + if(xmlXsiTypeConverter != null) { + jsonObject.accumulate(config.getcDataTagName(), + stringToValue(string, xmlXsiTypeConverter)); + } else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); + } + } + + } else if (token == LT) { + // still has <, denoting Nested element + // new < + //when subStruct come across opposite one, subStruct's recursion return true to subStruct. So, the tagName's content will be add + //when subStruct has been accumulated, return false to calling position + //address -> "<" -> street -> "<" -> /street -> true to add street into JSONObject -> false -> address + //address -> "<" -> zipcode -> "<" -> /zipcode -> true to add zipcode into JSONObject -> false -> address + //address -> "<" -> /address -> true to add address(JSONObject[street,zipcode]) to JSONObject(upper level) + + if (parseWithPath(x, jsonObject, tagName, config, paths, level+1, pLevel, replacement)) { + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.append(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.append(tagName, jsonObject); + } + } else { + if (jsonObject.length() == 0) { + //let values of same tagName, save as JSONArray context.accumulate(tagName, ""); } else if (jsonObject.length() == 1 && jsonObject.opt(config.getcDataTagName()) != null) { + //todo why jsonObject.length == 1 1:array reason 2:same name with content + //enter into subStruct and then return true, come here when subStruct is value + //config.getcDataTagName(): key:"content" is temporally saved with String context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); } else { + //when subStruct has nested element like
111Ave
context.accumulate(tagName, jsonObject); } } - + + //add by Sunsheng Su + //pLevel == path.length-1 (no need) + //pLevel not equal to level unless pLevel reach the last one + //this block code only executed when tagName has been found totally, including children + if(replacement == null && pLevel == paths.length-1 && level == pLevel-1){ + //for normal path, , like /a/b/c, this is b level and to get tagName c + if (paths[pLevel].split("[^0-9]").length != 1){ + throw new JSONFoundExecption( jsonObject.opt(paths[pLevel])); + } + //for path with index, like /a/b/c/1, this is c level, and accumulate until 1. + else{ + int index = Integer.parseInt(paths[pLevel]); + if(index == 0){ + throw new JSONFoundExecption( jsonObject.opt(config.getcDataTagName())); + } + else if(context.get(paths[pLevel-1]) instanceof JSONArray){ + if(context.getJSONArray(paths[pLevel-1]).length() == index+1) + throw new JSONFoundExecption( jsonObject.opt(config.getcDataTagName())); + } + } + } return false; } } @@ -508,7 +817,7 @@ public static Object stringToValue(String string) { } return string; } - + /** * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support. */ @@ -555,7 +864,7 @@ private static Number stringToNumber(final String val) throws NumberFormatExcept // integer representation. // This will narrow any values to the smallest reasonable Object representation // (Integer, Long, or BigInteger) - + // BigInteger down conversion: We use a similar bitLength compare as // BigInteger#intValueExact uses. Increases GC, but objects hold // only what they need. i.e. Less runtime overhead if the value is @@ -571,7 +880,7 @@ private static Number stringToNumber(final String val) throws NumberFormatExcept } throw new NumberFormatException("val ["+val+"] is not a valid number."); } - + /** * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support. */ @@ -589,7 +898,7 @@ private static boolean isDecimalNotation(final String val) { * name/value pairs and arrays of values. JSON does not does not like to * distinguish between elements and attributes. Sequences of similar * elements are represented as JSONArrays. Content text may be placed in a - * "content" member. Comments, prologs, DTDs, and
{@code 
+     * "content" member. Comments, prologs, DTDs, and 
{@code
      * <[ [ ]]>}
* are ignored. * @@ -610,7 +919,7 @@ public static JSONObject toJSONObject(String string) throws JSONException { * name/value pairs and arrays of values. JSON does not does not like to * distinguish between elements and attributes. Sequences of similar * elements are represented as JSONArrays. Content text may be placed in a - * "content" member. Comments, prologs, DTDs, and
{@code 
+     * "content" member. Comments, prologs, DTDs, and 
{@code
      * <[ [ ]]>}
* are ignored. * @@ -690,7 +999,7 @@ public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration conf * name/value pairs and arrays of values. JSON does not does not like to * distinguish between elements and attributes. Sequences of similar * elements are represented as JSONArrays. Content text may be placed in a - * "content" member. Comments, prologs, DTDs, and
{@code 
+     * "content" member. Comments, prologs, DTDs, and 
{@code
      * <[ [ ]]>}
* are ignored. * @@ -716,7 +1025,7 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws * name/value pairs and arrays of values. JSON does not does not like to * distinguish between elements and attributes. Sequences of similar * elements are represented as JSONArrays. Content text may be placed in a - * "content" member. Comments, prologs, DTDs, and
{@code 
+     * "content" member. Comments, prologs, DTDs, and 
{@code
      * <[ [ ]]>}
* are ignored. * @@ -733,6 +1042,74 @@ public static JSONObject toJSONObject(String string, XMLParserConfiguration conf return toJSONObject(new StringReader(string), config); } + + /** + * milestone 2 + * Read an XML file into a JSON object, and extract some smaller sub-object inside, + * given a certain path (use JSONPointer). + * @param reader + * The source string. + * @param path Configuration options for the parser. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static Object toJSONObject(Reader reader, JSONPointer path) { + try { + getJsonObjectWithPath(reader, path, null); + } catch (JSONFoundExecption e) { +// System.out.println(e.getMessage()); + if (e.getCode().equals("200")) { + return e.getJsonObject(); + } + } + return null; + } + + /* + * + * @param level: JSON level depth + * @param pLevel: path level depth + * @param replace: JSONObject for Milestone 2 + * */ + private static JSONObject getJsonObjectWithPath(Reader reader, JSONPointer path, JSONObject replace) throws JSONFoundExecption { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + String[] paths = path.toString().split("/"); + paths = Arrays.copyOfRange(paths, 1, paths.length); + + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parseWithPath(x, jo, null, XMLParserConfiguration.ORIGINAL, paths, 0, 0, replace); + } + } + return jo; + } + + /** + * milestone 2 + * Read an XML file into a JSON object, replace a sub-object on a certain key path + * with another JSON object that you construct, + * + * @param reader + * The source string. + * @param path Configuration options for the parser. + * @param replacement Configuration options for the parser. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) { + + JSONObject jo = null; + try { + jo = getJsonObjectWithPath(reader, path, replacement); + } catch (JSONFoundExecption e) { + e.printStackTrace(); + } + return jo; + } + + /** * Convert a JSONObject into a well-formed, element-normal XML string. * @@ -881,4 +1258,344 @@ public static String toString(final Object object, final String tagName, final X + ">" + string + ""; } + + + /** + * Milestone 3 + * + * reload original parse function and add a new parameter "Function keyTransformer" + * + * Scan the content following the named tag, attaching it to the context. + * + * @param x + * The XMLTokener containing the source string. + * @param context + * The JSONObject that will include the new material. + * @param name + * The tag name. + * @param keyTransformer + * key to new key + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, Function keyTransformer) + throws JSONException { + char c; + int i; + JSONObject jsonObject = null; + String string; + String tagName; + Object token; + XMLXsiTypeConverter xmlXsiTypeConverter; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + // + x.skipPast("-->"); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate(config.getcDataTagName(), string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag September 12, 2019 + //add type to Jsonobject + //"last_update_posted": { + // "type": "Actual", + // "content": "September 12, 2019" + // }, + string = (String) token; + string = (String) keyTransformer.apply(string); + + token = x.nextToken(); + if (token == EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + + if (config.isConvertNilAttributeToNull() + && NULL_ATTR.equals(string) + && Boolean.parseBoolean((String) token)) { + nilAttributeFound = true; + } else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty() + && TYPE_ATTR.equals(string)) { + xmlXsiTypeConverter = config.getXsiTypeMap().get(token); + } else if (!nilAttributeFound) { + jsonObject.accumulate(string, + config.isKeepStrings() + ? ((String) token) + : stringToValue((String) token)); + } + token = null; + } else { + jsonObject.accumulate(string, ""); + } + + + } else if (token == SLASH) { + // Empty tag <.../> + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (nilAttributeFound) { + context.append(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + //in loop + context.append(tagName, jsonObject); + } else { + context.put(tagName, new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(tagName, jsonObject); + } else { + context.accumulate(tagName, ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + // after a String, if it is >, then analysis inner content + for (;;) { + //loop goal: first time temporally save String, second time put into parent JSONObject + //if has nested element, do recursion + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + if(xmlXsiTypeConverter != null) { + jsonObject.accumulate(config.getcDataTagName(), + stringToValue(string, xmlXsiTypeConverter)); + } else { + if(jsonObject.length()>0){ + //For Milestone 3 + //here to deal with real content tagName, not configTagName + //September 12, 2019 + //"last_update_posted": { + // "type": "Actual", + // "content": "September 12, 2019" + // }, + String configTagName = (String) keyTransformer.apply(config.getcDataTagName()); + jsonObject.accumulate(configTagName, + config.isKeepStrings() ? string : stringToValue(string)); + } + else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); + } + } + } + + } else if (token == LT) { + // Nested element + // new < + //when subStruct come across opposite one, subStruct's recursion return true to subStruct + //when subStruct has been accumulated, return false to parentis done + if (parse(x, jsonObject, tagName, config, keyTransformer)) { + //For Milestone 3 + //deal with global tagName for most cases + tagName = (String) keyTransformer.apply(tagName); + + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.append(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.append(tagName, jsonObject); + } + } else { + if (jsonObject.length() == 0) { + //let values of same tagName, save as JSONArray + context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + //enter into subStruct and then return true, come here when subStruct is value + //config.getcDataTagName():"content" is temporally saving key + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + //when subStruct has nested element like
111Ave
+ context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + /* + * Milestone 3 + * Read an XML file into a JSON object, and add the prefix "swe262_" to all of its keys. + * + * YOURTYPEHERE should be a function (or "functional" in Java) + * that takes as input a String denoting a key and returns another String that is the transformation of the key + * + * functions provided by the client code, so they can be quite powerful + * and include all sorts of string matching and transformation logic. + * + * The goal here is that you do the transformation during the parsing of the XML file, not in another pass afterwards. + * "foo" --> "swe262_foo" + * "foo" --> "oof" + * + * https://canvas.eee.uci.edu/courses/42906/pages/project + * */ + public static JSONObject toJSONObject(Reader reader, Function keyTransformer){ + keyTransformer.apply("b"); + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parse(x, jo, null, XMLParserConfiguration.ORIGINAL, keyTransformer); + } + } + + return jo; + } + + /* + * Milestone 5 + * Add asynchronous methods to the library that allow the client code to proceed + * example: XML.toJSONObject(aReader, (JSONObject jo) -> {jo.write(aWriter);}, (Exception e) -> { something went wrong }); + * + * */ + static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);//LinkedBlockingQueue + public static CustomFuture toJSONObject(Reader reader, BiFunction fun, Function fail) throws ExecutionException, InterruptedException { + Function futureFun = (a) -> { + Object msg = null; + System.out.println("Async: " + Thread.currentThread().getName() + " is transforming xml to JSON in the background."); + try{ + JSONObject jsonObject = toJSONObject(reader, XMLParserConfiguration.ORIGINAL); + System.out.println("Async: xml to toJSONObject done"); + msg = fun.apply(jsonObject, "M5_inner"); + }catch (Exception e){ + System.out.println("Async: Exception happens!"); + msg = fail.apply(e); + }catch (Error e){ + System.out.println("Async: Error happens!"); + msg = fail.apply(e); + } + return msg; + }; + CustomFuture customFuture = new CustomFuture(futureFun); + executor.submit(customFuture); + return customFuture; + } + +// public static CompletableFuture toJSONObject(Reader reader, BiFunction fun, Function fail) throws ExecutionException, InterruptedException { +// CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> { +// String msg = null; +// System.out.println("Async: " + Thread.currentThread().getName() + " is transforming xml to JSON in the background."); +// try { +// JSONObject jsonObject = toJSONObject(reader, XMLParserConfiguration.ORIGINAL); +// System.out.println("Async: xml to toJSONObject done"); +// msg = fun.apply(jsonObject, "M5_inner"); +// } catch (Throwable e) { +// System.out.println("Async: Something wrong happens!"); +// msg = (String) fail.apply(e); +// } +// return msg; +// }); +// return completableFuture; +// } + } diff --git a/src/main/java/org/json/execption/JSONFoundExecption.java b/src/main/java/org/json/execption/JSONFoundExecption.java new file mode 100644 index 000000000..47d503ab1 --- /dev/null +++ b/src/main/java/org/json/execption/JSONFoundExecption.java @@ -0,0 +1,55 @@ +package org.json.execption; + +import org.json.JSONObject; + +/** + * @program: JSON-java + * @description: + * @author: Mr. Su + * @create: 2022-01-22 15:33 + **/ + +public class JSONFoundExecption extends Exception { + private String code; + + private Object jsonObject; + + + + public JSONFoundExecption(String code, String message) { + super(message); + this.setCode(code); + } + + public JSONFoundExecption(String code, String message, Throwable cause) { + super(message, cause); + this.setCode(code); + } + public JSONFoundExecption(Object jsonObject) { + super("success"); + if(jsonObject==null){ + this.setCode("404"); + } + else{ + this.setCode("200"); + } + this.setJsonObject(jsonObject); + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Object getJsonObject() { + return jsonObject; + } + + public void setJsonObject(Object jsonObject) { + this.jsonObject = jsonObject; + } + +} diff --git a/src/test/java/org/json/junit/M4StreamTest.java b/src/test/java/org/json/junit/M4StreamTest.java new file mode 100644 index 000000000..755195629 --- /dev/null +++ b/src/test/java/org/json/junit/M4StreamTest.java @@ -0,0 +1,136 @@ +package org.json.junit; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @program: JSON-java + * @description: + * @author: Mr. Su + * @create: 2022-02-18 11:15 + **/ + +public class M4StreamTest { + JSONObject jsonObject = null; + @Before + public void loadJSONObject(){ + jsonObject = new JSONObject("{\"menu\": {\n" + + " \"id\": \"file\",\n" + + " \"value\": \"File\",\n" + + " \"popup\": {\n" + + " \"menuitem\": [\n" + + " {\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n" + + " {\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n" + + " {\"value\": \"Submit\", \"onclick\": \"SubmitDoc()\"},\n" + + " {\"value\": \"Delete\", \"onclick\": \"DeleteDoc()\"},\n" + + " {\"value\": \"Revise\", \"onclick\": \"ReviseDoc()\"},\n" + + " {\"value\": \"Confirm\", \"onclick\": \"ConfirmDoc()\"},\n" + + " {\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n" + + " ]\n" + + " }\n" + + "}}"); + } + + /* + * JSONObject stream(): print key and value + * */ + @Test + public void testJSONObjectStreamForEach(){ + System.out.println("------------------------"); + System.out.println("JSONObject stream: print out key & value"); + jsonObject.stream() + .forEach(System.out::println); + } + + /* + * JSONObject stream(): filter and transform key to upper case + * */ + @Test + public void testJSONObjectStreamKeyToUpperCase(){ + System.out.println("------------------------"); + System.out.println("JSONObject stream: filter and transform key to upper case"); + jsonObject.stream() + .filter(e -> e.getValue() instanceof String) + .map(e->{ + return new AbstractMap.SimpleEntry<>(e.getKey().toUpperCase(), e.getValue()); + }) + .forEach(e->{ + System.out.println(e.getKey() + " : " + e.getValue()); + }); + } + + /* + * JSONObject stream() to Map + * */ + @Test + public void testJSONObjectStreamToMap(){ + System.out.println("------------------------"); + System.out.println("JSONObject stream: ToMap"); + Map map = jsonObject.stream() + .collect(Collectors.toMap(e -> e.getKey().toUpperCase(), e -> e.getValue(), (x1, x2) -> x1)); + map.entrySet().stream() + .forEach(System.out::println); + } + + /* + * JSONObject stream(): + * (1) filter key and get the first match value + * (2) filter value and get the first match key + * */ + @Test + public void testJSONStreamMatch(){ + System.out.println("------------------------"); + System.out.println("JSONObject stream: Retrieving the first Match by key \"onclick\""); + //match key + Optional firstValue = jsonObject.stream() + .filter(e -> "onclick".equals(e.getKey())) + .map(Map.Entry::getValue) + .findFirst(); + System.out.println(firstValue.get()); + + System.out.println("------------------------"); + System.out.println("JSONObject stream: Retrieving the first Match by value of \"CreateNewDoc()\""); + //match value + Optional firstKey = jsonObject.stream() + .filter(e -> "CreateNewDoc()".equals(e.getValue())) + .map(Map.Entry::getKey) + .findFirst(); + System.out.println(firstKey.get()); + } + + /* + * JSONObject stream(): filter key and turn values to list + * */ + @Test + public void testJSONStreamMatchToList(){ + + System.out.println("------------------------"); + System.out.println("JSONObject stream: filter key and turn values to list"); + //Retrieving Multiple Results + List buttons = jsonObject.stream() + .filter(e->"onclick".equals(e.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + for(Object button : buttons){ + System.out.println(button); + } + } + + /* + * JSONObject stream(): filter and sort by key + * */ + @Test + public void testJSONStreamSortByKey(){ + System.out.println("------------------------"); + System.out.println("JSONObject stream: filter and sort by key"); + //sort + jsonObject.stream() + .filter(e->e.getValue() instanceof String) + .sorted(Comparator.comparing(Map.Entry::getKey)) + .forEach(System.out::println); + } +} diff --git a/src/test/java/org/json/junit/XMLMyTest.java b/src/test/java/org/json/junit/XMLMyTest.java new file mode 100644 index 000000000..1149bfb8b --- /dev/null +++ b/src/test/java/org/json/junit/XMLMyTest.java @@ -0,0 +1,139 @@ +package org.json.junit; + +import org.json.JSONObject; +import org.json.JSONPointer; +import org.json.XML; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.*; +import java.util.Arrays; + +import static org.junit.Assert.fail; + +/** + * @program: JSON-java + * @description: + * @author: Mr. Su + * @create: 2022-01-25 11:26 + **/ + +public class XMLMyTest { + JSONPointer path; + Reader xmlReader; + String xmlFileName; + InputStream xmlStream = null; + JSONObject replacement; + @Before + public void load() { + + xmlFileName = "Issue537.xml"; + replacement = XML.toJSONObject("testContent"); + +// JSONPointer path = new JSONPointer("/clinical_study/required_header/download_date");//String + JSONPointer path = new JSONPointer("/clinical_study/required_header");//JSONObject +// JSONPointer path = new JSONPointer("/clinical_study/condition_browse/mesh_term"); //mesh_term is JSONArray +// JSONPointer path = new JSONPointer("/clinical_study/condition_browse/mesh_term/1"); +// JSONPointer path = new JSONPointer("/clinical_study/condition_browse/mesh_term1"); //no exist +// JSONPointer path = new JSONPointer("/condition_browse/mesh_term"); //no exist +// JSONPointer path = new JSONPointer("/clinical_study/condition_browse1/mesh_term"); //no exist +// JSONPointer path = new JSONPointer("/clinical_study/condition_browse/mesh_term/3"); //out of array + this.path = path; + xmlStream = XMLMyTest.class.getClassLoader().getResourceAsStream(xmlFileName); + this.xmlReader = new InputStreamReader(xmlStream); + + } + + @After + public void prepare() throws IOException { + xmlStream.close(); + } + + /* + * static JSONObject toJSONObject(Reader reader, JSONPointer path) + */ + //not using library + @Test + public void testToJSONWithReaderAndPointer1() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointer(false); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointer1 Run time:" + (endTime - startTime) + "ms"); + } + //using library + @Test + public void testToJSONWithReaderAndPointer2() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointer(true); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointer2 Run time:" + (endTime - startTime) + "ms"); + } + + private void _testToJSONWithReaderAndPointer(boolean isInLibrary) throws IOException { + Object actual; + if (isInLibrary) { + //do query inside library + actual = XML.toJSONObject(xmlReader, path); + write2File(actual, "inLib"); + } else { + //do query outside library + actual = path.queryFrom(XML.toJSONObject(xmlReader)); + write2File(actual, "outLib"); + } +// System.out.println(actual == null ? actual : actual.toString()); + } + + + private void write2File(Object actual, String ex) throws IOException { + String xmlRealPath = this.getClass().getResource("/" + xmlFileName).getPath(); + FileWriter out = new FileWriter(xmlRealPath.replace("xml", "json").replace(".json", "_" + ex + "_sub.json")); + BufferedWriter bw = new BufferedWriter(out); + bw.write(actual == null ? "" : actual.toString()); + bw.close(); + } + + /* + * static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) + */ + //not using library + @Test + public void testToJSONWithReaderAndPointerWithReplace1() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointerWithReplace(false); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointerWithReplace1 Run time:" + (endTime - startTime) + "ms"); + } + //using library + @Test + public void testToJSONWithReaderAndPointerWithReplace2() throws IOException { + long startTime = System.currentTimeMillis(); + _testToJSONWithReaderAndPointerWithReplace(true); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONWithReaderAndPointerWithReplace2 Run time:" + (endTime - startTime) + "ms"); + } + + private void _testToJSONWithReaderAndPointerWithReplace(boolean isInLibrary) throws IOException { + Object actual; + if (isInLibrary) { + //do query inside library + actual = XML.toJSONObject(xmlReader, path, replacement); + write2File(actual, "replace_inLib"); + } else { + //do query outside library + String[] strs = path.toString().split("/"); + String keyName = strs[strs.length - 1]; + String parentPath = ""; + for(int i=1; i { + return "swe262_"+cc; + }; + _testToJSONKeyReplaceWithKeyTransformer(func); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONKeyReplaceWithKeyTransformer Run time:" + (endTime - startTime) + "ms"); + } + + //reverse key in the library + @Test + public void testToJSONKeyReverseWithKeyTransformer() throws IOException { + long startTime = System.currentTimeMillis(); + Function func = cc-> { + StringBuilder sb = new StringBuilder((String) cc); + sb.reverse(); + return sb.toString(); + }; + _testToJSONKeyReplaceWithKeyTransformer(func); + long endTime = System.currentTimeMillis(); //get end time + System.out.println("testToJSONKeyReplaceWithKeyTransformer Run time:" + (endTime - startTime) + "ms"); + } + +// @Test + public void _testToJSONKeyReplaceWithKeyTransformer(Function func) throws IOException { + try { + //replace key inside library + JSONObject jsonObject; + jsonObject = XML.toJSONObject(xmlReader, func); + write2File(jsonObject, "M3_inLib"); + } catch (JSONException e) { + System.out.println(e); + } + } + + private static void refactorKeyName(JSONObject jsonObject, String prefix) { + Set sets = new HashSet(jsonObject.keySet()); + for(String key : sets){ + Object o = jsonObject.get(key); + refactorByType(prefix, o); + jsonObject.remove(key); + jsonObject.put(prefix+key, o); + } + } + + //support both jsonObject and jsonArray + private static void refactorByType(String prefix, Object object) { + if (object instanceof JSONObject) { + refactorKeyName((JSONObject) object, prefix); + } else if (object instanceof JSONArray) { + JSONArray jsonArray = (JSONArray)object; + for(Object o : jsonArray) { + refactorByType( prefix, o); + } + } + } + + private void write2File(JSONObject json, String ex) throws IOException { + String xmlRealPath = this.getClass().getResource("/" + xmlFileName).getPath(); + FileWriter out = new FileWriter(xmlRealPath.replace("xml", "json").replace(".json", "_" + ex + "_sub.json")); + BufferedWriter bw = new BufferedWriter(out); + bw.write(json == null ? "" : json.toString(4)); + bw.close(); + } + + + @Test + public void testHashMap1(){ + HashMap map = new HashMap(); + map.put("verification_date", "verification_date"); + map.put("last_update_submitted", "last_update_submitted"); + for(String key: map.keySet()){ + String value = map.get(key); + map.remove(key); + key = "swe262_" + key; + map.put(key, value); + System.out.println(key + ":" + map.get(key)); + } + } + + @Test + public void testHashMap2(){ + HashMap map = new HashMap(); + map.put("swe262_verification_date", "verification_date"); + map.put("swe262_last_update_submitted", "last_update_submitted"); + + for(String key: map.keySet()){ + System.out.println(key + ":" + map.get(key)); + } + } + + @Test + public void testHashMap3(){ + HashMap map = new HashMap(); + map.put("verification_date", "verification_date"); + map.put("last_update_submitted", "last_update_submitted"); + + for(String key: map.keySet()){ + System.out.println(key + ":" + map.get(key)); + } + } +} diff --git a/src/test/java/org/json/junit/XMLMyTestM5.java b/src/test/java/org/json/junit/XMLMyTestM5.java new file mode 100644 index 000000000..6ed1c0ed6 --- /dev/null +++ b/src/test/java/org/json/junit/XMLMyTestM5.java @@ -0,0 +1,96 @@ +package org.json.junit; + +import org.json.CustomFuture; +import org.json.JSONObject; +import org.json.XML; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @program: JSON-java + * @description: Add asynchronous methods to the library that allow the client code to proceed + * @author: Mr. Su + * @create: 2022-02-28 + **/ + +public class XMLMyTestM5 { + Reader xmlReader; + String xmlFileName; + InputStream xmlStream = null; + long startTime; +// CompletableFuture future; + CustomFuture future; + + @Before + public void load() { + startTime = System.currentTimeMillis(); + +// xmlFileName = "Issue537.xml"; + xmlFileName = "enwiki-250MB.xml"; +// xmlFileName = "enwiki-490MB.xml"; + xmlStream = XMLMyTestM3.class.getClassLoader().getResourceAsStream(xmlFileName); + this.xmlReader = new InputStreamReader(xmlStream); + } + + @After + public void end() { + long endTime = System.currentTimeMillis(); //get end time + System.out.println("Main: testAsyncToJSON Run time:" + (endTime - startTime) + "ms"); + } + + //success case + @Test + public void testAsyncToJSON() throws IOException, ExecutionException, InterruptedException { + BiFunction funSuccess = (jsonObject, str) -> { + System.out.println("Async: funSuccess writing into disk"); + try { + write2File(jsonObject, str); + return "Success"; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + future = XML.toJSONObject(xmlReader, funSuccess, funFail); + System.out.println("Main: Approach the end of main code"); + System.out.println("Main: The whole application ends with " + future.get()); + } + + //Error case + @Test + public void testAsyncToJSONError() throws IOException, ExecutionException, InterruptedException { + BiFunction funSuccess = (jsonObject, str) -> { + //simulate runtime exception + if(true) throw new RuntimeException("test error in funFail "); + return ""; + }; + future = XML.toJSONObject(xmlReader, funSuccess, funFail); + System.out.println("Main: Approach the end of main code"); + System.out.println("Main: The whole application ends with " + future.get()); + } + //Exception funciton + Function funFail = e -> { + System.out.println("Async: Catch failure, now funFail printing stackTrace"); + e.printStackTrace(); + return "Error"; + }; + + //Auxiliary function + private void write2File(JSONObject json, String ex) throws IOException { + String xmlRealPath = this.getClass().getResource("/" + xmlFileName).getPath(); + FileWriter out = new FileWriter(xmlRealPath.replace("xml", "json").replace(".json", "_" + ex + "_sub.json")); + BufferedWriter bw = new BufferedWriter(out); + bw.write(json == null ? "" : json.toString(4)); + bw.close(); + } +}