diff --git a/README.md b/README.md index 471bf439c..e912be602 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,7 @@ - +[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] -=============================== - -[](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 @@ +
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) + + +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) + +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 +``` + + +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 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(); + }; + ... +} +``` + + +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.
+ +{@code
+ * {@code
* & (ampersand) is replaced by &
* < (less than) is replaced by <
* > (greater than) is replaced by >
@@ -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 111 Ave
+ 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) {
+
+ //
+ x.skipPast("?>");
+ return false;
+ } else if (token == SLASH) {
+
+ // Close tag
+
+ token = x.nextToken();
+ if (name == null) {
+ throw x.syntaxError("Mismatched close tag " + token);
+ }
+ if (!token.equals(name)) {
+ throw x.syntaxError("Mismatched " + name + " and " + token);
+ }
+ if (x.nextToken() != GT) {
+ throw x.syntaxError("Misshaped close tag");
+ }
+ return true;
+
+ } else if (token instanceof Character) {
+ throw x.syntaxError("Misshaped tag");
+
+ // Open tag <
+ //if token is tagName
+ } else {
+ tagName = (String) token;
+ token = null;
+ jsonObject = new JSONObject();
+ boolean nilAttributeFound = false;
+ xmlXsiTypeConverter = null;
+
+ //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("" +tagName+ ">");
+ //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("" +tagName+ ">");
+ 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 111 Ave
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 + "" + tagName + ">";
}
+
+
+ /**
+ * 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) {
+
+ //
+ x.skipPast("?>");
+ return false;
+ } else if (token == SLASH) {
+
+ // Close tag
+
+ token = x.nextToken();
+ if (name == null) {
+ throw x.syntaxError("Mismatched close tag " + token);
+ }
+ if (!token.equals(name)) {
+ throw x.syntaxError("Mismatched " + name + " and " + token);
+ }
+ if (x.nextToken() != GT) {
+ throw x.syntaxError("Misshaped close tag");
+ }
+ return true;
+
+ } else if (token instanceof Character) {
+ throw x.syntaxError("Misshaped tag");
+
+ // Open tag <
+ //if token is tagName
+ } else {
+ tagName = (String) token;
+ token = null;
+ jsonObject = new JSONObject();
+ boolean nilAttributeFound = false;
+ xmlXsiTypeConverter = null;
+ for (;;) {
+ if (token == null) {
+ token = x.nextToken();
+ }
+ // attribute = value
+ if (token instanceof String) {
+ //For Milestone 3
+ //for this xml
+ //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 111 Ave
+ 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