diff --git a/.gitignore b/.gitignore index b78af4db7..0374660b6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ build /gradlew /gradlew.bat .gitmodules +.DS_Store diff --git a/UCI_Project_README.md b/UCI_Project_README.md new file mode 100644 index 000000000..b6f9a3ea1 --- /dev/null +++ b/UCI_Project_README.md @@ -0,0 +1,22 @@ +# Milestone II Changes + +All edits for Milestone II can be found within the `XMLTest.java` and `XML.java` files. We introduced two new overloaded parse methods, in addition to the two methods we were required to implement. These implementations are used to facilitate both of the required methods. Testing methods for these changes are located at the bottom of the `XMLTest.java` file. + +# Milestone III Changes + +For Milestone III, modifications were made within the `XMLTest.java` and `XML.java` files. All tests related to these changes are located at the bottom of the XML testing file. Within `XML.java`, we added two new methods: an overloaded parse method that accepts a function parameter and the `toJSONObject` method, which utilizes the new parse method for its operation and takes in a function parameter. + +# Milestone III Discussion + +In this phase, we introduced an additional parse method that accepts a Java `Function` object. This function is designed to take in a key as an object and return a modified key as an object. It integrates seamlessly with the parse method, allowing for real-time modification of keys during the XML to JSON conversion process. This functionality enables the dynamic alteration of keys to reflect the desired changes specified by the input function parameter. + +# Increased Efficiency with the Changes + +The recent enhancements to the parse method offer significant efficiency improvements over the initial Milestone I implementation, for several reasons: + +- **Milestone I Implementation:** + - Converted the entire XML object into a JSON string. + - Employed a recursive method to locate each object key and nested object key, replacing the value with a prefix. + +- **New Implementation:** + - Directly replaces each key encountered during the JSON conversion process with a new key, applying the provided function to modify the key. diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 039f136de..11c8073a5 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -24,9 +24,15 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Stack; import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import static org.json.NumberConversionUtil.potentialNumber; import static org.json.NumberConversionUtil.stringToNumber; @@ -2884,5 +2890,63 @@ private static JSONException recursivelyDefinedObjectException(String key) { ); } + public Stream toStream() { + return StreamSupport + .stream(Spliterators.spliteratorUnknownSize(new JSONObjectIterator(this), Spliterator.ORDERED), false); + } + + private static class JSONObjectIterator implements Iterator { + private final Stack stack; + + JSONObjectIterator(JSONObject jsonObject) { + this.stack = new Stack<>(); + stack.push(jsonObject); + } + + @Override + public boolean hasNext() { + return !stack.isEmpty(); + } + + @Override + public JSONObject next() { + if (!hasNext()) + throw new NoSuchElementException(); + + Object current = stack.pop(); + + if (current instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) current; + + for (String key : jsonObject.keySet()) { + Object value = jsonObject.get(key); + + if (value instanceof JSONObject || value instanceof JSONArray) { + stack.push(value); + } + } + + return jsonObject; + } else if (current instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) current; + + for (int i = jsonArray.length() - 1; i >= 0; i--) { + Object value = jsonArray.get(i); + + if (value instanceof JSONObject || value instanceof JSONArray) { + stack.push(value); + } + } + + return null; // Null indicates that we're iterating over a JSONArray + } else { + throw new UnsupportedOperationException("JSONObjectStream only supports JSONObject and JSONArray"); + } + } + } + + public static Iterable stream(JSONObject jsonObject) { + return () -> new JSONObjectIterator(jsonObject); + } } diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index a94c3fc4b..1921ab09a 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -8,7 +8,16 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; +import java.util.Arrays; import java.util.Iterator; +import java.util.Stack; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; import static org.json.NumberConversionUtil.potentialNumber; import static org.json.NumberConversionUtil.stringToNumber; @@ -225,7 +234,7 @@ public static void noSpace(String string) throws JSONException { /** * Scan the content following the named tag, attaching it to the context. - * + * * @param x * The XMLTokener containing the source string. * @param context @@ -239,8 +248,774 @@ public static void noSpace(String string) throws JSONException { * @return true if the close tag is processed. * @throws JSONException Thrown if any parsing error occurs. */ - private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth) + private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth) + 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(); + + // "); + 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) { + //1748 + // Close 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) { + 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 + for (;;) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } + // WHERE STRING VALS ARE PROCESSED + 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)); + } + } + /** + * where RECURSION TO FIND NEW SUBOBJECTS BEGINS + */ + } else if (token == LT) { + // Nested element + if (currentNestingDepth == config.getMaxNestingDepth()) { + throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); + } + + if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) { + + 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) { + context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + + context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + + private static Stack stack = new Stack(); + + /** + * Search if the tag name is present in the path. + * + * @param str - The string array with the paths + * @param tagName - The required tag name to find + * @return - true if the required tagname is found in the paths + */ + private static boolean isPresent(String[] str, String tagName) { + str = Arrays.copyOf(str, str.length - 1); + + for (int i = 0; i < str.length; i += 1) { + if (tagName.equals(str[i])) + return true; + } + + return false; + } + + /** + * parse method for METHOD 1 MILESTONE 2 + * + * 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 config + * The XML parser configuration. + * @param currentNestingDepth + * The current nesting depth. + * @param path + * The path which is to be found + * @param requiredObj + * The object where the json object with the required + * path is saved + * @param constPath + * The path which won't be updated, but passed + * through every recursive function + * @return true if the close tag is processed. + * @throws JSONException Thrown if any parsing error occurs. + */ + private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, + int currentNestingDepth, String[] path, JSONObject requiredObj, final String[] constPath) + 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: + // <> + // <= + // << + + + /** + * this lets us jump from the closing tag of the thing we wanted + * all the way to the end of the XML document we are parsing. This + * is done by checking every closing tag with the target tagName we + * wish to replace. We utilize a stack to keep track of all tags as they + * come in. If the closing tag is equal to the target, we have found what + * we wanted, already extracted the subObject and therefore no longer need + * to be in the method. We skip past the remaining tags and get out of the + * method much faster + */ + token = x.nextToken(); + + if (!(token == BANG || token == QUEST)) { + if (token.equals(SLASH)) { + String s = stack.pop(); + + if (s.equals(constPath[constPath.length - 1])) { + while (stack.size() > 0) { + String abc = stack.pop(); + + x.skipPast(""); + } + + return true; + } + + } else + stack.push(token.toString()); + } + + // "); + 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 + 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) { + 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, ""); + } + } + // exiting the recursive method early in parsing + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + if (!isPresent(constPath, tagName)) + 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)); + if (tagName.equals(constPath[constPath.length - 1])) + requiredObj.accumulate(tagName, + config.isKeepStrings() ? string : stringToValue(string)); + } + } + + } else if (token == LT) { + // Nested element + if (currentNestingDepth == config.getMaxNestingDepth()) { + throw x.syntaxError( + "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); + } + + /** + * this part of the method is where we enter the recursive call. + * before we do that, we want to adjust the path. we saved the + * name of the tag currently being processed, and check it with + * our array path. if it matches, we decrement our path by the first + * element to keep track of the method's recursive call. + */ + String[] newP = path; + + if (path.length > 0 && tagName.equals(path[0])) { + newP = path.length > 0 ? Arrays.copyOfRange(path, 1, path.length) : path; + } + + if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1, newP, requiredObj, + constPath)) { + 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) { + context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + /** + * if the path arr is empty, we know that we + * can extract the subObject and save it to a + * method variable + */ + if (path.length == 0) { + requiredObj.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + } + } else { + if (tagName.equals(constPath[constPath.length - 1])) + context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + + 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(); + + // "); + 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 + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (config.getForceList().contains(keyTransformer.apply(tagName))) { + // Force the value to be an array + if (nilAttributeFound) { + context.append(keyTransformer.apply(tagName), JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.append(keyTransformer.apply(tagName), jsonObject); + } else { + context.put(keyTransformer.apply(tagName), new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(keyTransformer.apply(tagName), JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(keyTransformer.apply(tagName), jsonObject); + } else { + context.accumulate(keyTransformer.apply(tagName), ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + 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) { + // Nested element + if (parse(x, jsonObject, tagName, config, keyTransformer)) { + 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(keyTransformer.apply(tagName), jsonObject.opt(config.getcDataTagName())); + } else { + context.append(keyTransformer.apply(tagName), jsonObject); + } + } else { + if (jsonObject.length() == 0) { + context.accumulate(keyTransformer.apply(tagName), ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(keyTransformer.apply(tagName), jsonObject.opt(config.getcDataTagName())); + } else { + context.accumulate(keyTransformer.apply(tagName), jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + /** + * PARSE METHOD USED FOR MILESTONE 2 METHOD 2 + * + * @param x - XML Tokener used to parse XML String to JSON + * @param context - JSONObject that is being built from the XML String + * @param name - The tag name. + * @param config - XML configuration setting + * @param currentNestingDepth - The recorded level of recurssion that we are currently on + * @param path_tracker - This string holds the tags of the JSONPointer in order as they are processed + * @param replacement - this is the JSON Object that we want to use to replace the subObject + * @param openTagFound - atomic boolean used to determine if or when an open tag is found + * @param LAST_PATH - string allocation of tagname of the subObject that needs to be replaced + * @param NewTagName - This is the tagName associated with the replacement subObeject. + * @return - boolean value that measures success or failure in parsing XML to JSON + * @throws JSONException + */ + private static boolean parsed2(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, + int currentNestingDepth, String[] path_tracker, JSONObject replacement, AtomicBoolean openTagFound, + String LAST_PATH, String NewTagName) throws JSONException { + char c; int i; JSONObject jsonObject = null; @@ -261,7 +1036,10 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP token = x.nextToken(); + + // 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); @@ -390,15 +1204,19 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP // Content, between <...> and for (;;) { token = x.nextContent(); + if (token == null) { if (tagName != null) { throw x.syntaxError("Unclosed tag " + tagName); } return false; - } else if (token instanceof String) { + } + // WHERE STRING VALS ARE PROCESSED + else if (token instanceof String) { string = (String) token; + if (string.length() > 0) { - if(xmlXsiTypeConverter != null) { + if (xmlXsiTypeConverter != null) { jsonObject.accumulate(config.getcDataTagName(), stringToValue(string, xmlXsiTypeConverter)); } else { @@ -406,40 +1224,85 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP config.isKeepStrings() ? string : stringToValue(string)); } } - + /** + * where RECURSION TO FIND NEW SUBOBJECTS BEGINS + */ } else if (token == LT) { // Nested element if (currentNestingDepth == config.getMaxNestingDepth()) { - throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); + throw x.syntaxError( + "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); } + /** + * adjust path as we go deeper into object + */ + if (path_tracker.length > 0 && tagName.equals(path_tracker[0])) { + String[] newArray = path_tracker.length > 1 + ? Arrays.copyOfRange(path_tracker, 1, path_tracker.length) + : new String[0]; + path_tracker = newArray; + } + /** + * we have come to the tag of the object we want to replace + */ + if (path_tracker.length == 1) { - if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) { - 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); + LAST_PATH = path_tracker[0].trim(); + } + + if (parsed2(x, jsonObject, tagName, config, currentNestingDepth + 1, path_tracker, replacement, + openTagFound, LAST_PATH, NewTagName)) { + + /** + * check to see if we have found the replacement tags + * we inserted. if so, we replace the object with our + * own object + */ + if(tagName.equals(NewTagName)){ + + for (String key : replacement.keySet()) { + jsonObject.put(key, replacement.get(key)); } - } else { - if (jsonObject.length() == 0) { - context.accumulate(tagName, ""); - } else if (jsonObject.length() == 1 - && jsonObject.opt(config.getcDataTagName()) != null) { - context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + + context.put(tagName, jsonObject); + return false; + } + else{ + + + 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 { - context.accumulate(tagName, jsonObject); + if (jsonObject.length() == 0) { + context.accumulate(tagName, ""); + + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + + // where subObjects are processed + } else { + // accumulates first subObject + context.accumulate(tagName, jsonObject); + } } - } - return false; + return false; + } } } } } else { + throw x.syntaxError("Misshaped tag"); } } @@ -523,6 +1386,39 @@ public static Object stringToValue(String string) { public static JSONObject toJSONObject(String string) throws JSONException { return toJSONObject(string, XMLParserConfiguration.ORIGINAL); } + /** + * MILESTONE 5 + * Milestone V Method + * This method takes in a Reader Object as well as consumer functions that operate on the + * jsonObject after the executor thread has properly parsed the reader xml to JSON. This works + * with the use of an atomic reference that is thread safe and after which the callback passed + * within the method is utilized on the jsonObject that is parsed. if the jsonObject was unable + * to be created, then the exception consumer is passed on the error to be forwaded to the method + * user + * + * @param reader + * @param callback + * @param exceptionCallback + */ + public static void toJsonObject(Reader reader, Consumer callback, + Consumer exceptionCallback) { + AtomicReference converted = new AtomicReference<>(new JSONObject()); + ExecutorService executor = Executors.newSingleThreadExecutor(); + + executor.submit(() -> { + try { + JSONObject jsonObject = toJSONObject(reader); + converted.set(jsonObject); + + callback.accept(jsonObject); + } catch (Exception e) { + exceptionCallback.accept(e); + } + }); + + executor.shutdown(); + } + /** * Convert a well-formed (but not necessarily valid) XML into a @@ -654,6 +1550,108 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws public static JSONObject toJSONObject(String string, XMLParserConfiguration config) throws JSONException { return toJSONObject(new StringReader(string), config); } + + /** + * METHOD 1 MILESTONE 2 + * + * essentially use an array to parse through the different levels of the JSON structure + * as we get deeper. once we find the deepest level associated with the pointer, we can + * extract that subobject that is passed through an overloaded parse method along with + * every method call. We essentialluy add an empty json Object and once we find the subObject + * we are looking for, we save it to an empty JSONObject and return that value the second we find + * it thereby speeding up the process of finding a subObject without parsing the whole XML File + * + * + * @param reader - The reader object for the xml string + * @param path - The path that is needed to be found + * @return - The object under the required path + * @throws JSONException + */ + public static JSONObject toJSONObject(Reader reader, JSONPointer path) throws JSONException { + String[] tempPath = path.toString().split("/"); + tempPath = Arrays.copyOfRange(tempPath, 1, tempPath.length); + + JSONObject jo = new JSONObject(); + JSONObject requiredObj = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parse(x, jo, null, XMLParserConfiguration.ORIGINAL, 0, tempPath, requiredObj, tempPath); + } + } + + return requiredObj; + } + + public static JSONObject toJSONObject(Reader reader, Function keyTransformer) throws JSONException { + 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; + } + + /** + * METHOD 2 MILESTONE 2 + * @param reader - reads the XML String + * @param path - path to SubObject that we wish to replace + * @param replacement - the actual JSON Object that we want to use to replace the object at end of path + * @return - Modified JSON Object that has the aspect desired subObject replaced + * + * This method as it currently exists essentially takes the replacement object apart, puts its parent key to a + * new String val, at which point the real subOBject is extracted from the replacement and it is thrown into an overloaded + * parse method called parsed2. It essentially is able to use the inner library mechanics to find the subobject we want to + * replace, and then manually updating the opening and closing tags of said object, whereby the subObject is essentially used + * to overwrite the object that we need to replace with what we provided. + */ + + public static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) { + String the_path = path.toString(); + + String[] json_pointer = the_path.startsWith("/") ? the_path.substring(1).split("/") : the_path.split("/"); + + XMLParserConfiguration config = new XMLParserConfiguration(); + + AtomicBoolean openTagFound = new AtomicBoolean(false); + + String LAST_PATH = json_pointer[json_pointer.length - 1]; + + String [] arr = JSONObject.getNames(replacement); + // get the tagName of the overarching key value of the Replacement Object + String NewTagName = arr[0]; + // quickly going to extract the subObject of the replacement + JSONPointer getter = new JSONPointer("/" + NewTagName); + // actually procured the subObject of the replacement + JSONObject actualReplacement = (JSONObject) getter.queryFrom(replacement); + + + + + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + + if (x.more()) { + + parsed2(x, jo, null, XMLParserConfiguration.ORIGINAL, 0, json_pointer, actualReplacement, openTagFound, + LAST_PATH, NewTagName); + } + + } + + return jo; + + + } /** * Convert a JSONObject into a well-formed, element-normal XML string. diff --git a/src/test/java/org/json/XMLTest2.java b/src/test/java/org/json/XMLTest2.java new file mode 100644 index 000000000..09034b012 --- /dev/null +++ b/src/test/java/org/json/XMLTest2.java @@ -0,0 +1,158 @@ +package org.json; +import org.junit.Test; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.json.*; + +public class XMLTest2{ + + + /** + * This method tests the efficacy of the milestone 2 method 2 + * As you can see the initial xml object is taken and a replacement is + * named. The method is sucessfully able to replace the subobject (the entire + * adress body including nick, name, street, and zipcode) with the new + * object (jsonReplacement). + * + * The return object from the function call is not null and the resulting + * object matches the expected JSON Object output + */ + @Test + public void testReplacement() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " \n" + + " Crista \n" + + " Crista Lopes\n" + + " \n" + + " 92614\n" + + "
\n" + + "
"; + + String jsonReplacement = + " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + String expected = "{\"contact\":{\"place\":{\"zipcode\":92626,\"street\":\"Muholland Drive\"}}}"; + + + Reader reader = new StringReader(xmlString); + JSONPointer pointer = new JSONPointer("/contact/address"); + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + + JSONObject result = XML.toJSONObject(reader, pointer,replacement); + System.out.println(result); + assertNotNull("Resulting JSONObject should not be null", result); + assertEquals(expected, result.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + /** + * Further test efficacy of milestone 2 method 2 implementation by checking a different + * XMLString for a subObject. The structure of this one is different and therefore allows + * for a different means by which to verify the working order of this method. + * it checks to see if the subObject key has been placed within the xmlString and + * then it further checks if these values are able to be accessed. + */ + @Test + public void testReplacement2() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer to locate the subObject that needs to be extracted + JSONPointer pointer = new JSONPointer("/contact/address"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + // getting the answer + JSONObject result = XML.toJSONObject(reader, pointer, replacement); + + + JSONObject address = result.getJSONObject("contact").getJSONObject("place"); + assert result.getJSONObject("contact").has("place"); + assert address.getString("street").equals("Muholland Drive"); + assert address.getInt("zipcode") == (92626); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + /** + * What happens if we try to use a pointer to a subObject that doesn't exist? + * We would expect that nothing happens and the same object is returned + */ + @Test + public void testReplacementNull() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer that looks for subObject that doesn't exist + JSONPointer pointer = new JSONPointer("/contact/dog"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + // expected answer + String expected = XML.toJSONObject(xmlString).toString(); + + // getting the answer + JSONObject actual = XML.toJSONObject(reader, pointer, replacement); + + assertEquals(expected, actual.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } +} + diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 053f17a91..515c0899b 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -26,6 +26,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.json.CDL; import org.json.JSONArray; @@ -57,6 +58,7 @@ import org.json.junit.data.Singleton; import org.json.junit.data.SingletonEnum; import org.json.junit.data.WeirdList; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -3817,4 +3819,60 @@ public static HashMap buildNestedMap(int maxDepth) { return nestedMap; } + /** + * Test to determine validy of the stream method. + */ + @Test + public void streamTest() { + JSONObject obj = XML.toJSONObject( + "AAAASmithBBBBSmith"); + + JSONObject[] allNodesExpected = new JSONObject[] { + new JSONObject( + "{\"Books\":{\"book\":[{\"author\":\"ASmith\",\"title\":\"AAA\"},{\"author\":\"BSmith\",\"title\":\"BBB\"}]}}"), + new JSONObject( + "{\"book\":[{\"author\":\"ASmith\",\"title\":\"AAA\"},{\"author\":\"BSmith\",\"title\":\"BBB\"}]}"), + new JSONObject("{\"author\":\"ASmith\",\"title\":\"AAA\"}"), + new JSONObject("{\"author\":\"BSmith\",\"title\":\"BBB\"}") }; + + // Get All the nodes + JSONObject[] allNodesActual = obj.toStream().filter(node -> node != null).toArray(JSONObject[]::new); + + for (int i = 0; i < allNodesActual.length; i += 1) { + assertEquals(allNodesActual[i].toString(), allNodesExpected[i].toString()); + } + } + + @Test + // TEST TO SEE WE CAN GET TITLES + public void streamTest2(){ + JSONObject obj = XML.toJSONObject( + "AAAASmithBBBBSmith"); + + List titles = obj.toStream() + .filter(node -> node != null) + .map(node -> node.optString("title")) + .filter(x -> x.length() > 0) + .collect(Collectors.toList()); + + String expectedTitles[] = { "AAA", "BBB" }; + + for (int i = 0; i < titles.size(); i += 1) { + assertEquals(expectedTitles[i], titles.get(i)); + } + } + @Test + // TEST TO SEE IF WE CAN GET A PARTICULAR VALUE + public void streamTest3() { + + JSONObject obj = XML.toJSONObject( + "AAAASmithBBBBSmith"); + + JSONObject[] expected = obj.toStream().filter(node -> node != null) + .filter(node -> node.has("author") && + "ASmith".equals(node.optString("author"))) + .toArray(JSONObject[]::new); + + assertEquals(expected[0].toString(), "{\"author\":\"ASmith\",\"title\":\"AAA\"}"); + } } diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 823a06591..bd3700231 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -7,6 +7,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -21,31 +23,35 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import org.json.*; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; - /** * Tests for JSON-Java XML.java * Note: noSpace() will be tested by JSONMLTest */ public class XMLTest { /** - * JUnit supports temporary files and folders that are cleaned up after the test. - * https://garygregory.wordpress.com/2010/01/20/junit-tip-use-rules-to-manage-temporary-files-and-folders/ + * JUnit supports temporary files and folders that are cleaned up after the + * test. + * https://garygregory.wordpress.com/2010/01/20/junit-tip-use-rules-to-manage-temporary-files-and-folders/ */ @Rule public TemporaryFolder testFolder = new TemporaryFolder(); - /** * JSONObject from a null XML string. * Expects a NullPointerException */ - @Test(expected=NullPointerException.class) + @Test(expected = NullPointerException.class) public void shouldHandleNullXML() { String xmlStr = null; JSONObject jsonObject = XML.toJSONObject(xmlStr); @@ -79,15 +85,14 @@ public void shouldHandleNonXML() { */ @Test public void shouldHandleInvalidSlashInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " abc street\n"+ - "
\n"+ - "
"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " abc street\n" + + "
\n" + + "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -104,15 +109,14 @@ public void shouldHandleInvalidSlashInTag() { */ @Test public void shouldHandleInvalidBangInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " \n"+ - "
\n"+ - "
"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -129,15 +133,14 @@ public void shouldHandleInvalidBangInTag() { */ @Test public void shouldHandleInvalidBangNoCloseInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " \n"+ - ""; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + ""; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -154,15 +157,14 @@ public void shouldHandleInvalidBangNoCloseInTag() { */ @Test public void shouldHandleNoCloseStartTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " \n"+ - ""; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + ""; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -179,15 +181,14 @@ public void shouldHandleNoCloseStartTag() { */ @Test public void shouldHandleInvalidCDATABangInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " Joe Tester\n"+ - " \n"+ - "
\n"+ - "
"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " Joe Tester\n" + + " \n" + + "
\n" + + "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -203,9 +204,9 @@ public void shouldHandleInvalidCDATABangInTag() { */ @Test public void shouldHandleNullJSONXML() { - JSONObject jsonObject= null; - String actualXml=XML.toString(jsonObject); - assertEquals("generated XML does not equal expected XML","\"null\"",actualXml); + JSONObject jsonObject = null; + String actualXml = XML.toString(jsonObject); + assertEquals("generated XML does not equal expected XML", "\"null\"", actualXml); } /** @@ -213,7 +214,7 @@ public void shouldHandleNullJSONXML() { */ @Test public void shouldHandleEmptyJSONXML() { - JSONObject jsonObject= new JSONObject(); + JSONObject jsonObject = new JSONObject(); String xmlStr = XML.toString(jsonObject); assertTrue("xml string should be empty", xmlStr.isEmpty()); } @@ -223,22 +224,20 @@ public void shouldHandleEmptyJSONXML() { */ @Test public void shouldHandleNoStartTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " >\n"+ - "
\n"+ - "
"; - String expectedStr = - "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ - "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ - "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " >\n" + + "
\n" + + "
"; + String expectedStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\"" + + "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\"" + + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** @@ -246,34 +245,32 @@ public void shouldHandleNoStartTag() { */ @Test public void shouldHandleSimpleXML() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " Joe Tester\n"+ - " [CDATA[Baker street 5]\n"+ - " \n"+ - " true\n"+ - " false\n"+ - " null\n"+ - " 42\n"+ - " -23\n"+ - " -23.45\n"+ - " -23x.45\n"+ - " 1, 2, 3, 4.1, 5.2\n"+ - "
\n"+ - "
"; - - String expectedStr = - "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+ - "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+ - "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+ - "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+ - "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ - "},\"xsi:noNamespaceSchemaLocation\":"+ - "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ - "XMLSchema-instance\"}}"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " Joe Tester\n" + + " [CDATA[Baker street 5]\n" + + " \n" + + " true\n" + + " false\n" + + " null\n" + + " 42\n" + + " -23\n" + + " -23.45\n" + + " -23x.45\n" + + " 1, 2, 3, 4.1, 5.2\n" + + "
\n" + + "
"; + + String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\"," + + "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n" + + "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n" + + "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n" + + "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n" + + "},\"xsi:noNamespaceSchemaLocation\":" + + "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/" + + "XMLSchema-instance\"}}"; compareStringToJSONObject(xmlStr, expectedStr); compareReaderToJSONObject(xmlStr, expectedStr); @@ -284,44 +281,42 @@ public void shouldHandleSimpleXML() { * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testXmlEscapeToJson(){ - String xmlStr = - "\n"+ - ""+ - "\""+ - "A €33"+ - "A €22€"+ - "some text ©"+ - "" " & ' < >"+ - "𝄢 𐅥" + - ""; - String expectedStr = - "{\"root\":{" + - "\"rawQuote\":\"\\\"\"," + - "\"euro\":\"A €33\"," + - "\"euroX\":\"A €22€\"," + - "\"unknown\":\"some text ©\"," + - "\"known\":\"\\\" \\\" & ' < >\"," + - "\"high\":\"𝄢 𐅥\""+ - "}}"; - + public void testXmlEscapeToJson() { + String xmlStr = "\n" + + "" + + "\"" + + "A €33" + + "A €22€" + + "some text ©" + + "" " & ' < >" + + "𝄢 𐅥" + + ""; + String expectedStr = "{\"root\":{" + + "\"rawQuote\":\"\\\"\"," + + "\"euro\":\"A €33\"," + + "\"euroX\":\"A €22€\"," + + "\"unknown\":\"some text ©\"," + + "\"known\":\"\\\" \\\" & ' < >\"," + + "\"high\":\"𝄢 𐅥\"" + + "}}"; + compareStringToJSONObject(xmlStr, expectedStr); compareReaderToJSONObject(xmlStr, expectedStr); compareFileToJSONObject(xmlStr, expectedStr); } - + /** * Tests that control characters are escaped. */ @Test - public void testJsonToXmlEscape(){ + public void testJsonToXmlEscape() { final String jsonSrc = "{\"amount\":\"10,00 €\"," + "\"description\":\"Ação Válida\u0085\"," + "\"xmlEntities\":\"\\\" ' & < >\"" + "}"; JSONObject json = new JSONObject(jsonSrc); String xml = XML.toString(json); - //test control character not existing + // test control character not existing assertFalse("Escaping \u0085 failed. Found in XML output.", xml.contains("\u0085")); assertTrue("Escaping \u0085 failed. Entity not found in XML output.", xml.contains("…")); // test normal unicode existing @@ -343,24 +338,23 @@ public void testJsonToXmlEscape(){ @Test public void shouldHandleCommentsInXML() { - String xmlStr = - "\n"+ - "\n"+ - "\n"+ - "
\n"+ - " comment ]]>\n"+ - " Joe Tester\n"+ - " \n"+ - " Baker street 5\n"+ - "
\n"+ + String xmlStr = "\n" + + "\n" + + "\n" + + "
\n" + + " comment ]]>\n" + + " Joe Tester\n" + + " \n" + + " Baker street 5\n" + + "
\n" + "
"; JSONObject jsonObject = XML.toJSONObject(xmlStr); - String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+ - "street 5\",\"name\":\"Joe Tester\",\"content\":\" this is -- "+ + String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker " + + "street 5\",\"name\":\"Joe Tester\",\"content\":\" this is -- " + " comment \"}}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** @@ -368,31 +362,29 @@ public void shouldHandleCommentsInXML() { */ @Test public void shouldHandleToString() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " [CDATA[Joe & T > e < s " t ' er]]\n"+ - " Baker street 5\n"+ - " 1, 2, 3, 4.1, 5.2\n"+ - "
\n"+ - "
"; - - String expectedStr = - "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\","+ - "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\","+ - "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ - "},\"xsi:noNamespaceSchemaLocation\":"+ - "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ + String xmlStr = "\n" + + "\n" + + "
\n" + + " [CDATA[Joe & T > e < s " t ' er]]\n" + + " Baker street 5\n" + + " 1, 2, 3, 4.1, 5.2\n" + + "
\n" + + "
"; + + String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\"," + + "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\"," + + "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n" + + "},\"xsi:noNamespaceSchemaLocation\":" + + "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/" + "XMLSchema-instance\"}}"; - + JSONObject jsonObject = XML.toJSONObject(xmlStr); String xmlToStr = XML.toString(jsonObject); JSONObject finalJsonObject = XML.toJSONObject(xmlToStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); - Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); } /** @@ -405,8 +397,8 @@ public void shouldHandleContentNoArraytoString() { JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = ">"; - assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ - finalStr+"]", expectedFinalStr, finalStr); + assertEquals("Should handle expectedFinal: [" + expectedStr + "] final: [" + + finalStr + "]", expectedFinalStr, finalStr); } /** @@ -416,15 +408,14 @@ public void shouldHandleContentNoArraytoString() { */ @Test public void shouldHandleContentArraytoString() { - String expectedStr = - "{\"addresses\":{" + - "\"content\":[1, 2, 3]}}"; + String expectedStr = "{\"addresses\":{" + + "\"content\":[1, 2, 3]}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); - String expectedFinalStr = ""+ + String expectedFinalStr = "" + "1\n2\n3"; - assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ - finalStr+"]", expectedFinalStr, finalStr); + assertEquals("Should handle expectedFinal: [" + expectedStr + "] final: [" + + finalStr + "]", expectedFinalStr, finalStr); } /** @@ -433,83 +424,84 @@ public void shouldHandleContentArraytoString() { */ @Test public void shouldHandleArraytoString() { - String expectedStr = - "{\"addresses\":{"+ - "\"something\":[1, 2, 3]}}"; + String expectedStr = "{\"addresses\":{" + + "\"something\":[1, 2, 3]}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); - String expectedFinalStr = ""+ - "123"+ + String expectedFinalStr = "" + + "123" + ""; - assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ - finalStr+"]", expectedFinalStr, finalStr); + assertEquals("Should handle expectedFinal: [" + expectedStr + "] final: [" + + finalStr + "]", expectedFinalStr, finalStr); } - + /** * Tests that the XML output for empty arrays is consistent. */ @Test - public void shouldHandleEmptyArray(){ + public void shouldHandleEmptyArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("array",new Object[]{}); + jo1.put("array", new Object[] {}); final JSONObject jo2 = new JSONObject(); - jo2.put("array",new JSONArray()); + jo2.put("array", new JSONArray()); final String expected = ""; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected an empty root tag", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected an empty root tag", expected, output2); } - + /** - * Tests that the XML output for arrays is consistent when an internal array is empty. + * Tests that the XML output for arrays is consistent when an internal array is + * empty. */ @Test - public void shouldHandleEmptyMultiArray(){ + public void shouldHandleEmptyMultiArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("arr",new Object[]{"One", new String[]{}, "Four"}); + jo1.put("arr", new Object[] { "One", new String[] {}, "Four" }); final JSONObject jo2 = new JSONObject(); - jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{}), "Four"})); + jo2.put("arr", new JSONArray(new Object[] { "One", new JSONArray(new String[] {}), "Four" })); final String expected = "OneFour"; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected a matching array", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected a matching array", expected, output2); } - + /** * Tests that the XML output for arrays is consistent when arrays are not empty. */ @Test - public void shouldHandleNonEmptyArray(){ + public void shouldHandleNonEmptyArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("arr",new String[]{"One", "Two", "Three"}); + jo1.put("arr", new String[] { "One", "Two", "Three" }); final JSONObject jo2 = new JSONObject(); - jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"})); + jo2.put("arr", new JSONArray(new String[] { "One", "Two", "Three" })); final String expected = "OneTwoThree"; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected a non empty root tag", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected a non empty root tag", expected, output2); } /** - * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays. + * Tests that the XML output for arrays is consistent when arrays are not empty + * and contain internal arrays. */ @Test - public void shouldHandleMultiArray(){ + public void shouldHandleMultiArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"}); + jo1.put("arr", new Object[] { "One", new String[] { "Two", "Three" }, "Four" }); final JSONObject jo2 = new JSONObject(); - jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{"Two", "Three"}), "Four"})); + jo2.put("arr", new JSONArray(new Object[] { "One", new JSONArray(new String[] { "Two", "Three" }), "Four" })); final String expected = "OneTwoThreeFour"; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected a matching array", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected a matching array", expected, output2); } @@ -519,33 +511,30 @@ public void shouldHandleMultiArray(){ */ @Test public void shouldHandleNestedArraytoString() { - String xmlStr = - "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+ - "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ - "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + String xmlStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\"," + + "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\"" + + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject jsonObject = new JSONObject(xmlStr); String finalStr = XML.toString(jsonObject); JSONObject finalJsonObject = XML.toJSONObject(finalStr); - String expectedStr = "
"+ - "12"+ - "3"+ - "
test.xsdhttp://www.w3.org/2001/XMLSche"+ + String expectedStr = "
" + + "12" + + "3" + + "
test.xsdhttp://www.w3.org/2001/XMLSche" + "ma-instance
"; JSONObject expectedJsonObject = XML.toJSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); } - /** - * Possible bug: + * Possible bug: * Illegal node-names must be converted to legal XML-node-names. * The given example shows 2 nodes which are valid for JSON, but not for XML. * Therefore illegal arguments should be converted to e.g. an underscore (_). */ @Test - public void shouldHandleIllegalJSONNodeNames() - { + public void shouldHandleIllegalJSONNodeNames() { JSONObject inputJSON = new JSONObject(); inputJSON.append("123IllegalNode", "someValue1"); inputJSON.append("Illegal@node", "someValue2"); @@ -556,27 +545,27 @@ public void shouldHandleIllegalJSONNodeNames() * This is invalid XML. Names should not begin with digits or contain * certain values, including '@'. One possible solution is to replace * illegal chars with '_', in which case the expected output would be: - * <___IllegalNode>someValue1someValue2 + * <___IllegalNode>someValue1someValue2 */ String expected = "<123IllegalNode>someValue1someValue2"; - assertEquals("length",expected.length(), result.length()); - assertTrue("123IllegalNode",result.contains("<123IllegalNode>someValue1")); - assertTrue("Illegal@node",result.contains("someValue2")); + assertEquals("length", expected.length(), result.length()); + assertTrue("123IllegalNode", result.contains("<123IllegalNode>someValue1")); + assertTrue("Illegal@node", result.contains("someValue2")); } /** * JSONObject with NULL value, to XML.toString() */ @Test - public void shouldHandleNullNodeValue() - { + public void shouldHandleNullNodeValue() { JSONObject inputJSON = new JSONObject(); inputJSON.put("nullValue", JSONObject.NULL); // This is a possible preferred result // String expectedXML = ""; /** - * This is the current behavior. JSONObject.NULL is emitted as + * This is the current behavior. JSONObject.NULL is emitted as * the string, "null". */ String actualXML = "null"; @@ -614,20 +603,21 @@ public void contentOperations() { /* * text content is accumulated in a "content" inside a local JSONObject. - * If there is only one instance, it is saved in the context (a different JSONObject - * from the calling code. and the content element is discarded. + * If there is only one instance, it is saved in the context (a different + * JSONObject + * from the calling code. and the content element is discarded. */ - xmlStr = "value 1"; + xmlStr = "value 1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("3. 2 items", 1 == jsonObject.length()); assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1"))); /* - * array-style text content (multiple tags with the same name) is + * array-style text content (multiple tags with the same name) is * accumulated in a local JSONObject with key="content" and value=JSONArray, * saved in the context, and then the local JSONObject is discarded. */ - xmlStr = "value 12true"; + xmlStr = "value 12true"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("4. 1 item", 1 == jsonObject.length()); assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray); @@ -639,10 +629,10 @@ public void contentOperations() { /* * Complex content is accumulated in a "content" field. For example, an element - * may contain a mix of child elements and text. Each text segment is - * accumulated to content. + * may contain a mix of child elements and text. Each text segment is + * accumulated to content. */ - xmlStr = "val1val2"; + xmlStr = "val1val2"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("5. 1 item", 1 == jsonObject.length()); assertTrue("5. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); @@ -656,10 +646,10 @@ public void contentOperations() { assertTrue("5. content array entry 1", "val2".equals(jsonArray.get(1))); /* - * If there is only 1 complex text content, then it is accumulated in a + * If there is only 1 complex text content, then it is accumulated in a * "content" field as a string. */ - xmlStr = "val1"; + xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("6. 1 item", 1 == jsonObject.length()); assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); @@ -670,9 +660,9 @@ public void contentOperations() { /* * In this corner case, the content sibling happens to have key=content * We end up with an array within an array, and no content element. - * This is probably a bug. + * This is probably a bug. */ - xmlStr = "val1"; + xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("7. 1 item", 1 == jsonObject.length()); assertTrue("7. jsonArray found", jsonObject.get("tag1") instanceof JSONArray); @@ -687,42 +677,41 @@ public void contentOperations() { /* * Confirm behavior of original issue */ - String jsonStr = - "{"+ - "\"Profile\": {"+ - "\"list\": {"+ - "\"history\": {"+ - "\"entries\": ["+ - "{"+ - "\"deviceId\": \"id\","+ - "\"content\": {"+ - "\"material\": ["+ - "{"+ - "\"stuff\": false"+ - "}"+ - "]"+ - "}"+ - "}"+ - "]"+ - "}"+ - "}"+ - "}"+ + String jsonStr = "{" + + "\"Profile\": {" + + "\"list\": {" + + "\"history\": {" + + "\"entries\": [" + + "{" + + "\"deviceId\": \"id\"," + + "\"content\": {" + + "\"material\": [" + + "{" + + "\"stuff\": false" + + "}" + + "]" + + "}" + + "}" + + "]" + + "}" + + "}" + + "}" + "}"; jsonObject = new JSONObject(jsonStr); xmlStr = XML.toString(jsonObject); /* * This is the created XML. Looks like content was mistaken for - * complex (child node + text) XML. - * - * - * - * - * id - * {"material":[{"stuff":false}]} - * - * - * - * + * complex (child node + text) XML. + * + * + * + * + * id + * {"material":[{"stuff":false}]} + * + * + * + * */ assertTrue("nothing to test here, see comment on created XML, above", true); } @@ -730,26 +719,28 @@ public void contentOperations() { /** * Convenience method, given an input string and expected result, * convert to JSONObject and compare actual to expected result. - * @param xmlStr the string to parse + * + * @param xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareStringToJSONObject(String xmlStr, String expectedStr) { JSONObject jsonObject = XML.toJSONObject(xmlStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** * Convenience method, given an input string and expected result, * convert to JSONObject via reader and compare actual to expected result. - * @param xmlStr the string to parse + * + * @param xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { JSONObject expectedJsonObject = new JSONObject(expectedStr); Reader reader = new StringReader(xmlStr); JSONObject jsonObject = XML.toJSONObject(reader); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** @@ -757,9 +748,9 @@ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { * JSONObject via file and compare actual to expected result. * * @param xmlStr - * the string to parse + * the string to parse * @param expectedStr - * the expected JSON string + * the expected JSON string * @throws IOException */ private void compareFileToJSONObject(String xmlStr, String expectedStr) { @@ -776,12 +767,12 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) { Reader reader = new FileReader(tempFile); try { JSONObject jsonObject = XML.toJSONObject(reader); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } finally { reader.close(); } } catch (IOException e) { - fail("Error: " +e.getMessage()); + fail("Error: " + e.getMessage()); } } @@ -791,10 +782,11 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) { @Test public void testToJSONArray_jsonOutput() { final String originalXml = "011000True"; - final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}"); + final JSONObject expectedJson = new JSONObject( + "{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}"); final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false); - Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJsonOutput, expectedJson); } /** @@ -814,23 +806,24 @@ public void testToJSONArray_reversibility() { @Test public void testToJsonXML() { final String originalXml = "011000True"; - final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"); + final JSONObject expected = new JSONObject( + "{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"); + + final JSONObject actual = XML.toJSONObject(originalXml, true); - final JSONObject actual = XML.toJSONObject(originalXml,true); - Util.compareActualVsExpectedJsonObjects(actual, expected); - + final String reverseXml = XML.toString(actual); // this reversal isn't exactly the same. use JSONML for an exact reversal // the order of the elements may be differnet as well. final String expectedReverseXml = "01011000True"; - assertEquals("length",expectedReverseXml.length(), reverseXml.length()); + assertEquals("length", expectedReverseXml.length(), reverseXml.length()); assertTrue("array contents", reverseXml.contains("011000")); assertTrue("item contents", reverseXml.contains("01")); assertTrue("title contents", reverseXml.contains("True")); } - + /** * test to validate certain conditions of XML unescaping. */ @@ -857,15 +850,16 @@ public void testUnescape() { assertEquals("{\"xml\":\"Can cope <\"}", XML.toJSONObject("Can cope &lt; ").toString()); assertEquals("Can cope < ", XML.unescape("Can cope &lt; ")); - + assertEquals("{\"xml\":\"Can cope 4\"}", XML.toJSONObject("Can cope &#x34; ").toString()); assertEquals("Can cope 4 ", XML.unescape("Can cope &#x34; ")); - } + } /** - * test passes when xsi:nil="true" converting to null (JSON specification-like nil conversion enabled) + * test passes when xsi:nil="true" converting to null (JSON specification-like + * nil conversion enabled) */ @Test public void testToJsonWithNullWhenNilConversionEnabled() { @@ -874,14 +868,15 @@ public void testToJsonWithNullWhenNilConversionEnabled() { final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration() - .withKeepStrings(false) - .withcDataTagName("content") - .withConvertNilAttributeToNull(true)); + .withKeepStrings(false) + .withcDataTagName("content") + .withConvertNilAttributeToNull(true)); assertEquals(expectedJsonString, json.toString()); } /** - * test passes when xsi:nil="true" not converting to null (JSON specification-like nil conversion disabled) + * test passes when xsi:nil="true" not converting to null (JSON + * specification-like nil conversion disabled) */ @Test public void testToJsonWithNullWhenNilConversionDisabled() { @@ -896,12 +891,10 @@ public void testToJsonWithNullWhenNilConversionDisabled() { * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testIssue537CaseSensitiveHexEscapeMinimal(){ - String xmlStr = - "\n"+ - "Neutrophils.Hypersegmented | Bld-Ser-Plas"; - String expectedStr = - "{\"root\":\"Neutrophils.Hypersegmented | Bld-Ser-Plas\"}"; + public void testIssue537CaseSensitiveHexEscapeMinimal() { + String xmlStr = "\n" + + "Neutrophils.Hypersegmented | Bld-Ser-Plas"; + String expectedStr = "{\"root\":\"Neutrophils.Hypersegmented | Bld-Ser-Plas\"}"; JSONObject xmlJSONObj = XML.toJSONObject(xmlStr, true); JSONObject expected = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(xmlJSONObj, expected); @@ -911,7 +904,7 @@ public void testIssue537CaseSensitiveHexEscapeMinimal(){ * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testIssue537CaseSensitiveHexEscapeFullFile(){ + public void testIssue537CaseSensitiveHexEscapeFullFile() { try { InputStream xmlStream = null; try { @@ -922,7 +915,7 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){ try { jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.json"); final JSONObject expected = new JSONObject(new JSONTokener(jsonStream)); - Util.compareActualVsExpectedJsonObjects(actual,expected); + Util.compareActualVsExpectedJsonObjects(actual, expected); } finally { if (jsonStream != null) { jsonStream.close(); @@ -934,7 +927,7 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){ } } } catch (IOException e) { - fail("file writer error: " +e.getMessage()); + fail("file writer error: " + e.getMessage()); } } @@ -942,14 +935,12 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){ * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testIssue537CaseSensitiveHexUnEscapeDirect(){ - String origStr = - "Neutrophils.Hypersegmented | Bld-Ser-Plas"; - String expectedStr = - "Neutrophils.Hypersegmented | Bld-Ser-Plas"; + public void testIssue537CaseSensitiveHexUnEscapeDirect() { + String origStr = "Neutrophils.Hypersegmented | Bld-Ser-Plas"; + String expectedStr = "Neutrophils.Hypersegmented | Bld-Ser-Plas"; String actualStr = XML.unescape(origStr); - - assertEquals("Case insensitive Entity unescape", expectedStr, actualStr); + + assertEquals("Case insensitive Entity unescape", expectedStr, actualStr); } /** @@ -961,7 +952,7 @@ public void testToJsonWithTypeWhenTypeConversionDisabled() { String expectedJsonString = "{\"root\":{\"id\":{\"xsi:type\":\"string\",\"content\":1234}}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration()); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } /** @@ -975,17 +966,19 @@ public void testToJsonWithTypeWhenTypeConversionEnabled() { JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); xsiTypeMap.put("integer", new XMLXsiTypeConverter() { - @Override public Integer convert(final String value) { + @Override + public Integer convert(final String value) { return Integer.valueOf(value); } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } @Test @@ -996,17 +989,19 @@ public void testToJsonWithXSITypeWhenTypeConversionEnabled() { JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); xsiTypeMap.put("integer", new XMLXsiTypeConverter() { - @Override public Integer convert(final String value) { + @Override + public Integer convert(final String value) { return Integer.valueOf(value); } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } @Test @@ -1016,12 +1011,13 @@ public void testToJsonWithXSITypeWhenTypeConversionNotEnabledOnOne() { JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } @Test @@ -1029,7 +1025,8 @@ public void testXSITypeMapNotModifiable() { Map> xsiTypeMap = new HashMap>(); XMLParserConfiguration config = new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); @@ -1037,16 +1034,18 @@ public void testXSITypeMapNotModifiable() { try { config.getXsiTypeMap().put("boolean", new XMLXsiTypeConverter() { - @Override public Boolean convert(final String value) { + @Override + public Boolean convert(final String value) { return Boolean.valueOf(value); } }); fail("Expected to be unable to modify the config"); - } catch (Exception ignored) { } + } catch (Exception ignored) { + } } @Test - public void testIndentComplicatedJsonObject(){ + public void testIndentComplicatedJsonObject() { String str = "{\n" + " \"success\": true,\n" + " \"error\": null,\n" + @@ -1109,7 +1108,7 @@ public void testIndentComplicatedJsonObject(){ " }\n" + " }\n" + " ]\n" + - "}" ; + "}"; JSONObject jsonObject = new JSONObject(str); String actualIndentedXmlString = XML.toString(jsonObject, 1); JSONObject actualJsonObject = XML.toJSONObject(actualIndentedXmlString); @@ -1175,34 +1174,32 @@ public void testIndentComplicatedJsonObject(){ JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); - } @Test - public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured(){ + public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured() { String jsonString = "{outer:{innerOne:\"\", innerTwo:\"two\"}}"; JSONObject jsonObject = new JSONObject(jsonString); String expectedXmlString = "two"; - String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(true)); + String xmlForm = XML.toString(jsonObject, "encloser", new XMLParserConfiguration().withCloseEmptyTag(true)); JSONObject actualJsonObject = XML.toJSONObject(xmlForm); JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test - public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured(){ + public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured() { String jsonString = "{outer:{innerOne:\"\", innerTwo:\"two\"}}"; JSONObject jsonObject = new JSONObject(jsonString); String expectedXmlString = "two"; - String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(false)); + String xmlForm = XML.toString(jsonObject, "encloser", new XMLParserConfiguration().withCloseEmptyTag(false)); JSONObject actualJsonObject = XML.toJSONObject(xmlForm); JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString); assertTrue(expectedJsonObject.similar(actualJsonObject)); } - @Test - public void testIndentSimpleJsonObject(){ + public void testIndentSimpleJsonObject() { String str = "{ \"employee\": { \n" + " \"name\": \"sonoo\", \n" + " \"salary\": 56000, \n" + @@ -1223,7 +1220,7 @@ public void testIndentSimpleJsonObject(){ } @Test - public void testIndentSimpleJsonArray(){ + public void testIndentSimpleJsonArray() { String str = "[ \n" + " {\"name\":\"Ram\", \"email\":\"Ram@gmail.com\"}, \n" + " {\"name\":\"Bob\", \"email\":\"bob32@gmail.com\"} \n" + @@ -1242,11 +1239,10 @@ public void testIndentSimpleJsonArray(){ JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); - } @Test - public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ + public void testIndentComplicatedJsonObjectWithArrayAndWithConfig() { try (InputStream jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.json")) { final JSONObject object = new JSONObject(new JSONTokener(jsonStream)); String actualString = XML.toString(object, null, XMLParserConfiguration.KEEP_STRINGS, 2); @@ -1255,13 +1251,13 @@ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ char[] buffer = new char[bufferSize]; StringBuilder expected = new StringBuilder(); Reader in = new InputStreamReader(xmlStream, "UTF-8"); - for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) { + for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0;) { expected.append(buffer, 0, numRead); } assertTrue(XML.toJSONObject(expected.toString()).similar(XML.toJSONObject(actualString))); } } catch (IOException e) { - fail("file writer error: " +e.getMessage()); + fail("file writer error: " + e.getMessage()); } } @@ -1272,24 +1268,25 @@ public void testMaxNestingDepthOf42IsRespected() { final int maxNestingDepth = 42; try { - XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); + XML.toJSONObject(wayTooLongMalformedXML, + XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); fail("Expecting a JSONException"); } catch (JSONException e) { assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", - e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); + e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); } } @Test public void testMaxNestingDepthIsRespectedWithValidXML() { final String perfectlyFineXML = "\n" + - " \n" + - " sonoo\n" + - " 56000\n" + - " true\n" + - " \n" + - "\n"; + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\n"; final int maxNestingDepth = 1; @@ -1299,19 +1296,19 @@ public void testMaxNestingDepthIsRespectedWithValidXML() { fail("Expecting a JSONException"); } catch (JSONException e) { assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", - e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); + e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); } } @Test public void testMaxNestingDepthWithValidFittingXML() { final String perfectlyFineXML = "\n" + - " \n" + - " sonoo\n" + - " 56000\n" + - " true\n" + - " \n" + - "\n"; + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\n"; final int maxNestingDepth = 3; @@ -1320,10 +1317,381 @@ public void testMaxNestingDepthWithValidFittingXML() { } catch (JSONException e) { e.printStackTrace(); fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " + - "parameter of the XMLParserConfiguration used"); + "parameter of the XMLParserConfiguration used"); } } -} + /** + * This method tests the efficacy of the MILESTONE 2 METHOD 2 + * As you can see the initial xml object is taken and a replacement is + * named. The method is sucessfully able to replace the subobject (the entire + * adress body including nick, name, street, and zipcode) with the new + * object (jsonReplacement). + * + * The return object from the function call is not null and the resulting + * object matches the expected JSON Object output + */ + @Test + public void testReplacement() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " \n" + + " Crista \n" + + " Crista Lopes\n" + + " \n" + + " 92614\n" + + "
\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + String expected = "{\"contact\":{\"place\":{\"zipcode\":92626,\"street\":\"Muholland Drive\"}}}"; + + Reader reader = new StringReader(xmlString); + JSONPointer pointer = new JSONPointer("/contact/address"); + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + JSONObject result = XML.toJSONObject(reader, pointer, replacement); + System.out.println(result); + assertNotNull("Resulting JSONObject should not be null", result); + assertEquals(expected, result.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + + /** + * Further test efficacy of MILESTONE 2 METHOD 2 implementation by checking a + * different + * XMLString for a subObject. The structure of this one is different and + * therefore allows + * for a different means by which to verify the working order of this method. + * it checks to see if the subObject key has been placed within the xmlString + * and + * then it further checks if these values are able to be accessed. + */ + @Test + public void testReplacement2() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer to locate the subObject that needs to be extracted + JSONPointer pointer = new JSONPointer("/contact/address"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + // getting the answer + JSONObject result = XML.toJSONObject(reader, pointer, replacement); + + JSONObject address = result.getJSONObject("contact").getJSONObject("place"); + assert result.getJSONObject("contact").has("place"); + assert address.getString("street").equals("Muholland Drive"); + assert address.getInt("zipcode") == (92626); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + + /** + * MILESTONE 2 METHOD 2 + * + * What happens if we try to use a pointer to a subObject that doesn't exist? + * We would expect that nothing happens and the same object is returned + */ + @Test + public void testReplacementNull() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer that looks for subObject that doesn't exist + JSONPointer pointer = new JSONPointer("/contact/dog"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + // expected answer + String expected = XML.toJSONObject(xmlString).toString(); + + // getting the answer + JSONObject actual = XML.toJSONObject(reader, pointer, replacement); + + assertEquals(expected, actual.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + + /** + * MILESTONE 2 METHOD 1 + */ + @Test + public void customXmlPathExample1() { + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String json = "{\"zipcode\":92614,\"street\":\"Ave of Nowhere\"}\n"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), new JSONPointer("/contact/address")); + JSONObject expected = new JSONObject(json); + + assertTrue(expected.similar(actual)); + } + + /** + * MILESTONE 2 METHOD 1 + */ + @Test + public void customXmlPathExample2() { + String xml = "\n" + + "\n" + + "uhsafouash\n" + + "
\n" + + " \n" + + " Crista \n" + + " Crista Lopes\n" + + "\n" + + " 92614\n" + + "
\n" + + "shah\n" + + "
"; + String json = "{\"nick\":\"Crista\",\"name\":\"Crista Lopes\"}"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), new JSONPointer("/contact/address/street")); + JSONObject expected = new JSONObject(json); + + assertTrue(expected.similar(actual)); + + } + + /** + * MILESTONE 2 METHOD 1 + */ + @Test + public void customXmlPathExample3() { + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String json = "{\"street\":\"Ave of Nowhere\"}\n"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), new JSONPointer("/contact/address/street")); + JSONObject expected = new JSONObject(json); + + assertTrue(expected.similar(actual)); + } + + /** + * MILESTONE III TEST + * + * Determines working order of new toJSONObject method + * successfully transforms all keys based upon input function + * parameters + */ + @Test + public void updateKeys() { + Function addPrefix = (str) -> "swe262_" + str.toUpperCase(); + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String expected = "{\"swe262_CONTACT\":{\"swe262_NAME\":\"Crista Lopes\",\"swe262_NICK\":\"Crista\",\"swe262_ADDRESS\":{\"swe262_STREET\":\"Ave of Nowhere\",\"swe262_ZIPCODE\":92614}}}"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), addPrefix); + assertEquals(expected, actual.toString()); + } + + /* + * MILESTONE III TEST + * + * Deletes part of the key + */ + @Test + public void updateKeys2() { + Function change = (str) -> str.substring(0, str.length() - 2); + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String expected = "{\"conta\":{\"addre\":{\"stre\":\"Ave of Nowhere\",\"zipco\":92614},\"na\":\"Crista Lopes\",\"ni\":\"Crista\"}}"; + + JSONObject actual = XML.toJSONObject(new StringReader(xml), change); + assertEquals(expected, actual.toString()); + } + + /* + * MILESTONE III TEST + * + * removes key + */ + @Test + public void updateKeys3() { + Function change = (str) -> " "; + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String expected = "{\" \":{\" \":[{\" \":[\"Ave of Nowhere\",92614]},\"Crista\",\"Crista Lopes\"]}}"; + + + JSONObject actual = XML.toJSONObject(new StringReader(xml), change); + + System.out.println(actual.toString()); + + assertEquals(expected, actual.toString()); + } + + /* + * MILESTONE V TEST + * + * tests the xml to json conversion capabilities on simple object + */ + @Test + public void testV() throws InterruptedException { + String xml = "Value"; + CountDownLatch latch = new CountDownLatch(1); // To wait for the async operation + AtomicReference resultRef = new AtomicReference<>(); + + XML.toJsonObject( + new StringReader(xml), + jsonObject -> { + resultRef.set(jsonObject); + latch.countDown(); // Signal completion of async operation + }, + exception -> fail("Exception should not occur")); + + latch.await(); // Wait for the async operation to complete + JSONObject expected = new JSONObject("{\"root\":{\"element\":\"Value\"}}"); + assertEquals(expected.toString(), resultRef.get().toString()); + } + /** + * Test async XML to JSON error handling capacities + * @throws InterruptedException + */ + @Test + public void testV2() throws InterruptedException { + String invalidXml = "Value"; // Missing closing tag + CountDownLatch latch = new CountDownLatch(1); + AtomicReference exceptionRef = new AtomicReference<>(); + + XML.toJsonObject( + new StringReader(invalidXml), + jsonObject -> fail("Success callback should not be called for invalid XML"), + exception -> { + exceptionRef.set(exception); + latch.countDown(); + }); + + latch.await(); + assertNotNull(exceptionRef.get()); + assertTrue(exceptionRef.get() instanceof JSONException); + } + /** + * Want to test if XML is being parsed properly on larger object + */ + @Test + public void testV3() { + + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonString = "{\"contact\":{\"nick\":\"Crista\",\"address\":{\"zipcode\":92614,\"street\":\"Ave of Nowhere\"},\"name\":\"Crista Lopes\"}}"; + AtomicBoolean isJsonAsExpected = new AtomicBoolean(false); + AtomicReference exceptionRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + XML.toJsonObject( + new StringReader(xml), + jsonObject -> { + System.out.println(jsonObject.toString()); + if (jsonObject != null && jsonObject.toString().equals(jsonString)) { + isJsonAsExpected.set(true); + System.out.println(jsonObject.toString()); + } + latch.countDown(); + }, + exception -> { + exceptionRef.set(exception); + latch.countDown(); + }); + try { + boolean completed = latch.await(2, TimeUnit.SECONDS); + assertTrue("Asynchronous task did complete in time", completed); + + assertNull("Exception occurred during processing", exceptionRef.get()); + assertTrue("JSON as expected", isJsonAsExpected.get()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Restore interrupted status + fail("Test interrupted"); + } + } + +}