From 1a0cf3ec1eea2f111ff51d3f3fdfee12ec9592fe Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Tue, 5 Apr 2016 22:38:36 +0300 Subject: [PATCH 01/10] introduce helper classes to process a JSONObject and its nodes --- .../net/minidev/json/actions/PathLocator.java | 66 +++++ .../net/minidev/json/actions/PathRemover.java | 74 ++++++ .../minidev/json/actions/PathReplicator.java | 75 ++++++ .../minidev/json/actions/PathsRetainer.java | 85 ++++++ .../actions/navigate/CopyPathsAction.java | 141 ++++++++++ .../json/actions/navigate/JSONNavigator.java | 189 ++++++++++++++ .../json/actions/navigate/JSONPath.java | 243 ++++++++++++++++++ .../json/actions/navigate/NavigateAction.java | 102 ++++++++ .../json/actions/navigate/package-info.java | 28 ++ .../json/actions/traverse/JSONTraverser.java | 84 ++++++ .../actions/traverse/LocatePathsAction.java | 85 ++++++ .../json/actions/traverse/RemoveAction.java | 82 ++++++ .../json/actions/traverse/RetainAction.java | 114 ++++++++ .../json/actions/traverse/TraverseAction.java | 62 +++++ .../json/actions/traverse/package-info.java | 31 +++ .../json/test/actions/JSONPathTest.java | 93 +++++++ .../json/test/actions/PathLocatorTest.java | 159 ++++++++++++ .../json/test/actions/PathRemoverTest.java | 136 ++++++++++ .../json/test/actions/PathReplicatorTest.java | 232 +++++++++++++++++ .../json/test/actions/PathsRetainerTest.java | 168 ++++++++++++ 20 files changed, 2249 insertions(+) create mode 100755 json-smart/src/main/java/net/minidev/json/actions/PathLocator.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/PathRemover.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java create mode 100644 json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java create mode 100644 json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java create mode 100755 json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java create mode 100755 json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java create mode 100755 json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java create mode 100755 json-smart/src/test/java/net/minidev/json/test/actions/PathReplicatorTest.java create mode 100755 json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java new file mode 100755 index 00000000..1e80a1f1 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java @@ -0,0 +1,66 @@ +package net.minidev.json.actions; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.traverse.JSONTraverser; +import net.minidev.json.actions.traverse.LocatePathsAction; +import net.minidev.json.actions.traverse.TraverseAction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Searches for paths in a {@link JSONObject} and returns those found. + *

+ * Traverses the specified {@link JSONObject} searching for nodes whose paths (from the root down) match + * any of the user-specified paths. The paths that match are returned. + *

+ * A path to locate must be specified in the n-gram format - a list of keys from the root down separated by dots: + * K0[[[[.K1].K2].K3]...] + *
+ * A key to the right of a dot is a direct child of a key to the left of a dot. Keys with a dot in their name are + * not supported. + *

+ * + * @author adoneitan@gmail.com + */ +public class PathLocator +{ + private List pathsToFind; + + public PathLocator(JSONArray pathsToFind) + { + if (pathsToFind == null || pathsToFind.isEmpty()) { + this.pathsToFind = Collections.emptyList(); + } + else + { + this.pathsToFind = new ArrayList(); + for (Object s : pathsToFind) { + this.pathsToFind.add((String) s); + } + } + } + + public PathLocator(List pathsToFind) + { + this.pathsToFind = pathsToFind == null || pathsToFind.size() == 0 ? + Collections.emptyList() : pathsToFind; + } + + public PathLocator(String... pathsToFind) + { + this.pathsToFind = pathsToFind == null || pathsToFind.length == 0 ? + Collections.emptyList() : Arrays.asList(pathsToFind); + } + + public List find(JSONObject object) + { + TraverseAction action = new LocatePathsAction(this.pathsToFind); + JSONTraverser traversal = new JSONTraverser(action); + traversal.traverse(object); + return (List) action.result(); + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java new file mode 100755 index 00000000..fb3e1bd8 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java @@ -0,0 +1,74 @@ +package net.minidev.json.actions; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.traverse.JSONTraverser; +import net.minidev.json.actions.traverse.RemoveAction; +import net.minidev.json.actions.traverse.TraverseAction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Removes branches of nodes from a {@link JSONObject} matching the list of user-specified paths. + *

+ * A path to remove must be specified in the n-gram format - a list of keys from the root down separated by dots: + * K0[[[[.K1].K2].K3]...] + *
+ * A key to the right of a dot is a direct child of a key to the left of a dot. Keys with a dot in their name are + * not supported. + *

+ * Usage Example: + *

+ * To remove the field k1.k2 from the {@link JSONObject} {k1:{k2:v2}, k3:{k4:v4}} use the remover like so: + *

+ * PathRemover pr = new PathRemover("k1.k2");
+ * JSONObject cleanObject = pr.remove(new JSONObject(...));
+ * 
+ * The resulting object 'cleanObject' would be {k1:{k3:{k4:v4}}} + *

+ * See unit tests for more examples + * + * @author adoneitan@gmail.com + * + */ +public class PathRemover +{ + private List pathsToRemove; + + public PathRemover(JSONArray pathsToRemove) + { + if (pathsToRemove == null || pathsToRemove.isEmpty()) { + this.pathsToRemove = Collections.emptyList(); + } + else + { + this.pathsToRemove = new ArrayList(); + for (Object s : pathsToRemove) { + this.pathsToRemove.add((String) s); + } + } + } + + public PathRemover(List pathsToRemove) + { + this.pathsToRemove = pathsToRemove == null || pathsToRemove.size() == 0 ? + Collections.emptyList() : pathsToRemove; + } + + public PathRemover(String... pathsToRemove) + { + this.pathsToRemove = pathsToRemove == null || pathsToRemove.length == 0 ? + Collections.emptyList() : Arrays.asList(pathsToRemove); + } + + public JSONObject remove(JSONObject objectToClean) + { + TraverseAction strategy = new RemoveAction(this.pathsToRemove); + JSONTraverser traversal = new JSONTraverser(strategy); + traversal.traverse(objectToClean); + return (JSONObject) strategy.result(); + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java b/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java new file mode 100755 index 00000000..b708374f --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java @@ -0,0 +1,75 @@ +package net.minidev.json.actions; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.navigate.CopyPathsAction; +import net.minidev.json.actions.navigate.JSONNavigator; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Creates a copy of a {@link JSONObject} consisting only of the nodes on the user-specified paths. + *

+ * Paths that do not exist in the specified object are ignored silently. Specifying an empty list of paths + * to copy or only non-existent paths will result in an empty object being returned. + *

+ * A path to copy must be specified in the n-gram format - a list of keys from the root down separated by dots: + * K0[[[[.K1].K2].K3]...] + *
+ * A key to the right of a dot is a direct child of a key to the left of a dot. Keys with a dot in their name are + * not supported. + *

+ * Sample usage: + *

+ * To replicate the branch k1.k2 from {k1:{k2:v2}, k3:{k4:v4}} use the {@link PathReplicator} like so: + *

+ * PathReplicator pr = new {@link PathReplicator}("k1.k2")
+ * JSONObject copiedObject = pr.copy(new JSONObject(...))
+ * 
+ * The resulting object 'copiedObject' would be {k1:{k2:v2}} + *

+ * see unit tests for more examples + * + * @author adoneitan@gmail.com + */ +public class PathReplicator +{ + protected List pathsToCopy; + + public PathReplicator(JSONArray pathsToCopy) + { + if (pathsToCopy == null || pathsToCopy.isEmpty()) { + this.pathsToCopy = Collections.emptyList(); + } + else + { + this.pathsToCopy = new LinkedList(); + for (Object s : pathsToCopy) { + this.pathsToCopy.add((String) s); + } + } + } + + public PathReplicator(List pathsToCopy) + { + this.pathsToCopy = pathsToCopy == null || pathsToCopy.size() == 0 ? + Collections.emptyList() : pathsToCopy; + } + + public PathReplicator(String... pathsToCopy) + { + this.pathsToCopy = pathsToCopy == null || pathsToCopy.length == 0 ? + Collections.emptyList() : new LinkedList(Arrays.asList(pathsToCopy)); + } + + public JSONObject replicate(JSONObject sourceObj) throws Exception + { + CopyPathsAction s = new CopyPathsAction(); + JSONNavigator n = new JSONNavigator(s, pathsToCopy); + n.nav(sourceObj); + return (JSONObject) s.result(); + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java new file mode 100755 index 00000000..6dc146dd --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java @@ -0,0 +1,85 @@ +package net.minidev.json.actions; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.traverse.RetainAction; +import net.minidev.json.actions.traverse.JSONTraverser; +import net.minidev.json.actions.traverse.LocatePathsAction; +import net.minidev.json.actions.traverse.TraverseAction; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Retains branches of nodes of a {@link JSONObject} matching the list of user-specified paths. + *

+ * A path to copy must be specified in the n-gram format - a list of keys from the root down separated by dots: + * K0[[[[.K1].K2].K3]...] + *
+ * A key to the right of a dot is a direct child of a key to the left of a dot. Keys with a dot in their name are + * not supported. + *

+ * Example: + *

+ * to retain the field k1.k2 in the {@link JSONObject} {k1:{k2:v1}, k3:{k4:v2}} instantiate the retainer like so: + * new JSONObjectCleaner("k1.k2") + * The resulting object would be {k1:{k2:v1}} + *

+ * See unit tests in JSONObjectRetainerTest for more examples + * + * @author adoneitan@gmail.com + */ +public class PathsRetainer +{ + protected List pathsToRetain; + + public PathsRetainer(JSONArray pathsToRetain) + { + if (pathsToRetain == null || pathsToRetain.isEmpty()) { + this.pathsToRetain = Collections.emptyList(); + } + else + { + this.pathsToRetain = new LinkedList(); + for (Object s : pathsToRetain) { + this.pathsToRetain.add((String) s); + } + } + } + + public PathsRetainer(List pathsToRetain) + { + this.pathsToRetain = pathsToRetain == null || pathsToRetain.size() == 0 ? + Collections.emptyList() : pathsToRetain; + } + + public PathsRetainer(String... pathsToRetain) + { + this.pathsToRetain = pathsToRetain == null || pathsToRetain.length == 0 ? + Collections.emptyList() : new LinkedList(Arrays.asList(pathsToRetain)); + } + + public JSONObject retain(JSONObject object) + { + /** + * a path to retain which contains a path in the object, but is not itself a path in the object, + * will cause the sub-path to be retain although it shouldn't: + * object = {k0:v0} retain = {k0.k1} + * so the false path to retain has to be removed from the pathsToRetain list. + * + * The {@link LocatePathsAction} returns only paths which exist in the object. + */ + TraverseAction locateAction = new LocatePathsAction(pathsToRetain); + JSONTraverser t1 = new JSONTraverser(locateAction); + t1.traverse(object); + List realPathsToRetain = (List) locateAction.result(); + + //now reduce the object using only existing paths + TraverseAction reduce = new RetainAction(realPathsToRetain); + JSONTraverser t2 = new JSONTraverser(reduce); + t2.traverse(object); + return (JSONObject) reduce.result(); + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java new file mode 100755 index 00000000..e044167c --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java @@ -0,0 +1,141 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +import java.util.Collection; +import java.util.Stack; + +/** + * Creates a copy of a {@link JSONObject} containing just the nodes on the paths specified. + *

+ * Specified paths that do not exist in the source object are ignored silently. + * Specifying an empty list of paths to navigate or only non-existent paths will result in an empty + * object being returned. + *

+ * See package-info for more details + *

+ * Example: + *

+ * To copy the branch k1.k2 from {k1:{k2:v1}, k3:{k4:v2}} instantiate the copier like so: + * new JSONObjectCopier("k1.k2") + * The resulting copy would be {k1:{k2:v1}} + *

+ * See unit tests for more examples + * + * @author adoneitan@gmail.com + * + */ +public class CopyPathsAction implements NavigateAction +{ + private JSONObject destTree; + private JSONObject destBranch; + private Stack destNodeStack; + + @Override + public boolean handleNavigationStart(JSONObject source, Collection pathsToCopy) + { + if (source == null) + { + destTree = null; + return false; + } + + destTree = new JSONObject(); + if (pathsToCopy == null || pathsToCopy.size() == 0) { + return false; + } + + /** + * using ARRAY_PUSH which adds any new entry encountered in arrays, effectively + * allowing duplicate objects in the array, which is supported by Gigya + */ + return true; + } + + @Override + public boolean handleObjectStartAndRecur(JSONPath jp, JSONObject o) + { + //reached JSONObject node - instantiate it and recur + handleNewNode(jp, new JSONObject()); + return true; + } + + private void handleNewNode(JSONPath jp, Object node) + { + if (destNodeStack.peek() instanceof JSONObject) { + ((JSONObject) destNodeStack.peek()).put(jp.curr(), node); + } + else if (destNodeStack.peek() instanceof JSONArray) { + ((JSONArray) destNodeStack.peek()).add(node); + } + destNodeStack.push(node); + } + + @Override + public boolean handleArrayStartAndRecur(JSONPath jp, JSONArray o) + { + //reached JSONArray node - instantiate it and recur + handleNewNode(jp, new JSONArray()); + return true; + } + + @Override + public void handlePrematureNavigatedBranchEnd(JSONPath jp, Object source) { + throw new IllegalArgumentException("branch is shorter than path - path not found in source: '" + jp.origin() + "'"); + } + + @Override + public void handleObjectLeaf(JSONPath jp, Object o) { + ((JSONObject) destNodeStack.peek()).put(jp.curr(), o); + } + + @Override + public void handleArrayLeaf(int arrIndex, Object o) { + ((JSONArray) destNodeStack.peek()).add(o); + } + + @Override + public void handleArrayEnd(JSONPath jp) { + destNodeStack.pop(); + } + + @Override + public void handleObjectEnd(JSONPath jp) { + destNodeStack.pop(); + } + + @Override + public boolean handleNextPath(String path) + { + destBranch = new JSONObject(); + destNodeStack = new Stack(); + destNodeStack.push(destBranch); + return true; + } + + @Override + public void handlePathEnd(String path) { + destTree.merge(destBranch); + } + + @Override + public boolean failPathSilently(String path, Exception e) { + return false; + } + + @Override + public boolean failPathFast(String path, Exception e) { + return false; + } + + @Override + public void handleNavigationEnd() { + + } + + @Override + public Object result() { + return destTree; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java new file mode 100755 index 00000000..b5789af9 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java @@ -0,0 +1,189 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Navigates only the branches of a {@link JSONObject} corresponding to the paths specified. + *

+ * For each specified path to navigate, the {@link JSONNavigator} only traverses the matching + * branch. + *

+ * The navigator accepts an action and provides callback hooks for it to act on the traversed + * nodes at each significant step. See {@link NavigateAction}. + *

+ * See package-info for more details + *

+ * Example: + *

+ * To navigate the branch k1.k2 of the object {"k1":{"k2":"v1"}, "k3":{"k4":"v2"}} instantiate + * the navigator like so: new JSONNavigator("k1.k2") + * + * @author adoneitan@gmail.com + * + */ +public class JSONNavigator +{ + protected List pathsToNavigate; + protected NavigateAction action; + + public JSONNavigator(NavigateAction action, JSONArray pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("NavigateAction cannot be null"); + } + this.action = action; + if (pathsToNavigate == null || pathsToNavigate.isEmpty()) { + this.pathsToNavigate = Collections.emptyList(); + } + else + { + this.pathsToNavigate = new LinkedList(); + for (Object s : pathsToNavigate) { + this.pathsToNavigate.add((String) s); + } + } + } + + public JSONNavigator(NavigateAction action, List pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("NavigateAction cannot be null"); + } + this.action = action; + this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.size() == 0 ? + Collections.emptyList() : pathsToNavigate; + } + + public JSONNavigator(NavigateAction action, String... pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("NavigateAction cannot be null"); + } + this.action = action; + this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.length == 0 ? + Collections.emptyList() : new LinkedList(Arrays.asList(pathsToNavigate)); + } + + public void nav(JSONObject object) throws Exception + { + if (action.handleNavigationStart(object, pathsToNavigate)) + { + for (String path: pathsToNavigate) + { + try + { + if (path != null && !path.equals("") && action.handleNextPath(path)) + { + JSONPath jp = new JSONPath(path); + nav(object, jp); + action.handlePathEnd(path); + } + } + catch (Exception e) + { + if (action.failPathSilently(path ,e)) { + break; + } + else if (action.failPathFast(path, e)) { + throw e; + } + } + } + } + action.handleNavigationEnd(); + } + + private void nav(JSONObject source, JSONPath jp) + { + if (jp.hasNext()) + { + if (source == null) + { + //source is null - navigation impossible + return; + } + String next = jp.next(); + if (!source.containsKey(next)) + { + // reached end of branch in source before end of specified json path - + // the specified path is illegal because it does not exist in the source. + action.handlePrematureNavigatedBranchEnd(jp, source); + } + else if (source.get(next) instanceof JSONObject && action.handleObjectStartAndRecur(jp, (JSONObject) source.get(next))) + { + //reached JSONObject node - handle it and recur into it + nav((JSONObject) source.get(next), jp); + } + else if (source.get(next) instanceof JSONArray && action.handleArrayStartAndRecur(jp, (JSONArray) source.get(next))) + { + //reached JSONArray node - handle it and recur into it + nav((JSONArray) source.get(next), jp); + } + else if (jp.hasNext()) + { + // reached leaf node (not a container) in source but specified path expects children - + // the specified path is illegal because it does not exist in the source. + action.handlePrematureNavigatedBranchEnd(jp, source.get(next)); + } + else if (!jp.hasNext()) + { + //reached leaf in source and specified path is also at leaf -> handle it + action.handleObjectLeaf(jp, source.get(next)); + } + else + { + throw new IllegalStateException("fatal: unreachable code reached at '" + jp.origin() + "'"); + } + } + action.handleObjectEnd(jp); + } + + private void nav(JSONArray source, JSONPath jp) + { + if (source == null) + { + //array is null - navigation impossible + return; + } + int arrIndex = 0; + for (Object arrItem : source.toArray()) + { + if (arrItem instanceof JSONObject && action.handleObjectStartAndRecur(jp, (JSONObject) arrItem)) + { + // clone the path so that for each JSONObject in the array, + // the iterator continues from the same position in the path + JSONPath jpClone = getClone(jp); + nav((JSONObject) arrItem, jpClone); + } + else if (arrItem instanceof JSONArray) + { + throw new IllegalArgumentException("illegal json - found array nested inside array at: '" + jp.origin() + "'"); + } + else if (!jp.hasNext()) + { + //reached leaf - handle it + action.handleArrayLeaf(arrIndex, arrItem); + } + arrIndex++; + } + action.handleArrayEnd(jp); + + } + + private JSONPath getClone(JSONPath jp) + { + try + { + return jp.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("failed to clone json path", e); + } + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java new file mode 100755 index 00000000..2bf589ff --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java @@ -0,0 +1,243 @@ +package net.minidev.json.actions.navigate; + + +import net.minidev.json.JSONObject; + +import java.util.Arrays; +import java.util.List; +import java.util.ListIterator; + +/** + * {@link JSONPath} represents an n-gram formatted path + * corresponding to a branch in a {@link JSONObject} + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public class JSONPath +{ + protected enum Step {NONE, NEXT, PREV} + protected final String path; + protected List keys; + protected ListIterator keysItr; + protected String currKey; + protected Step lastStep; + protected StringBuilder origin; + protected StringBuilder remainder; + + public JSONPath(String path) + { + checkPath(path); + this.path = path; + this.keys = Arrays.asList(path.split("\\.")); + reset(); + } + + public void reset() + { + keysItr = keys.listIterator(); + currKey = ""; + lastStep = Step.NONE; + origin = new StringBuilder(""); + remainder = new StringBuilder(path); + } + + public boolean hasNext() { + return keysItr.hasNext(); + } + + public int nextIndex() { + return keysItr.nextIndex(); + } + + public String next() + { + currKey = keysItr.next(); + /** when changing direction the {@link ListIterator} does not really + * move backward so an extra step is performed */ + if (!lastStep.equals(Step.PREV)) { + originIncrement(); + remainderDecrement(); + } + lastStep = Step.NEXT; + return currKey; + } + + public boolean hasPrev() { + return keysItr.hasPrevious(); + } + + public int prevIndex() { + return keysItr.previousIndex(); + } + + public String prev() + { + String temp = currKey; + currKey = keysItr.previous(); + /** when changing direction the {@link ListIterator} does not really + * move backward so an extra step is performed */ + if (!lastStep.equals(Step.NEXT)) { + remainderIncrement(temp); + originDecrement(); + } + lastStep = Step.PREV; + return currKey; + } + + private void remainderDecrement() + { + if (length() == 1) + remainder = new StringBuilder(""); + else if (remainder.indexOf(".") < 0) + remainder = new StringBuilder(""); + else + remainder.delete(0, remainder.indexOf(".") + 1); + } + + private void originDecrement() + { + if (length() == 1) + origin = new StringBuilder(""); + else if (origin.indexOf(".") < 0) + origin = new StringBuilder(""); + else + origin.delete(origin.lastIndexOf("."), origin.length()); + } + + private void originIncrement() + { + if (origin.length() != 0) { + origin.append('.'); + } + origin.append(currKey); + } + + private void remainderIncrement(String prev) + { + if (remainder.length() == 0) + remainder = new StringBuilder(prev); + else + remainder = new StringBuilder(prev).append('.').append(remainder); + } + + /** + * @return An n-gram path from the first key to the current key (inclusive) + */ + public String path() { + return path; + } + + /** + * @return An n-gram path from the first key to the current key (inclusive) + */ + public String origin() { + return origin.toString(); + } + + /** + * @return An n-gram path from the current key to the last key (inclusive) + */ + public String remainder() { + return remainder.toString(); + } + + /** + * @return first element in the JSONPath + */ + public String first() { + return keys.get(0); + } + + /** + * @return last element in the JSONPath + */ + public String last() { + return keys.get(keys.size() - 1); + } + + /** + * @return current element pointed to by the path iterator + */ + public String curr() { + return currKey; + } + + public int length() { + return keys.size(); + } + + public String subPath(int firstIndex, int lastIndex) + { + if (lastIndex < firstIndex) { + throw new IllegalArgumentException("bad call to subPath"); + } + StringBuilder sb = new StringBuilder(path.length()); + for (int i = firstIndex; i <= lastIndex; i++) + { + sb.append(keys.get(i)); + if (i < lastIndex) { + sb.append('.'); + } + } + sb.trimToSize(); + return sb.toString(); + } + + private void checkPath(String path) + { + if (path == null || path.equals("")) + throw new IllegalArgumentException("path cannot be null or empty"); + if (path.startsWith(".") || path.endsWith(".") || path.contains("src/main")) + throw new IllegalArgumentException("path cannot start or end with '.' or contain '..'"); + } + + @Override + public JSONPath clone() throws CloneNotSupportedException + { + JSONPath cloned = new JSONPath(this.path); + while (cloned.nextIndex() != this.nextIndex()) { + cloned.next(); + } + if (cloned.prevIndex() != this.prevIndex()) { + cloned.prev(); + } + cloned.lastStep = this.lastStep; + cloned.currKey = new String(this.currKey); + cloned.origin = new StringBuilder(this.origin); + cloned.remainder = new StringBuilder(this.remainder); + return cloned; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JSONPath jsonPath = (JSONPath) o; + + return path().equals(jsonPath.path()) && + hasNext() == jsonPath.hasNext() && + hasPrev() == jsonPath.hasPrev() && + curr().equals(jsonPath.curr()) && + origin().equals(jsonPath.origin()) && + remainder().equals(jsonPath.remainder()) && + lastStep == jsonPath.lastStep; + + } + + @Override + public int hashCode() { + int result = path.hashCode(); + result = 31 * result + keys.hashCode(); + result = 31 * result + keysItr.hashCode(); + result = 31 * result + currKey.hashCode(); + result = 31 * result + lastStep.hashCode(); + result = 31 * result + origin.hashCode(); + result = 31 * result + remainder.hashCode(); + return result; + } +} + + diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java new file mode 100755 index 00000000..72af94f7 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java @@ -0,0 +1,102 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +import java.util.Collection; + +/** + * An interface for a processing action on the nodes of a {@link JSONObject} while navigating its branches. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public interface NavigateAction +{ + /** + * called before any navigation of the {@link JSONObject} starts + * @return true if navigation should start at all + */ + boolean handleNavigationStart(JSONObject objectToNavigate, Collection pathsToNavigate); + + /** + * called before navigation of a new path starts + * @return true if the specified path should be navigated + */ + boolean handleNextPath(String path); + + /** + * reached end of branch in source before end of specified json path - + * the specified path does not exist in the source. + */ + void handlePrematureNavigatedBranchEnd(JSONPath jp, Object source); + + /** + * called after the navigation of a path ends + */ + void handlePathEnd(String path); + + /** + * called if navigation of a path throws an exception + * @return true if the failure on this path should not abort the rest of the navigation + */ + boolean failPathSilently(String path, Exception e); + + /** + * called if navigation of a path throws an exception + * @return true if the failure on this path should abort the rest of the navigation + */ + boolean failPathFast(String path, Exception e); + + /** + * called when an object node is encountered on the path + * @return true if the navigator should navigate into the object + */ + boolean handleObjectStartAndRecur(JSONPath jp, JSONObject sourceNode); + + /** + * called when an array node is encountered on the path + * @return true if the navigator should navigate into the array + */ + boolean handleArrayStartAndRecur(JSONPath jp, JSONArray sourceNode); + + /** + * called when a leaf node is reached in a JSONObject. + * a leaf in a JSONObject is a key-value pair where the value is not a container itself + * (it is not a JSONObject nor a JSONArray) + * @param jp - the JsonPath pointing to the leaf + */ + void handleObjectLeaf(JSONPath jp, Object value); + + /** + * called when a leaf in a JSONArray is reached. + * a leaf in a JSONArray is a non-container item + * (it is not a JSONObject nor a JSONArray) + * @param arrIndex - the index of the item in the JSONArray + * @param arrItem - the item + */ + void handleArrayLeaf(int arrIndex, Object arrItem); + + /** + * called after all the items of an array have been visited + * @param jp - the JsonPath pointing to the array + */ + void handleArrayEnd(JSONPath jp); + + /** + * called after all the entries of a JSONObject have been visited + * @param jp - the JsonPath pointing to the object + */ + void handleObjectEnd(JSONPath jp); + + /** + * called after all navigation ends, and just before the navigation method exits + */ + void handleNavigationEnd(); + + /** + * holds the result of the navigation, as assigned by the action implementing this interface + */ + Object result(); +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java new file mode 100644 index 00000000..537cf36e --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java @@ -0,0 +1,28 @@ +/** + * Navigate user-specified paths in a {@link net.minidev.json.JSONObject} and process them + *

+ * {@link net.minidev.json.actions.navigate.JSONNavigator} only navigates through branches corresponding + * to user-specified paths. For each path, the navigation starts at the root and moves down the branch. + *

+ * The {@link net.minidev.json.actions.navigate.JSONNavigator} accepts a + * {@link net.minidev.json.actions.navigate.NavigateAction} and provides callback hooks at each significant + * step which the {@link net.minidev.json.actions.navigate.NavigateAction} may use to process + * the nodes. + *

+ * A path to navigate must be specified in the n-gram format - a list of keys from the root down separated by dots: + * K0[[[[.K1].K2].K3]...] + *
+ * A key to the right of a dot is a direct child of a key to the left of a dot. Keys with a dot in their name are + * not supported. + *

+ * Sample usage: + *

+ * NavigateAction navAction = new NavigateAction(){...};
+ * JSONNavigator jsonNav = new JSONNavigator(navAction, "foo.bar.path");
+ * jsonNav.nav(new JSONObject(...));
+ * Object result = navAction.result();
+ * 
+ * + * @author adoneitan@gmail.com + */ +package net.minidev.json.actions.navigate; \ No newline at end of file diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java new file mode 100755 index 00000000..ce543a20 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java @@ -0,0 +1,84 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +import java.util.Iterator; +import java.util.Map; + +/** + * Traverses every node of a {@link JSONObject} + *

+ * {@link JSONTraverser} accepts an action and provides callback hooks for it to act + * on the traversed nodes at each significant step. See {@link TraverseAction}. + *

+ * A key to the right of a dot is a direct child of a key to the left of a dot. + * Keys with a dot in their name are not supported. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + * + */ +public class JSONTraverser +{ + private TraverseAction action; + + public JSONTraverser(TraverseAction action) + { + this.action = action; + } + + public void traverse(JSONObject object) + { + if (action.handleStart(object)){ + breadthFirst("", object); + } + action.handleEnd(); + } + + private void breadthFirst(String fullPathToObject, JSONObject jsonObject) + { + if (jsonObject == null || jsonObject.entrySet() == null) { + return; + } + Iterator> it = jsonObject.entrySet().iterator(); + while (it.hasNext() && action.handleNext()) + { + Map.Entry entry = it.next(); + if (entry.getKey().contains(".") && !action.handleDotChar()) + { + //a dot char '.' in the key is not supported by the action, abandon this path + continue; + } + String fullPathToEntry = "".equals(fullPathToObject) ? entry.getKey() : fullPathToObject + "." + entry.getKey(); + if (action.handleEntryAndIgnoreChildren(fullPathToEntry, it, entry)) + { + continue; + } + else if (entry.getValue() instanceof JSONObject && action.handleJSONObjectChild()) + { + breadthFirst(fullPathToEntry, (JSONObject) entry.getValue()); + } + else if (entry.getValue() instanceof JSONArray && action.handleJSONArrayChild()) + { + breadthFirst(fullPathToEntry, (JSONArray) entry.getValue()); + } + } + } + + private void breadthFirst(String fullPathToObject, JSONArray jsonArray) + { + for (Object arrItem : jsonArray.toArray()) + { + if (arrItem instanceof JSONObject) + { + breadthFirst(fullPathToObject, (JSONObject) arrItem); + } + else if (arrItem instanceof JSONArray) + { + breadthFirst(fullPathToObject, (JSONArray) arrItem); + } + } + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java new file mode 100755 index 00000000..6d7a3709 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java @@ -0,0 +1,85 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONObject; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Searches for paths in a {@link JSONObject} and returns those found + *

+ * A path is not removed from the user-specified list once its processing is over, + * because identical objects in the same array are supported by this action. + *

+ * See package-info for more details + *

+ * See unit tests for examples + * + * @author adoneitan@gmail.com + * + */ +public class LocatePathsAction implements TraverseAction +{ + protected List pathsFound; + protected List pathsToFind; + + /** + * + * @param pathsToFind A path to a field in the {@link JSONObject} should be specified in n-gram format where keys are chained: + * k0[[[.k1].k2]...] + */ + public LocatePathsAction(List pathsToFind) + { + this.pathsToFind = pathsToFind; + pathsFound = new LinkedList(); + } + + @Override + public boolean handleStart(JSONObject object) + { + return object != null && pathsToFind != null && pathsToFind.size() > 0; + } + + @Override + public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + { + if (pathsToFind.contains(pathToEntry)) + { + //reached end of path that is being searched + pathsFound.add(pathToEntry); + } + return false; + } + + @Override + public boolean handleDotChar() { + return false; + } + + @Override + public boolean handleNext() { + return true; + } + + @Override + public boolean handleJSONObjectChild() { + return true; + } + + @Override + public boolean handleJSONArrayChild() { + return true; + } + + @Override + public void handleEnd() { + //nothing to do + } + + @Override + public Object result() { + return pathsFound; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java new file mode 100755 index 00000000..00d9f8ef --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java @@ -0,0 +1,82 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONObject; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Removes branches from a {@link JSONObject}. + *

+ * A path is not removed from the user-specified list once its processing is over, + * because identical objects in the same array are supported by this action. + *

+ * See package-info for more details + *

+ * See unit tests for examples + * + * @author adoneitan@gmail.com + * + */ +public class RemoveAction implements TraverseAction +{ + protected JSONObject result; + protected List pathsToRemove; + + public RemoveAction(List pathsToRemove) + { + this.pathsToRemove = pathsToRemove; + } + + @Override + public boolean handleStart(JSONObject object) + { + result = object; + return object != null && pathsToRemove != null && pathsToRemove.size() > 0; + } + + @Override + public boolean handleDotChar() { + return true; + } + + @Override + public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + { + if (pathsToRemove.contains(pathToEntry)) + { + it.remove(); + //the entry has been removed from the traversal iterator, no point in traversing its children + return true; + } + return false; + } + + @Override + public boolean handleNext() + { + //must traverse the whole object + return true; + } + + @Override + public boolean handleJSONObjectChild() { + return true; + } + + @Override + public boolean handleJSONArrayChild() { + return true; + } + + @Override + public void handleEnd() { + //nothing to do + } + + @Override + public Object result() { + return result; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java new file mode 100755 index 00000000..1234ff65 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java @@ -0,0 +1,114 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONObject; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Retain branches or parts of branches matching a specified list of paths. + *

+ * Paths are matched from the root down. If a user-specified path ends at a non-leaf node, + * the rest of the branch from that node to the leaf is not retained. + *

+ * A path is not removed from the user-specified list once its processing is over, + * because identical objects in the same array are supported by this action. + *

+ * See package-info for more details + *

+ * See unit tests for examples + * + * @author adoneitan@gmail.com + * + */ +public class RetainAction implements TraverseAction +{ + protected JSONObject result; + protected List pathsToRetain; + + public RetainAction(List pathsToRetain) + { + this.pathsToRetain = new ArrayList(pathsToRetain); + } + + @Override + public boolean handleStart(JSONObject object) + { + if (object == null) + { + result = null; + return false; + } + if (pathsToRetain == null || pathsToRetain.size() == 0) + { + result = new JSONObject(); + return false; + } + result = object; + return true; + } + + @Override + public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + { + /** + * if the full path to the entry is not contained in any of the paths to retain - remove it from the object + * this step does not remove entries whose full path is contained in a path to retain but are not equal to an + * entry to retain + */ + if (!foundStartsWith(pathToEntry) || entry.getKey().contains(".")) + { + it.remove(); + //the entry has been removed from the traversal iterator, no point in traversing its children + return true; + } + return false; + } + + @Override + public boolean handleDotChar() + { + //need to reach handleEntryAndIgnoreChildren() in order to remove the path containing the dot char + return true; + } + + @Override + public boolean handleNext() + { + //must traverse the whole object + return true; + } + + @Override + public boolean handleJSONObjectChild() { + return true; + } + + @Override + public boolean handleJSONArrayChild(){ + return true; + } + + @Override + public void handleEnd() + { + // nothing to do + } + + @Override + public Object result() { + return result; + } + + protected boolean foundStartsWith(String path) + { + for (String p : pathsToRetain) { + if (p == path || (p != null && path != null && p.startsWith(path))) { + return true; + } + } + return false; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java new file mode 100755 index 00000000..6184c6f4 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java @@ -0,0 +1,62 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +import java.util.Iterator; +import java.util.Map; + +/** + * An interface for a processing action on the nodes of a {@link JSONObject} while traversing it. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public interface TraverseAction +{ + /** + * called before any traversal of the {@link JSONObject} starts + * @return true if traversal should start at all + */ + boolean handleStart(JSONObject object); + + /** + * @return false if encountering a key containing a '.' char on the path should abort the traversal of the path + */ + boolean handleDotChar(); + + /** + * called for each entry in given level of the {@link JSONObject} + * @return false if children of the specified entry should not be traversed + */ + boolean handleEntryAndIgnoreChildren(String fullPathToObject, Iterator> it, Map.Entry entry); + + /** + * called before the next entry in a given level of the {@link JSONObject} is processed + * @return true if siblings of the last entry should be processed + */ + boolean handleNext(); + + /** + * called if the child of the currently processed entry is a {@link JSONObject} + * @return true if a {@link JSONObject} child of the current entry should be processed + */ + boolean handleJSONObjectChild(); + + /** + * called if the child of the currently processed entry is a {@link JSONArray} + * @return true if a {@link JSONArray} child of the current entry should be processed + */ + boolean handleJSONArrayChild(); + + /** + * called after the traversal ends, and just before the traversal method exits + */ + void handleEnd(); + + /** + * holds the result of the traversal, as assigned by the action implementing this interface + */ + Object result(); +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java new file mode 100644 index 00000000..dcb334a3 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java @@ -0,0 +1,31 @@ +/** + * + * Traverse all the nodes in a {@link net.minidev.json.JSONObject} and process them + *

+ * The traversal starts at the root and moves breadth-first down the branches. + * The {@link net.minidev.json.actions.traverse.JSONTraverser} accepts a + * {@link net.minidev.json.actions.traverse.TraverseAction} and provides callback hooks at each significant + * step which the {@link net.minidev.json.actions.traverse.TraverseAction} may use to process + * the nodes. + *

+ * The {@link net.minidev.json.actions.traverse.JSONTraverser} assumes that paths in the tree which the + * {@link net.minidev.json.JSONObject} represents are specified in the n-gram format - a list of keys from the + * root down separated by dots: + *

+ * K0[[[[.K1].K2].K3]...] + *

+ * A key to the right of a dot is a direct child of a key to the left of a dot. + * Keys with a dot in their name are not supported. + *

+ * Sample usage: + *

+ * TraverseAction tAction = new TraverseAction(){...};
+ * JSONTraverser jsonTrv = new JSONTraverser(tAction);
+ * jsonTrv.traverse(new JSONObject(...));
+ * Object result = tAction.result();
+ * 
+ * + + * @author adoneitan@gmail.com + */ +package net.minidev.json.actions.traverse; \ No newline at end of file diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java new file mode 100755 index 00000000..54031296 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java @@ -0,0 +1,93 @@ +package net.minidev.json.test.actions; + +import net.minidev.json.actions.navigate.JSONPath; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author adoneitan@gmail.com + */ +public class JSONPathTest +{ + @Test + public void testIterator() + { + JSONPath jp = new JSONPath("a.b.c"); + assertTrue(jp.nextIndex() == 0); + assertTrue(jp.prevIndex() == -1); + assertTrue("".equals(jp.curr())); + assertTrue("".equals(jp.origin())); + assertTrue("a.b.c".equals(jp.remainder())); + assertTrue(jp.hasNext()); + assertFalse(jp.hasPrev()); + + jp.next(); + assertTrue("a".equals(jp.curr())); + assertTrue("a".equals(jp.origin())); + assertTrue("b.c".equals(jp.remainder())); + assertTrue(jp.hasNext()); + assertTrue(jp.hasPrev()); + + jp.next(); + assertTrue("b".equals(jp.curr())); + assertTrue("a.b".equals(jp.origin())); + assertTrue("c".equals(jp.remainder())); + assertTrue(jp.hasNext()); + assertTrue(jp.hasPrev()); + + jp.next(); + assertTrue("c".equals(jp.curr())); + assertTrue("a.b.c".equals(jp.origin())); + assertTrue("".equals(jp.remainder())); + assertFalse(jp.hasNext()); + assertTrue(jp.hasPrev()); + + /** the first prev() after a next only changes direction. see {@link ListIterator} for details */ + jp.prev(); + assertTrue("c".equals(jp.curr())); + assertTrue("a.b.c".equals(jp.origin())); + assertTrue("".equals(jp.remainder())); + assertTrue(jp.hasNext()); + assertTrue(jp.hasPrev()); + + jp.prev(); + assertTrue("b".equals(jp.curr())); + assertTrue("a.b".equals(jp.origin())); + assertTrue("c".equals(jp.remainder())); + assertTrue(jp.hasNext()); + assertTrue(jp.hasPrev()); + + jp.prev(); + assertTrue("a".equals(jp.curr())); + assertTrue("a".equals(jp.origin())); + assertTrue("b.c".equals(jp.remainder())); + assertTrue(jp.hasNext()); + assertFalse(jp.hasPrev()); + } + + @Test + public void testSubPath() + { + JSONPath jp = new JSONPath("a.b.c"); + assertTrue(jp.subPath(1,2).equals("b.c")); + } + + @Test + public void testClone() throws CloneNotSupportedException + { + JSONPath jp1 = new JSONPath("a.b.c"); + JSONPath jp2 = jp1.clone(); + assertTrue(jp1.equals(jp2)); + + jp1.next(); + JSONPath jp3 = jp1.clone(); + assertTrue(jp1.equals(jp3)); + + jp1.prev(); + JSONPath jp4 = jp1.clone(); + assertTrue(jp1.equals(jp4)); + + } +} \ No newline at end of file diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java new file mode 100755 index 00000000..890bbfcc --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java @@ -0,0 +1,159 @@ +package net.minidev.json.test.actions; + +import net.minidev.json.actions.PathLocator; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.ParseException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * @author adoneitan@gmail.com + */ +@RunWith(Parameterized.class) +public class PathLocatorTest +{ + private String jsonToSearch; + private Object keysToFind; + private String[] expectedFound; + + public PathLocatorTest(String jsonToSearch, Object keysToFind, String[] expectedFound) + { + this.jsonToSearch = jsonToSearch; + this.keysToFind = keysToFind; + this.expectedFound = expectedFound; + } + + @Parameterized.Parameters + public static Collection params() + { + return Arrays.asList(new Object[][]{ + + //nulls, bad/empty keys + {null, null, new String[]{} }, + {null, "", new String[]{} }, + {null, "k1", new String[]{} }, + {null, new String[]{}, new String[]{} }, + {null, new JSONArray(), new String[]{} }, + {null, new ArrayList(0), new String[]{} },//5 + + //empty json, bad/empty keys + {"{}", null, new String[]{} }, + {"{}", "", new String[]{} }, + {"{}", "k1", new String[]{} }, + {"{}", new String[]{}, new String[]{} }, + {"{}", new JSONArray(), new String[]{} }, + {"{}", new ArrayList(0), new String[]{} },//11 + + //simple json, bad/empty keys + {"{\"k0\":\"v0\"}", null, new String[]{} }, + {"{\"k0\":\"v0\"}", "", new String[]{} }, + {"{\"k0\":\"v0\"}", "k1", new String[]{} }, + {"{\"k0\":\"v0\"}", new String[]{}, new String[]{} }, + {"{\"k0\":\"v0\"}", new JSONArray(), new String[]{} }, + {"{\"k0\":\"v0\"}", new ArrayList(0), new String[]{} },//17 + + //simple json, valid/invalid keys + {"{\"k0\":\"v0\"}", "k0", new String[]{"k0"} }, + {"{\"k0\":\"v0\"}", "v0", new String[]{} }, + {"{\"k0\":\"v0\"}", "k0.k1", new String[]{} }, + {"{\"k0\":\"v0\"}", "k1.k0", new String[]{} }, + {"{\"k0\":null}", "k0", new String[]{"k0"} }, + {"{\"k0\":null}", null, new String[]{} },//23 + + //key with dot char + {"{\"k0.k1\":\"v0\"}", "k0", new String[]{} }, + {"{\"k0.k1\":\"v0\"}", "k1", new String[]{} }, + {"{\"k0.k1\":\"v0\"}", "k0.k1", new String[]{} }, + + // key with dot ambiguity + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0", new String[]{"k0"} }, + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k1", new String[]{} }, + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0.k1", new String[]{"k0.k1"} }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1", new String[]{"k0.k1"} },//30 + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1.k2", new String[]{"k0.k1.k2"} }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k1.k2", new String[]{} }, + {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k0", new String[]{"k0"} }, + {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k1.k2", new String[]{"k1.k2"} }, + + //ignore non-existent keys but keep good keys + {"{\"k0\":\"v0\",\"k1\":\"v1\"}", new String[]{"k0","k2"}, new String[]{"k0"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k2"}, new String[]{"k0"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2"}, new String[]{"k0", "k1.k2"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2.k3"}, new String[]{"k0"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2","k1"}, new String[]{"k0","k1","k1.k2"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k0.k2"}, new String[]{"k0","k1"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k2"}, new String[]{"k0","k1"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k2"}, new String[]{"k1.k2"} }, + + //arrays - key inside array treated as child + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0", new String[]{"k0"} }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1", new String[]{"k0.k1"} }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", new String[]{"k0.k1.k2"} }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}", "k0.k1.k2", new String[]{"k0.k1.k2", "k0.k1.k2"} }, + }); + } + + @Test + public void test() throws ParseException + { + JSONObject objectToSearch = jsonToSearch != null ? (JSONObject) JSONValue.parseWithException(jsonToSearch) : null; + PathLocator f = switchKeyToRemove(); + List found = f.find(objectToSearch); + assertEquals(Arrays.asList(expectedFound), found); + } + + private PathLocator switchKeyToRemove() + { + long m = System.currentTimeMillis(); + if (keysToFind == null && m % 4 == 0) + { + System.out.println("cast to String"); + return new PathLocator((String)null); + } + else if (keysToFind == null && m % 4 == 1) + { + System.out.println("cast to String[]"); + return new PathLocator((String[])null); + } + else if (keysToFind == null && m % 4 == 2) + { + System.out.println("cast to JSONArray"); + return new PathLocator((JSONArray)null); + } + else if (keysToFind == null && m % 4 == 3) + { + System.out.println("cast to List"); + return new PathLocator((List)null); + } + else if (keysToFind instanceof String) + { + return new PathLocator((String) keysToFind); + } + else if (keysToFind instanceof String[]) + { + return new PathLocator((String[]) keysToFind); + } + else if (keysToFind instanceof JSONArray) + { + return new PathLocator((JSONArray) keysToFind); + } + else if (keysToFind instanceof List) + { + return new PathLocator((List) keysToFind); + } + else + { + throw new IllegalArgumentException("bad test setup: wrong type of key to remove"); + } + } +} \ No newline at end of file diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java new file mode 100755 index 00000000..51d9c813 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java @@ -0,0 +1,136 @@ +package net.minidev.json.test.actions; + +import net.minidev.json.actions.PathRemover; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.ParseException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link PathRemover} + * + * @author adoneitan@gmail.com + */ +@RunWith(Parameterized.class) +public class PathRemoverTest +{ + private String jsonToClean; + private Object keyToRemove; + private String expectedJson; + + public PathRemoverTest(String jsonToClean, Object keyToRemove, String expectedJson) + { + this.jsonToClean = jsonToClean; + this.keyToRemove = keyToRemove; + this.expectedJson = expectedJson; + } + + @Parameterized.Parameters + public static Collection params() + { + return Arrays.asList(new Object[][]{ + + {null, "key", null }, // null json + {"{}", "key", "{}" }, // empty json + {"{\"first\": null}", null, "{\"first\": null}" }, // null key + {"{\"first\": null}", "", "{\"first\": null}" }, // empty string key + {"{\"first\": null}", new String[]{}, "{\"first\": null}" }, // empty string array key + {"{\"first\": null}", new JSONArray(), "{\"first\": null}" }, // empty json array key + {"{\"first\": null}", new ArrayList(0), "{\"first\": null}" }, // empty list key + {"{\"first\": null}", "first", "{}" }, // remove root key + {"{\"first.f1\": null}", "first.f1", "{}" }, // key with dot + {"{\"first.f1\": \"withDot\", \"first\":{\"f1\": null}}", "first.f1", "{\"first\":{}}" }, // key with dot ambiguity + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}", "first.f2.f3.id", "{\"first\":{\"f2\":{\"f3\":{}}}}" }, // nested object remove single leaf + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}", "notfound", "{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}" }, // nested object key does not exist + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3.id", "{\"first\":{\"f2\":{\"f3\":{\"name\":\"me\"}}}}"}, // nested object remove first leaf + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3.name", "{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}" }, // nested object remove last leaf + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3", "{\"first\":{\"f2\":{}}}" }, // nested object remove intermediate node + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first", "{}" }, // nested object remove root + {"{\"first\":{\"f2\":[[1,{\"id\":\"id1\"},3],4]}}", "first.f2.id", "{\"first\":{\"f2\":[[1,{},3],4]}}" }, // double nested array remove leaf + {"{\"first\":{\"f2\":[[1,{\"id\":\"id1\"},3],4]}}", "first.f2", "{\"first\":{}}" }, // double nested array remove array + {"{\"first\":[[1,{\"id\":\"id1\"},3],4]}", "first", "{}" }, // double nested array remove root + + //arrays + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1", "{\"k0\":{}}" }, // value is array + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[1,{},3,4]}}" }, // full path into array object + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.3" , "{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}" }, // full path into array primitive + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[1,{},{},3,4]}}" }, // full path into array with identical items + + // composite json remove all roots + {"{\"first\": {\"f2\":{\"id\":\"id1\"}}, \"second\": [{\"k1\":{\"id\":\"id1\"}}, 4, 5, 6, {\"id\": 123}], \"third\": 789, \"id\": null}", + (JSONArray) JSONValue.parse("[\"first\",\"second\",\"third\",\"id\"]"), + "{}" }, + // composite json remove all leaves + {"{\"first\": {\"f2\":{\"id\":\"id1\"}}, \"second\": [{\"k1\":{\"id\":\"id1\"}}, 4, 5, 6, {\"id\": 123}], \"third\": 789, \"id\": null}", + (List) Arrays.asList("first.f2.id", "second.k1.id", "second.id", "third", "id"), + "{\"first\": {\"f2\":{}}, \"second\": [{\"k1\":{}}, 4, 5, 6, {}]}" }, + + }); + } + + @Test + public void test() throws ParseException + { + JSONObject objectToClean = jsonToClean != null ? (JSONObject) JSONValue.parseWithException(jsonToClean) : null; + JSONObject expectedObject = expectedJson != null ? (JSONObject) JSONValue.parseWithException(expectedJson): null; + PathRemover cl = switchKeyToRemove(); + cl.remove(objectToClean); + assertEquals(expectedObject, objectToClean); + } + + private PathRemover switchKeyToRemove() + { + long m = System.currentTimeMillis(); + if (keyToRemove == null && m % 4 == 0) + { + System.out.println("cast to String"); + return new PathRemover((String)null); + } + else if (keyToRemove == null && m % 4 == 1) + { + System.out.println("cast to String[]"); + return new PathRemover((String[])null); + } + else if (keyToRemove == null && m % 4 == 2) + { + System.out.println("cast to JSONArray"); + return new PathRemover((JSONArray)null); + } + else if (keyToRemove == null && m % 4 == 3) + { + System.out.println("cast to List"); + return new PathRemover((List)null); + } + else if (keyToRemove instanceof String) + { + return new PathRemover((String)keyToRemove); + } + else if (keyToRemove instanceof String[]) + { + return new PathRemover((String[])keyToRemove); + } + else if (keyToRemove instanceof JSONArray) + { + return new PathRemover((JSONArray)keyToRemove); + } + else if (keyToRemove instanceof List) + { + return new PathRemover((List)keyToRemove); + } + else + { + throw new IllegalArgumentException("bad test setup: wrong type of key to remove"); + } + } + +} diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathReplicatorTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathReplicatorTest.java new file mode 100755 index 00000000..3c9c13de --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathReplicatorTest.java @@ -0,0 +1,232 @@ +package net.minidev.json.test.actions; + +import net.minidev.json.actions.PathReplicator; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * @author adoneitan@gmail.com + */ +@RunWith(Parameterized.class) +public class PathReplicatorTest +{ + private String jsonSource; + private Object pathsToCopy; + private Object expected; + + public PathReplicatorTest(String jsonSource, Object pathsToCopy, Object expected) + { + this.jsonSource = jsonSource; + this.pathsToCopy = pathsToCopy; + this.expected = expected; + } + + @Parameterized.Parameters + public static Collection params() + { + return Arrays.asList(new Object[][]{ + + //nulls, bad/empty keys + {null, null, null }, + {null, "", null }, + {null, "k1", null }, + {null, new String[]{}, null }, + {null, new JSONArray(), null }, + {null, new ArrayList(0), null },//5 + + //empty json, bad/empty keys + {"{}", null, "{}" }, + {"{}", "", "{}" }, + {"{}", "k1", "{}" }, + {"{}", new String[]{}, "{}" }, + {"{}", new JSONArray(), "{}" }, + {"{}", new ArrayList(0), "{}" },//11 + + //simple json, bad/empty keys + {"{\"k0\":\"v0\"}", null, "{}" }, + {"{\"k0\":\"v0\"}", "", "{}" }, + {"{\"k0\":\"v0\"}", "k1", "{}" }, + {"{\"k0\":\"v0\"}", new String[]{}, "{}" }, + {"{\"k0\":\"v0\"}", new JSONArray(), "{}" }, + {"{\"k0\":\"v0\"}", new ArrayList(0), "{}" },//17 + + //simple json, valid/invalid keys + {"{\"k0\":\"v0\"}", "k0", "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\"}", "v0", "{}" }, + {"{\"k0\":\"v0\"}", "k0.k1", "{}" },//20 + {"{\"k0\":\"v0\"}", "k1.k0", "{}" }, + {"{\"k0\":null}", "k0", "{\"k0\":null}" }, + {"{\"k0\":null}", "v0", "{}" }, + + //key with dot char + {"{\"k0.k1\":\"v0\"}", "k0", "{}" }, + {"{\"k0.k1\":\"v0\"}", "k1", "{}" }, + {"{\"k0.k1\":\"v0\"}", "k0.k1", "{}" }, + + // key with dot ambiguity + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0", "{\"k0\":{}}" }, + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k1", "{}" }, + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0.k1", "{\"k0\":{\"k1\":null}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1", "{\"k0\":{\"k1\":{}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1.k2", "{\"k0\":{\"k1\":{\"k2\":null}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k1.k2", "{}" }, + {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k0", "{\"k0\":{}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k1.k2", "{\"k1\":{\"k2\":\"v2\"}}}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0.k2.k3.k4", "{}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0.k2.k3", "{}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0.k2", "{\"k0\":{\"k2\":{}}}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0", "{\"k0\":{}}" },//38 + + //ignore non-existent keys but keep good keys + {"{\"k0\":\"v0\",\"k1\":\"v1\"}", new String[]{"k0","k2"}, "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k2"}, "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2"}, "{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k0.k2"}, "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2","k1"}, "{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1"}, "{\"k0\":\"v0\",\"k1\":{}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k2"}, "{\"k0\":\"v0\",\"k1\":{}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k2"}, "{\"k1\":{\"k2\":\"v2\"}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k2.k3"}, "{}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0.k1.k2"}, "{}" },//48 + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k0"}, "{}" }, + + //arrays - key inside array treated as child + {"{\"k0\":{\"k1\":[1,2,3,4]}}", "k0", "{\"k0\":{}}" }, + {"{\"k0\":{\"k1\":[1,2,3,4]}}", "k0.k1", "{\"k0\":{\"k1\":[1,2,3,4]}}" }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0", "{\"k0\":{}}" }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1", "{\"k0\":{\"k1\":[1,{},3,4]}}" }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[{\"k2\":\"v2\"}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}", "k0.k1", "{\"k0\":{\"k1\":[{},{}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":\"v4\"}]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[{\"k2\":\"v2\"}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":\"v4\"}]}}", "k0.k3.k4", "{\"k0\":{\"k3\":[{\"k4\":\"v4\"}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":{\"k5\":\"v5\"}}]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[{\"k2\":\"v2\"}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":{\"k5\":\"v5\"}}]}}", "k0.k3.k4", "{\"k0\":{\"k3\":[{\"k4\":{}}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":{\"k5\":\"v5\"}}]}}", "k0.k3.k4.k5", "{\"k0\":{\"k3\":[{\"k4\":{\"k5\":\"v5\"}}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":{\"k5\":\"v5\"}}]}}", new String[]{"k0.k1", "k0.k3"}, "{\"k0\":{\"k3\":[{}],\"k1\":[{}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"}],\"k3\":[{\"k4\":{\"k5\":\"v5\"}}]}}", new String[]{"k0.k1", "k0.k3.k4.k5"}, "{\"k0\":{\"k3\":[{\"k4\":{\"k5\":\"v5\"}}],\"k1\":[{}]}}" }, + }); + } + + @Test + public void test() throws Exception + { + JSONObject objectSource = jsonSource != null ? (JSONObject) JSONValue.parseWithException(jsonSource) :null; + PathReplicator copier = switchKeyToCopy(); + JSONObject copied = copier.replicate(objectSource); + JSONObject expectedObj = expected != null ? (JSONObject) JSONValue.parseWithException((String) expected) : null; + assertEquals(expectedObj, copied); + } + + @Test + public void test2() throws Exception + { + JSONObject objectSource = jsonSource != null ? (JSONObject) JSONValue.parseWithException(jsonSource) :null; + PathReplicator copier = switchKeyToCopy2(); + JSONObject copied = copier.replicate(objectSource); + JSONObject expectedObj = expected != null ? (JSONObject) JSONValue.parseWithException((String) expected) : null; + assertEquals(expectedObj, copied); + } + + private PathReplicator switchKeyToCopy() + { + long m = System.currentTimeMillis(); + if (pathsToCopy == null && m % 4 == 0) + { + System.out.println("cast to String"); + return new PathReplicator((String)null); + } + else if (pathsToCopy == null && m % 4 == 1) + { + System.out.println("cast to String[]"); + return new PathReplicator((String[])null); + } + else if (pathsToCopy == null && m % 4 == 2) + { + System.out.println("cast to JSONArray"); + return new PathReplicator((JSONArray)null); + } + else if (pathsToCopy == null && m % 4 == 3) + { + System.out.println("cast to List"); + return new PathReplicator((List)null); + } + else if (pathsToCopy instanceof String) + { + return new PathReplicator((String) pathsToCopy); + } + else if (pathsToCopy instanceof String[]) + { + return new PathReplicator((String[]) pathsToCopy); + } + else if (pathsToCopy instanceof JSONArray) + { + return new PathReplicator((JSONArray) pathsToCopy); + } + else if (pathsToCopy instanceof List) + { + return new PathReplicator((List) pathsToCopy); + } + else + { + throw new IllegalArgumentException("bad test setup: wrong type of key to remove"); + } + } + + private PathReplicator switchKeyToCopy2() + { + long m = System.currentTimeMillis(); + if (pathsToCopy == null && m % 4 == 0) + { + System.out.println("cast to String"); + return new PathReplicator((String)null); + } + else if (pathsToCopy == null && m % 4 == 1) + { + System.out.println("cast to String[]"); + return new PathReplicator((String[])null); + } + else if (pathsToCopy == null && m % 4 == 2) + { + System.out.println("cast to JSONArray"); + return new PathReplicator((JSONArray)null); + } + else if (pathsToCopy == null && m % 4 == 3) + { + System.out.println("cast to List"); + return new PathReplicator((List)null); + } + else if (pathsToCopy instanceof String) + { + return new PathReplicator((String) pathsToCopy); + } + else if (pathsToCopy instanceof String[]) + { + return new PathReplicator((String[]) pathsToCopy); + } + else if (pathsToCopy instanceof JSONArray) + { + return new PathReplicator((JSONArray) pathsToCopy); + } + else if (pathsToCopy instanceof List) + { + return new PathReplicator((List) pathsToCopy); + } + else + { + throw new IllegalArgumentException("bad test setup: wrong type of key to remove"); + } + } + +} \ No newline at end of file diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java new file mode 100755 index 00000000..c6cee591 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java @@ -0,0 +1,168 @@ +package net.minidev.json.test.actions; + +import net.minidev.json.actions.PathsRetainer; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.ParseException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * @author adoneitan@gmail.com + */ +@RunWith(Parameterized.class) +public class PathsRetainerTest +{ + private String jsonToReduce; + private Object keyToKeep; + private String expectedReducedJson; + + public PathsRetainerTest(String jsonToReduce, Object keyToKeep, String expectedReducedJson) + { + this.jsonToReduce = jsonToReduce; + this.keyToKeep = keyToKeep; + this.expectedReducedJson = expectedReducedJson; + } + + @Parameterized.Parameters + public static Collection params() + { + return Arrays.asList(new Object[][]{ + + //nulls, bad/empty keys + {null, null, null }, + {null, "", null }, + {null, "k1", null }, + {null, new String[]{}, null }, + {null, new JSONArray(), null }, + {null, new ArrayList(0), null },//5 + + //empty json, bad/empty keys + {"{}", null, "{}" }, + {"{}", "", "{}" }, + {"{}", "k1", "{}" }, + {"{}", new String[]{}, "{}" }, + {"{}", new JSONArray(), "{}" }, + {"{}", new ArrayList(0), "{}" },//11 + + //simple json, bad/empty keys + {"{\"k0\":\"v0\"}", null, "{}" }, + {"{\"k0\":\"v0\"}", "", "{}" }, + {"{\"k0\":\"v0\"}", "k1", "{}" }, + {"{\"k0\":\"v0\"}", new String[]{}, "{}" }, + {"{\"k0\":\"v0\"}", new JSONArray(), "{}" }, + {"{\"k0\":\"v0\"}", new ArrayList(0), "{}" },//17 + + //simple json, valid/invalid keys + {"{\"k0\":\"v0\"}", "k0", "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\"}", "v0", "{}" }, + {"{\"k0\":\"v0\"}", "k0.k1", "{}" }, + {"{\"k0\":\"v0\"}", "k1.k0", "{}" }, + {"{\"k0\":null}", "k0", "{\"k0\":null}" }, + {"{\"k0\":null}", "v0", "{}" },//23 + + //key with dot char + {"{\"k0.k1\":\"v0\"}", "k0", "{}" }, + {"{\"k0.k1\":\"v0\"}", "k1", "{}" }, + {"{\"k0.k1\":\"v0\"}", "k0.k1", "{}" }, + + // key with dot ambiguity + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0", "{\"k0\":{}}" },//27 + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k1", "{}" }, + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0.k1", "{\"k0\":{\"k1\":null}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1", "{\"k0\":{\"k1\":{}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1.k2", "{\"k0\":{\"k1\":{\"k2\":null}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k1.k2", "{}" }, + {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k0", "{\"k0\":{}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k1.k2", "{\"k1\":{\"k2\":\"v2\"}}}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0.k2.k3.k4", "{}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0.k2.k3", "{}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0.k2", "{\"k0\":{\"k2\":{}}}" }, + {"{\"k0\":{\"k1\":\"v1\",\"k2\":{\"k3.k4\":\"dot\"}}}", "k0", "{\"k0\":{}}" }, + + //ignore non-existent keys but keep good keys + {"{\"k0\":\"v0\",\"k1\":\"v1\"}", new String[]{"k0","k2"}, "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k2"}, "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2"}, "{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k0.k2"}, "{\"k0\":\"v0\"}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2","k1"}, "{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1"}, "{\"k0\":\"v0\",\"k1\":{}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k2"}, "{\"k0\":\"v0\",\"k1\":{}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k2"}, "{\"k1\":{\"k2\":\"v2\"}}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k2.k3"}, "{}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0.k1.k2"}, "{}" }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k0"}, "{}" }, + + //arrays - key inside array treated as child + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0", "{\"k0\":{}}" }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1", "{\"k0\":{\"k1\":[1,{},3,4]}}" }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}", "k0.k1", "{\"k0\":{\"k1\":[{},{}]}}" }, + {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}", "k0.k1.k2", "{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}" }, + }); + } + + @Test + public void test() throws ParseException + { + JSONObject objectToReduce = jsonToReduce != null ? (JSONObject) JSONValue.parseWithException(jsonToReduce) :null; + JSONObject expectedReducedObj = expectedReducedJson != null ? (JSONObject) JSONValue.parseWithException(expectedReducedJson):null; + PathsRetainer reducer = switchKeyToRemove(); + JSONObject reducedObj = reducer.retain(objectToReduce); + assertEquals(expectedReducedObj, reducedObj); + } + + private PathsRetainer switchKeyToRemove() + { + long m = System.currentTimeMillis(); + if (keyToKeep == null && m % 4 == 0) + { + System.out.println("cast to String"); + return new PathsRetainer((String)null); + } + else if (keyToKeep == null && m % 4 == 1) + { + System.out.println("cast to String[]"); + return new PathsRetainer((String[])null); + } + else if (keyToKeep == null && m % 4 == 2) + { + System.out.println("cast to JSONArray"); + return new PathsRetainer((JSONArray)null); + } + else if (keyToKeep == null && m % 4 == 3) + { + System.out.println("cast to List"); + return new PathsRetainer((List)null); + } + else if (keyToKeep instanceof String) + { + return new PathsRetainer((String) keyToKeep); + } + else if (keyToKeep instanceof String[]) + { + return new PathsRetainer((String[]) keyToKeep); + } + else if (keyToKeep instanceof JSONArray) + { + return new PathsRetainer((JSONArray) keyToKeep); + } + else if (keyToKeep instanceof List) + { + return new PathsRetainer((List) keyToKeep); + } + else + { + throw new IllegalArgumentException("bad test setup: wrong type of key to remove"); + } + } +} \ No newline at end of file From 2d42e34b62f63e99cf7b7059cf7ecc3ab690bf0e Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Tue, 5 Apr 2016 22:40:21 +0300 Subject: [PATCH 02/10] gitignore intellij idea files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 286679a2..8c0b7388 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ json-smart/target/ *.classpath *.project *.prefs +*.iml +.idea \ No newline at end of file From 909c53c3e5e1c38620ab576f8b9e8ff6db57e0ee Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Mon, 18 Apr 2016 00:25:43 +0300 Subject: [PATCH 03/10] rename actions --- .../src/main/java/net/minidev/json/actions/PathRemover.java | 4 ++-- .../src/main/java/net/minidev/json/actions/PathsRetainer.java | 4 ++-- .../traverse/{RemoveAction.java => RemovePathsAction.java} | 4 ++-- .../traverse/{RetainAction.java => RetainPathsAction.java} | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename json-smart/src/main/java/net/minidev/json/actions/traverse/{RemoveAction.java => RemovePathsAction.java} (93%) rename json-smart/src/main/java/net/minidev/json/actions/traverse/{RetainAction.java => RetainPathsAction.java} (95%) diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java index fb3e1bd8..1a2568ea 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java @@ -3,7 +3,7 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.actions.traverse.JSONTraverser; -import net.minidev.json.actions.traverse.RemoveAction; +import net.minidev.json.actions.traverse.RemovePathsAction; import net.minidev.json.actions.traverse.TraverseAction; import java.util.ArrayList; @@ -66,7 +66,7 @@ public PathRemover(String... pathsToRemove) public JSONObject remove(JSONObject objectToClean) { - TraverseAction strategy = new RemoveAction(this.pathsToRemove); + TraverseAction strategy = new RemovePathsAction(this.pathsToRemove); JSONTraverser traversal = new JSONTraverser(strategy); traversal.traverse(objectToClean); return (JSONObject) strategy.result(); diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java index 6dc146dd..7a73f5e3 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java @@ -2,7 +2,7 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; -import net.minidev.json.actions.traverse.RetainAction; +import net.minidev.json.actions.traverse.RetainPathsAction; import net.minidev.json.actions.traverse.JSONTraverser; import net.minidev.json.actions.traverse.LocatePathsAction; import net.minidev.json.actions.traverse.TraverseAction; @@ -77,7 +77,7 @@ public JSONObject retain(JSONObject object) List realPathsToRetain = (List) locateAction.result(); //now reduce the object using only existing paths - TraverseAction reduce = new RetainAction(realPathsToRetain); + TraverseAction reduce = new RetainPathsAction(realPathsToRetain); JSONTraverser t2 = new JSONTraverser(reduce); t2.traverse(object); return (JSONObject) reduce.result(); diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsAction.java similarity index 93% rename from json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java rename to json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsAction.java index 00d9f8ef..0b41d8e9 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsAction.java @@ -19,12 +19,12 @@ * @author adoneitan@gmail.com * */ -public class RemoveAction implements TraverseAction +public class RemovePathsAction implements TraverseAction { protected JSONObject result; protected List pathsToRemove; - public RemoveAction(List pathsToRemove) + public RemovePathsAction(List pathsToRemove) { this.pathsToRemove = pathsToRemove; } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsAction.java similarity index 95% rename from json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java rename to json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsAction.java index 1234ff65..49a9361e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsAction.java @@ -23,12 +23,12 @@ * @author adoneitan@gmail.com * */ -public class RetainAction implements TraverseAction +public class RetainPathsAction implements TraverseAction { protected JSONObject result; protected List pathsToRetain; - public RetainAction(List pathsToRetain) + public RetainPathsAction(List pathsToRetain) { this.pathsToRetain = new ArrayList(pathsToRetain); } From 38f9a537bb4d7a93f4b63d21b4703de1f0779f05 Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Mon, 18 Apr 2016 00:26:07 +0300 Subject: [PATCH 04/10] add ElementRemover --- .../minidev/json/actions/ElementRemover.java | 51 +++++++++ .../traverse/RemoveElementsAction.java | 87 ++++++++++++++ .../json/test/actions/ElementRemoverTest.java | 106 ++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100755 json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java create mode 100755 json-smart/src/test/java/net/minidev/json/test/actions/ElementRemoverTest.java diff --git a/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java b/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java new file mode 100755 index 00000000..06db5a8c --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java @@ -0,0 +1,51 @@ +package net.minidev.json.actions; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.traverse.JSONTraverser; +import net.minidev.json.actions.traverse.RemoveElementsAction; +import net.minidev.json.actions.traverse.TraverseAction; + +import java.util.*; + +/** + * Removes key:value elements from every node of a {@link JSONObject} matching the list of user-specified elements. + *

+ * An element to remove must be specified as a key:value pair + *

+ * Usage Example: + *

+ * To remove the element k2:v2 from the {@link JSONObject} {k0:{k2:v2, k3:v3}, k1:{k2:v2, k4:v4}} use the remover like so: + *

+ * PathRemover pr = new PathRemover("k2.v2");
+ * JSONObject cleanObject = pr.remove(new JSONObject(...));
+ * 
+ * The resulting object 'cleanObject' would be {k0:{k3:v3}, k1:{k4:v4}} + *

+ * See unit tests for more examples + * + * @author adoneitan@gmail.com + * + */ +public class ElementRemover +{ + private Map elementsToRemove; + + public ElementRemover(Map elementsToRemove) + { + this.elementsToRemove = elementsToRemove == null ? Collections.emptyMap() : elementsToRemove; + } + + public ElementRemover(JSONObject elementsToRemove) + { + this.elementsToRemove = elementsToRemove == null ? Collections.emptyMap() : elementsToRemove; + } + + public JSONObject remove(JSONObject objectToClean) + { + TraverseAction strategy = new RemoveElementsAction(this.elementsToRemove); + JSONTraverser traversal = new JSONTraverser(strategy); + traversal.traverse(objectToClean); + return (JSONObject) strategy.result(); + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java new file mode 100755 index 00000000..948ffac1 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java @@ -0,0 +1,87 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONObject; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Removes key:value elements from a {@link JSONObject}. + *

+ * An element is not removed from the user-specified list once its processing is over, + * because it may appear in more than one node. + *

+ * See package-info for more details + *

+ * See unit tests for examples + * + * @author adoneitan@gmail.com + * + */ +public class RemoveElementsAction implements TraverseAction +{ + protected JSONObject result; + protected final Map elementsToRemove; + protected final boolean allowDotChar; + + public RemoveElementsAction(Map elementsToRemove, boolean allowDotChar) + { + this.elementsToRemove = elementsToRemove; + this.allowDotChar = allowDotChar; + } + + public RemoveElementsAction(Map elementsToRemove) + { + this(elementsToRemove, false); + } + + @Override + public boolean handleStart(JSONObject object) + { + result = object; + return object != null && elementsToRemove != null && elementsToRemove.size() > 0; + } + + @Override + public boolean handleDotChar() { + return allowDotChar; + } + + @Override + public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + { + if (elementsToRemove.entrySet().contains(entry)) + { + it.remove(); + } + return false; + } + + @Override + public boolean handleNext() + { + //must traverse the whole object + return true; + } + + @Override + public boolean handleJSONObjectChild() { + return true; + } + + @Override + public boolean handleJSONArrayChild() { + return true; + } + + @Override + public void handleEnd() { + //nothing to do + } + + @Override + public Object result() { + return result; + } +} diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/ElementRemoverTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/ElementRemoverTest.java new file mode 100755 index 00000000..327fa2d1 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/test/actions/ElementRemoverTest.java @@ -0,0 +1,106 @@ +package net.minidev.json.test.actions; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.actions.ElementRemover; +import net.minidev.json.parser.ParseException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.*; + +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link ElementRemover} + * + * @author adoneitan@gmail.com + */ +@RunWith(Parameterized.class) +public class ElementRemoverTest +{ + private String jsonToClean; + private String elementsToRemove; + private String expectedJson; + + public ElementRemoverTest(String jsonToClean, String elementsToRemove, String expectedJson) + { + this.jsonToClean = jsonToClean; + this.elementsToRemove = elementsToRemove; + this.expectedJson = expectedJson; + } + + @Parameterized.Parameters + public static Collection params() + { + return Arrays.asList(new Object[][]{ + + {"{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", null, "{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}"}, + {"{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", "{}", "{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}"}, + {"{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", "{\"k0\":\"v2\"}", "{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}"}, + {"{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", "{\"k2\":\"v2\"}", "{\"k0\":{},\"k1\":{\"k3\":\"v3\"}}"}, + {"{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", "{\"k0\":{\"k2\":\"v2\"}}", "{\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}"}, + {"{\"k0\":{\"k2\":\"v2\"},\"k1\":{\"k2\":\"v2\",\"k3\":\"v3\"}}", "{\"k2\":\"v2\",\"k3\":\"v3\"}", "{\"k0\":{},\"k1\":{}}"}, + {"{\"k0\":{}}", "{}", "{\"k0\":{}}"}, + }); + } + + @Test + public void test() throws ParseException + { + JSONObject objectToClean = jsonToClean != null ? (JSONObject) JSONValue.parseWithException(jsonToClean) : null; + JSONObject expectedObject = expectedJson != null ? (JSONObject) JSONValue.parseWithException(expectedJson): null; + JSONObject toRemove = elementsToRemove != null ? (JSONObject) JSONValue.parseWithException(elementsToRemove): null; + ElementRemover er = new ElementRemover(toRemove); + er.remove(objectToClean); + assertEquals(expectedObject, objectToClean); + } + +// private ElementRemover switchKeyToRemove() +// { +// long m = System.currentTimeMillis(); +// if (elementsToRemove == null && m % 4 == 0) +// { +// System.out.println("cast to String"); +// return new ElementRemover((String)null); +// } +// else if (elementsToRemove == null && m % 4 == 1) +// { +// System.out.println("cast to String[]"); +// return new ElementRemover((String[])null); +// } +// else if (elementsToRemove == null && m % 4 == 2) +// { +// System.out.println("cast to JSONArray"); +// return new ElementRemover((JSONArray)null); +// } +// else if (elementsToRemove == null && m % 4 == 3) +// { +// System.out.println("cast to List"); +// return new ElementRemover((List)null); +// } +// else if (elementsToRemove instanceof String) +// { +// return new ElementRemover((String) elementsToRemove); +// } +// else if (elementsToRemove instanceof String[]) +// { +// return new ElementRemover((String[]) elementsToRemove); +// } +// else if (elementsToRemove instanceof JSONArray) +// { +// return new ElementRemover((JSONArray) elementsToRemove); +// } +// else if (elementsToRemove instanceof List) +// { +// return new ElementRemover((List) elementsToRemove); +// } +// else +// { +// throw new IllegalArgumentException("bad test setup: wrong type of key to remove"); +// } +// } + +} From 92adc26e9726ffc18a05b321a90a15d315816de7 Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Mon, 6 Jun 2016 15:40:45 +0300 Subject: [PATCH 05/10] enhance traversal API --- .../minidev/json/actions/ElementRemover.java | 7 +- .../net/minidev/json/actions/PathLocator.java | 16 ++- .../net/minidev/json/actions/PathRemover.java | 6 +- .../minidev/json/actions/PathsRetainer.java | 26 +++-- .../actions/navigate/CopyPathsAction.java | 11 +- .../json/actions/navigate/JSONNavigator.java | 16 +-- .../json/actions/navigate/NavigateAction.java | 11 +- .../json/actions/path/DotDelimiter.java | 23 ++++ .../actions/{navigate => path}/JSONPath.java | 43 +++---- .../json/actions/path/PathDelimiter.java | 43 +++++++ .../actions/traverse/JSONTraverseAction.java | 15 +++ .../json/actions/traverse/JSONTraverser.java | 66 +---------- .../actions/traverse/KeysPrintAction.java | 68 +++++++++++ ...Action.java => LocatePathsJSONAction.java} | 52 ++++++--- ...ion.java => RemoveElementsJSONAction.java} | 44 ++++---- ...Action.java => RemovePathsJsonAction.java} | 42 +++---- ...Action.java => RetainPathsJsonAction.java} | 66 ++++++----- .../json/actions/traverse/TraverseAction.java | 62 ---------- .../actions/traverse/TreeTraverseAction.java | 74 ++++++++++++ .../json/actions/traverse/TreeTraverser.java | 106 ++++++++++++++++++ .../json/actions/traverse/package-info.java | 8 +- .../actions/traverse/KeysPrintActionTest.java | 43 +++++++ .../json/test/actions/JSONPathTest.java | 12 +- .../json/test/actions/PathLocatorTest.java | 24 ++-- .../json/test/actions/PathRemoverTest.java | 4 +- .../json/test/actions/PathsRetainerTest.java | 9 +- 26 files changed, 601 insertions(+), 296 deletions(-) create mode 100644 json-smart/src/main/java/net/minidev/json/actions/path/DotDelimiter.java rename json-smart/src/main/java/net/minidev/json/actions/{navigate => path}/JSONPath.java (79%) create mode 100644 json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverseAction.java create mode 100644 json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java rename json-smart/src/main/java/net/minidev/json/actions/traverse/{LocatePathsAction.java => LocatePathsJSONAction.java} (57%) rename json-smart/src/main/java/net/minidev/json/actions/traverse/{RemoveElementsAction.java => RemoveElementsJSONAction.java} (55%) rename json-smart/src/main/java/net/minidev/json/actions/traverse/{RemovePathsAction.java => RemovePathsJsonAction.java} (56%) rename json-smart/src/main/java/net/minidev/json/actions/traverse/{RetainPathsAction.java => RetainPathsJsonAction.java} (50%) delete mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java create mode 100644 json-smart/src/test/java/net/minidev/json/actions/traverse/KeysPrintActionTest.java diff --git a/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java b/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java index 06db5a8c..c501b58e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java +++ b/json-smart/src/main/java/net/minidev/json/actions/ElementRemover.java @@ -1,10 +1,9 @@ package net.minidev.json.actions; -import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.actions.traverse.JSONTraverser; -import net.minidev.json.actions.traverse.RemoveElementsAction; -import net.minidev.json.actions.traverse.TraverseAction; +import net.minidev.json.actions.traverse.RemoveElementsJsonAction; +import net.minidev.json.actions.traverse.JSONTraverseAction; import java.util.*; @@ -43,7 +42,7 @@ public ElementRemover(JSONObject elementsToRemove) public JSONObject remove(JSONObject objectToClean) { - TraverseAction strategy = new RemoveElementsAction(this.elementsToRemove); + JSONTraverseAction strategy = new RemoveElementsJsonAction(this.elementsToRemove); JSONTraverser traversal = new JSONTraverser(strategy); traversal.traverse(objectToClean); return (JSONObject) strategy.result(); diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java index 1e80a1f1..e66eea55 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java @@ -2,9 +2,11 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.PathDelimiter; import net.minidev.json.actions.traverse.JSONTraverser; -import net.minidev.json.actions.traverse.LocatePathsAction; -import net.minidev.json.actions.traverse.TraverseAction; +import net.minidev.json.actions.traverse.LocatePathsJsonAction; +import net.minidev.json.actions.traverse.JSONTraverseAction; import java.util.ArrayList; import java.util.Arrays; @@ -29,6 +31,7 @@ public class PathLocator { private List pathsToFind; + private PathDelimiter pathDelimiter = new DotDelimiter().withAcceptDelimiterInNodeName(false); public PathLocator(JSONArray pathsToFind) { @@ -56,9 +59,14 @@ public PathLocator(String... pathsToFind) Collections.emptyList() : Arrays.asList(pathsToFind); } - public List find(JSONObject object) + public PathLocator with(PathDelimiter pathDelimiter) { + this.pathDelimiter = pathDelimiter; + return this; + } + + public List locate(JSONObject object) { - TraverseAction action = new LocatePathsAction(this.pathsToFind); + JSONTraverseAction action = new LocatePathsJsonAction(this.pathsToFind, pathDelimiter); JSONTraverser traversal = new JSONTraverser(action); traversal.traverse(object); return (List) action.result(); diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java index 1a2568ea..f2532da4 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java @@ -3,8 +3,8 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.actions.traverse.JSONTraverser; -import net.minidev.json.actions.traverse.RemovePathsAction; -import net.minidev.json.actions.traverse.TraverseAction; +import net.minidev.json.actions.traverse.RemovePathsJsonAction; +import net.minidev.json.actions.traverse.JSONTraverseAction; import java.util.ArrayList; import java.util.Arrays; @@ -66,7 +66,7 @@ public PathRemover(String... pathsToRemove) public JSONObject remove(JSONObject objectToClean) { - TraverseAction strategy = new RemovePathsAction(this.pathsToRemove); + JSONTraverseAction strategy = new RemovePathsJsonAction(this.pathsToRemove); JSONTraverser traversal = new JSONTraverser(strategy); traversal.traverse(objectToClean); return (JSONObject) strategy.result(); diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java index 7a73f5e3..7e256814 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java @@ -2,10 +2,12 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; -import net.minidev.json.actions.traverse.RetainPathsAction; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.PathDelimiter; +import net.minidev.json.actions.traverse.JSONTraverseAction; import net.minidev.json.actions.traverse.JSONTraverser; -import net.minidev.json.actions.traverse.LocatePathsAction; -import net.minidev.json.actions.traverse.TraverseAction; +import net.minidev.json.actions.traverse.LocatePathsJsonAction; +import net.minidev.json.actions.traverse.RetainPathsJsonAction; import java.util.Arrays; import java.util.Collections; @@ -34,6 +36,7 @@ public class PathsRetainer { protected List pathsToRetain; + private PathDelimiter pathDelimiter = new DotDelimiter().withAcceptDelimiterInNodeName(false); public PathsRetainer(JSONArray pathsToRetain) { @@ -60,26 +63,31 @@ public PathsRetainer(String... pathsToRetain) this.pathsToRetain = pathsToRetain == null || pathsToRetain.length == 0 ? Collections.emptyList() : new LinkedList(Arrays.asList(pathsToRetain)); } + + public PathsRetainer with(PathDelimiter pathDelimiter) { + this.pathDelimiter = pathDelimiter; + return this; + } public JSONObject retain(JSONObject object) { /** * a path to retain which contains a path in the object, but is not itself a path in the object, - * will cause the sub-path to be retain although it shouldn't: + * will cause the sub-path to be retained although it shouldn't: * object = {k0:v0} retain = {k0.k1} * so the false path to retain has to be removed from the pathsToRetain list. * - * The {@link LocatePathsAction} returns only paths which exist in the object. + * The {@link LocatePathsJsonAction} returns only paths which exist in the object. */ - TraverseAction locateAction = new LocatePathsAction(pathsToRetain); + JSONTraverseAction locateAction = new LocatePathsJsonAction(pathsToRetain, pathDelimiter); JSONTraverser t1 = new JSONTraverser(locateAction); t1.traverse(object); List realPathsToRetain = (List) locateAction.result(); //now reduce the object using only existing paths - TraverseAction reduce = new RetainPathsAction(realPathsToRetain); - JSONTraverser t2 = new JSONTraverser(reduce); + JSONTraverseAction retainer = new RetainPathsJsonAction(realPathsToRetain, pathDelimiter); + JSONTraverser t2 = new JSONTraverser(retainer); t2.traverse(object); - return (JSONObject) reduce.result(); + return (JSONObject) retainer.result(); } } diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java index e044167c..ba62fafd 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java @@ -2,6 +2,7 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.JSONPath; import java.util.Collection; import java.util.Stack; @@ -54,7 +55,7 @@ public boolean handleNavigationStart(JSONObject source, Collection paths } @Override - public boolean handleObjectStartAndRecur(JSONPath jp, JSONObject o) + public boolean handleJSONObject(JSONPath jp, JSONObject o) { //reached JSONObject node - instantiate it and recur handleNewNode(jp, new JSONObject()); @@ -73,7 +74,7 @@ else if (destNodeStack.peek() instanceof JSONArray) { } @Override - public boolean handleArrayStartAndRecur(JSONPath jp, JSONArray o) + public boolean handleJSONArrray(JSONPath jp, JSONArray o) { //reached JSONArray node - instantiate it and recur handleNewNode(jp, new JSONArray()); @@ -86,17 +87,17 @@ public void handlePrematureNavigatedBranchEnd(JSONPath jp, Object source) { } @Override - public void handleObjectLeaf(JSONPath jp, Object o) { + public void handleJSONObjectLeaf(JSONPath jp, Object o) { ((JSONObject) destNodeStack.peek()).put(jp.curr(), o); } @Override - public void handleArrayLeaf(int arrIndex, Object o) { + public void handleJSONArrayLeaf(int arrIndex, Object o) { ((JSONArray) destNodeStack.peek()).add(o); } @Override - public void handleArrayEnd(JSONPath jp) { + public void handleJSONArrayEnd(JSONPath jp) { destNodeStack.pop(); } diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java index b5789af9..06da109f 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java @@ -2,6 +2,8 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.JSONPath; import java.util.Arrays; import java.util.Collections; @@ -80,7 +82,7 @@ public void nav(JSONObject object) throws Exception { if (path != null && !path.equals("") && action.handleNextPath(path)) { - JSONPath jp = new JSONPath(path); + JSONPath jp = new JSONPath(path, new DotDelimiter().withAcceptDelimiterInNodeName(true)); nav(object, jp); action.handlePathEnd(path); } @@ -115,12 +117,12 @@ private void nav(JSONObject source, JSONPath jp) // the specified path is illegal because it does not exist in the source. action.handlePrematureNavigatedBranchEnd(jp, source); } - else if (source.get(next) instanceof JSONObject && action.handleObjectStartAndRecur(jp, (JSONObject) source.get(next))) + else if (source.get(next) instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) source.get(next))) { //reached JSONObject node - handle it and recur into it nav((JSONObject) source.get(next), jp); } - else if (source.get(next) instanceof JSONArray && action.handleArrayStartAndRecur(jp, (JSONArray) source.get(next))) + else if (source.get(next) instanceof JSONArray && action.handleJSONArrray(jp, (JSONArray) source.get(next))) { //reached JSONArray node - handle it and recur into it nav((JSONArray) source.get(next), jp); @@ -134,7 +136,7 @@ else if (jp.hasNext()) else if (!jp.hasNext()) { //reached leaf in source and specified path is also at leaf -> handle it - action.handleObjectLeaf(jp, source.get(next)); + action.handleJSONObjectLeaf(jp, source.get(next)); } else { @@ -154,7 +156,7 @@ private void nav(JSONArray source, JSONPath jp) int arrIndex = 0; for (Object arrItem : source.toArray()) { - if (arrItem instanceof JSONObject && action.handleObjectStartAndRecur(jp, (JSONObject) arrItem)) + if (arrItem instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) arrItem)) { // clone the path so that for each JSONObject in the array, // the iterator continues from the same position in the path @@ -168,11 +170,11 @@ else if (arrItem instanceof JSONArray) else if (!jp.hasNext()) { //reached leaf - handle it - action.handleArrayLeaf(arrIndex, arrItem); + action.handleJSONArrayLeaf(arrIndex, arrItem); } arrIndex++; } - action.handleArrayEnd(jp); + action.handleJSONArrayEnd(jp); } diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java index 72af94f7..eddf5438 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java @@ -2,6 +2,7 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.JSONPath; import java.util.Collection; @@ -53,13 +54,13 @@ public interface NavigateAction * called when an object node is encountered on the path * @return true if the navigator should navigate into the object */ - boolean handleObjectStartAndRecur(JSONPath jp, JSONObject sourceNode); + boolean handleJSONObject(JSONPath jp, JSONObject sourceNode); /** * called when an array node is encountered on the path * @return true if the navigator should navigate into the array */ - boolean handleArrayStartAndRecur(JSONPath jp, JSONArray sourceNode); + boolean handleJSONArrray(JSONPath jp, JSONArray sourceNode); /** * called when a leaf node is reached in a JSONObject. @@ -67,7 +68,7 @@ public interface NavigateAction * (it is not a JSONObject nor a JSONArray) * @param jp - the JsonPath pointing to the leaf */ - void handleObjectLeaf(JSONPath jp, Object value); + void handleJSONObjectLeaf(JSONPath jp, Object value); /** * called when a leaf in a JSONArray is reached. @@ -76,13 +77,13 @@ public interface NavigateAction * @param arrIndex - the index of the item in the JSONArray * @param arrItem - the item */ - void handleArrayLeaf(int arrIndex, Object arrItem); + void handleJSONArrayLeaf(int arrIndex, Object arrItem); /** * called after all the items of an array have been visited * @param jp - the JsonPath pointing to the array */ - void handleArrayEnd(JSONPath jp); + void handleJSONArrayEnd(JSONPath jp); /** * called after all the entries of a JSONObject have been visited diff --git a/json-smart/src/main/java/net/minidev/json/actions/path/DotDelimiter.java b/json-smart/src/main/java/net/minidev/json/actions/path/DotDelimiter.java new file mode 100644 index 00000000..2ea2bc07 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/path/DotDelimiter.java @@ -0,0 +1,23 @@ +package net.minidev.json.actions.path; + +/** + * Encapsulates the delimiter '.' of the path parts when the path is specified in n-gram format. + * For example: root.node1.node11.leaf + * + * @author adoneitan@gmail.com + * @since 31 May2016 + */ +public class DotDelimiter extends PathDelimiter +{ + protected static final char DELIM_CHAR = '.'; + + public DotDelimiter() + { + super(DELIM_CHAR); + } + + public String regex() + { + return "\\."; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java b/json-smart/src/main/java/net/minidev/json/actions/path/JSONPath.java similarity index 79% rename from json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java rename to json-smart/src/main/java/net/minidev/json/actions/path/JSONPath.java index 2bf589ff..2ab7d85a 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONPath.java +++ b/json-smart/src/main/java/net/minidev/json/actions/path/JSONPath.java @@ -1,4 +1,4 @@ -package net.minidev.json.actions.navigate; +package net.minidev.json.actions.path; import net.minidev.json.JSONObject; @@ -17,6 +17,7 @@ */ public class JSONPath { + protected enum Step {NONE, NEXT, PREV} protected final String path; protected List keys; @@ -25,12 +26,14 @@ protected enum Step {NONE, NEXT, PREV} protected Step lastStep; protected StringBuilder origin; protected StringBuilder remainder; + protected PathDelimiter delim; - public JSONPath(String path) + public JSONPath(String path, PathDelimiter delim) { + this.delim = delim; checkPath(path); this.path = path; - this.keys = Arrays.asList(path.split("\\.")); + this.keys = Arrays.asList(path.split(delim.regex())); reset(); } @@ -90,26 +93,26 @@ private void remainderDecrement() { if (length() == 1) remainder = new StringBuilder(""); - else if (remainder.indexOf(".") < 0) + else if (remainder.indexOf(delim.str()) < 0) remainder = new StringBuilder(""); else - remainder.delete(0, remainder.indexOf(".") + 1); + remainder.delete(0, remainder.indexOf(delim.str()) + 1); } private void originDecrement() { if (length() == 1) origin = new StringBuilder(""); - else if (origin.indexOf(".") < 0) + else if (origin.indexOf(delim.str()) < 0) origin = new StringBuilder(""); else - origin.delete(origin.lastIndexOf("."), origin.length()); + origin.delete(origin.lastIndexOf(delim.str()), origin.length()); } private void originIncrement() { if (origin.length() != 0) { - origin.append('.'); + origin.append(delim.chr()); } origin.append(currKey); } @@ -119,7 +122,7 @@ private void remainderIncrement(String prev) if (remainder.length() == 0) remainder = new StringBuilder(prev); else - remainder = new StringBuilder(prev).append('.').append(remainder); + remainder = new StringBuilder(prev).append(delim.chr()).append(remainder); } /** @@ -178,7 +181,7 @@ public String subPath(int firstIndex, int lastIndex) { sb.append(keys.get(i)); if (i < lastIndex) { - sb.append('.'); + sb.append(delim.chr()); } } sb.trimToSize(); @@ -189,14 +192,14 @@ private void checkPath(String path) { if (path == null || path.equals("")) throw new IllegalArgumentException("path cannot be null or empty"); - if (path.startsWith(".") || path.endsWith(".") || path.contains("src/main")) - throw new IllegalArgumentException("path cannot start or end with '.' or contain '..'"); + if (path.startsWith(delim.str()) || path.endsWith(delim.str()) || path.contains(delim.str() + delim.str())) + throw new IllegalArgumentException(String.format("path cannot start or end with %s or contain '%s%s'", delim.str(), delim.str(), delim.str())); } @Override public JSONPath clone() throws CloneNotSupportedException { - JSONPath cloned = new JSONPath(this.path); + JSONPath cloned = new JSONPath(this.path, this.delim); while (cloned.nextIndex() != this.nextIndex()) { cloned.next(); } @@ -218,12 +221,13 @@ public boolean equals(Object o) { JSONPath jsonPath = (JSONPath) o; return path().equals(jsonPath.path()) && - hasNext() == jsonPath.hasNext() && - hasPrev() == jsonPath.hasPrev() && - curr().equals(jsonPath.curr()) && - origin().equals(jsonPath.origin()) && - remainder().equals(jsonPath.remainder()) && - lastStep == jsonPath.lastStep; + hasNext() == jsonPath.hasNext() && + hasPrev() == jsonPath.hasPrev() && + curr().equals(jsonPath.curr()) && + origin().equals(jsonPath.origin()) && + remainder().equals(jsonPath.remainder()) && + lastStep == jsonPath.lastStep && + delim.equals(jsonPath.delim); } @@ -236,6 +240,7 @@ public int hashCode() { result = 31 * result + lastStep.hashCode(); result = 31 * result + origin.hashCode(); result = 31 * result + remainder.hashCode(); + result = 31 * result + delim.hashCode(); return result; } } diff --git a/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java b/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java new file mode 100644 index 00000000..494406cb --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java @@ -0,0 +1,43 @@ +package net.minidev.json.actions.path; + +/** + * Encapsulates the delimiter of the path parts when given in n-gram format. + * + * @author adoneitan@gmail.com + * @since 31 May2016 + */ +public abstract class PathDelimiter +{ + protected char delimChar; + protected String delimStr; + protected boolean acceptDelimInKey; + + public PathDelimiter(char delim) + { + this.delimChar = delim; + this.delimStr = "" + delim; + } + + public PathDelimiter withAcceptDelimiterInNodeName(boolean acceptDelimInKey) { + this.acceptDelimInKey = acceptDelimInKey; + return this; + } + + public boolean accept(String key) + { + if (!acceptDelimInKey && key.contains(delimStr)) + return false; + return true; + } + + public String str() { + return delimStr; + } + + public char chr() { + return delimChar; + } + + public abstract String regex(); + +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverseAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverseAction.java new file mode 100755 index 00000000..19aa6f20 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverseAction.java @@ -0,0 +1,15 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +/** + * An interface for a processing action on the nodes of a {@link JSONObject} while traversing it. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public interface JSONTraverseAction extends TreeTraverseAction +{ +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java index ce543a20..2a6d62a0 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java @@ -3,14 +3,11 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; -import java.util.Iterator; -import java.util.Map; - /** * Traverses every node of a {@link JSONObject} *

* {@link JSONTraverser} accepts an action and provides callback hooks for it to act - * on the traversed nodes at each significant step. See {@link TraverseAction}. + * on the traversed nodes at each significant step. See {@link JSONTraverseAction}. *

* A key to the right of a dot is a direct child of a key to the left of a dot. * Keys with a dot in their name are not supported. @@ -20,65 +17,12 @@ * @author adoneitan@gmail.com * */ -public class JSONTraverser +public class JSONTraverser extends TreeTraverser { - private TraverseAction action; - - public JSONTraverser(TraverseAction action) - { - this.action = action; - } - - public void traverse(JSONObject object) - { - if (action.handleStart(object)){ - breadthFirst("", object); - } - action.handleEnd(); - } - - private void breadthFirst(String fullPathToObject, JSONObject jsonObject) - { - if (jsonObject == null || jsonObject.entrySet() == null) { - return; - } - Iterator> it = jsonObject.entrySet().iterator(); - while (it.hasNext() && action.handleNext()) - { - Map.Entry entry = it.next(); - if (entry.getKey().contains(".") && !action.handleDotChar()) - { - //a dot char '.' in the key is not supported by the action, abandon this path - continue; - } - String fullPathToEntry = "".equals(fullPathToObject) ? entry.getKey() : fullPathToObject + "." + entry.getKey(); - if (action.handleEntryAndIgnoreChildren(fullPathToEntry, it, entry)) - { - continue; - } - else if (entry.getValue() instanceof JSONObject && action.handleJSONObjectChild()) - { - breadthFirst(fullPathToEntry, (JSONObject) entry.getValue()); - } - else if (entry.getValue() instanceof JSONArray && action.handleJSONArrayChild()) - { - breadthFirst(fullPathToEntry, (JSONArray) entry.getValue()); - } - } - } + private JSONTraverseAction action; - private void breadthFirst(String fullPathToObject, JSONArray jsonArray) + public JSONTraverser(JSONTraverseAction action) { - for (Object arrItem : jsonArray.toArray()) - { - if (arrItem instanceof JSONObject) - { - breadthFirst(fullPathToObject, (JSONObject) arrItem); - } - else if (arrItem instanceof JSONArray) - { - breadthFirst(fullPathToObject, (JSONArray) arrItem); - } - } + super(action); } } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java new file mode 100644 index 00000000..b3e8e993 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java @@ -0,0 +1,68 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.ParseException; + +import java.util.Map; + +/** + * @author adoneitan@gmail.com + * @since 5/24/16. + */ +public class KeysPrintAction implements JSONTraverseAction +{ + @Override + public boolean start(JSONObject object) + { + return true; + } + + @Override + public boolean traverseEntry(String fullPathToEntry, Map.Entry entry) + { + System.out.println(entry.getKey()); + return true; + } + + @Override + public boolean recurInto(String pathToEntry, Object entryValue) { + return true; + } + + @Override + public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { + return true; + } + + @Override + public void handleLeaf(String pathToEntry, Object entryValue) + { + + } + + @Override + public void handleLeaf(String fullPathToContainingList, int listIndex, Object listItem) + { + + } + + @Override + public boolean removeEntry(String fullPathToEntry, Map.Entry entry) + { + return false; + } + + @Override + public void end() + { + + } + + @Override + public Object result() + { + return null; + } + +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java similarity index 57% rename from json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java rename to json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java index 6d7a3709..c26400d2 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java @@ -1,8 +1,8 @@ package net.minidev.json.actions.traverse; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.PathDelimiter; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -20,61 +20,68 @@ * @author adoneitan@gmail.com * */ -public class LocatePathsAction implements TraverseAction +public class LocatePathsJsonAction implements JSONTraverseAction { protected List pathsFound; protected List pathsToFind; + protected PathDelimiter delim; /** * * @param pathsToFind A path to a field in the {@link JSONObject} should be specified in n-gram format where keys are chained: * k0[[[.k1].k2]...] */ - public LocatePathsAction(List pathsToFind) + public LocatePathsJsonAction(List pathsToFind, PathDelimiter delim) { this.pathsToFind = pathsToFind; + this.delim = delim; pathsFound = new LinkedList(); } @Override - public boolean handleStart(JSONObject object) + public boolean start(JSONObject object) { return object != null && pathsToFind != null && pathsToFind.size() > 0; } @Override - public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + public boolean traverseEntry(String fullPathToEntry, Map.Entry entry) { - if (pathsToFind.contains(pathToEntry)) - { - //reached end of path that is being searched - pathsFound.add(pathToEntry); + if (!delim.accept(entry.getKey())) { + return false; } - return false; + locatePath(fullPathToEntry); + return true; } @Override - public boolean handleDotChar() { - return false; + public boolean recurInto(String pathToEntry, Object entryValue) { + return true; } @Override - public boolean handleNext() { + public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { return true; } @Override - public boolean handleJSONObjectChild() { - return true; + public void handleLeaf(String pathToEntry, Object entryValue) { + } @Override - public boolean handleJSONArrayChild() { - return true; + public void handleLeaf(String fullPathToContainingList, int listIndex, Object listItem) { + } @Override - public void handleEnd() { + public boolean removeEntry(String fullPathToEntry, Map.Entry entry) + { + return false; + } + + @Override + public void end() { //nothing to do } @@ -82,4 +89,13 @@ public void handleEnd() { public Object result() { return pathsFound; } + + private void locatePath(String pathToEntry) + { + if (pathsToFind.contains(pathToEntry)) + { + //reached end of path that is being searched + pathsFound.add(pathToEntry); + } + } } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java similarity index 55% rename from json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java rename to json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java index 948ffac1..20ad762a 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java @@ -2,9 +2,8 @@ import net.minidev.json.JSONObject; -import java.util.Iterator; -import java.util.List; import java.util.Map; +import java.util.Map.Entry; /** * Removes key:value elements from a {@link JSONObject}. @@ -19,64 +18,67 @@ * @author adoneitan@gmail.com * */ -public class RemoveElementsAction implements TraverseAction +public class RemoveElementsJsonAction implements JSONTraverseAction { protected JSONObject result; protected final Map elementsToRemove; protected final boolean allowDotChar; - public RemoveElementsAction(Map elementsToRemove, boolean allowDotChar) + public RemoveElementsJsonAction(Map elementsToRemove, boolean allowDotChar) { this.elementsToRemove = elementsToRemove; this.allowDotChar = allowDotChar; } - public RemoveElementsAction(Map elementsToRemove) + public RemoveElementsJsonAction(Map elementsToRemove) { this(elementsToRemove, false); } @Override - public boolean handleStart(JSONObject object) + public boolean start(JSONObject object) { result = object; return object != null && elementsToRemove != null && elementsToRemove.size() > 0; } @Override - public boolean handleDotChar() { - return allowDotChar; - } - - @Override - public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + public boolean removeEntry(String fullPathToEntry, Entry entry) { - if (elementsToRemove.entrySet().contains(entry)) - { - it.remove(); - } - return false; + return elementsToRemove.entrySet().contains(entry); } @Override - public boolean handleNext() + public boolean traverseEntry(String fullPathToEntry, Entry entry) { //must traverse the whole object return true; } @Override - public boolean handleJSONObjectChild() { + public boolean recurInto(String pathToEntry, Object entryValue) { return true; } @Override - public boolean handleJSONArrayChild() { + public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { return true; } @Override - public void handleEnd() { + public void handleLeaf(String pathToEntry, Object entryValue) + { + + } + + @Override + public void handleLeaf(String fullPathToContainingList, int listIndex, Object listItem) + { + + } + + @Override + public void end() { //nothing to do } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java similarity index 56% rename from json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsAction.java rename to json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java index 0b41d8e9..859bdd5e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java @@ -2,7 +2,6 @@ import net.minidev.json.JSONObject; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -19,59 +18,60 @@ * @author adoneitan@gmail.com * */ -public class RemovePathsAction implements TraverseAction +public class RemovePathsJsonAction implements JSONTraverseAction { protected JSONObject result; protected List pathsToRemove; - public RemovePathsAction(List pathsToRemove) + public RemovePathsJsonAction(List pathsToRemove) { this.pathsToRemove = pathsToRemove; } @Override - public boolean handleStart(JSONObject object) + public boolean start(JSONObject object) { result = object; return object != null && pathsToRemove != null && pathsToRemove.size() > 0; } @Override - public boolean handleDotChar() { - return true; - } - - @Override - public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) + public boolean removeEntry(String fullPathToEntry, Map.Entry entry) { - if (pathsToRemove.contains(pathToEntry)) - { - it.remove(); - //the entry has been removed from the traversal iterator, no point in traversing its children - return true; - } - return false; + return pathsToRemove.contains(fullPathToEntry); } @Override - public boolean handleNext() + public boolean traverseEntry(String fullPathToEntry, Map.Entry entry) { //must traverse the whole object return true; } @Override - public boolean handleJSONObjectChild() { + public boolean recurInto(String pathToEntry, Object entryValue) { return true; } @Override - public boolean handleJSONArrayChild() { + public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { return true; } @Override - public void handleEnd() { + public void handleLeaf(String pathToEntry, Object entryValue) + { + + } + + @Override + public void handleLeaf(String fullPathToContainingList, int listIndex, Object listItem) + { + + } + + @Override + public void end() { //nothing to do } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java similarity index 50% rename from json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsAction.java rename to json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java index 49a9361e..7671a891 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java @@ -1,11 +1,11 @@ package net.minidev.json.actions.traverse; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.PathDelimiter; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; /** * Retain branches or parts of branches matching a specified list of paths. @@ -23,18 +23,20 @@ * @author adoneitan@gmail.com * */ -public class RetainPathsAction implements TraverseAction +public class RetainPathsJsonAction implements JSONTraverseAction { + private final PathDelimiter delim; protected JSONObject result; protected List pathsToRetain; - public RetainPathsAction(List pathsToRetain) + public RetainPathsJsonAction(List pathsToRetain, PathDelimiter delim) { this.pathsToRetain = new ArrayList(pathsToRetain); + this.delim = delim; } @Override - public boolean handleStart(JSONObject object) + public boolean start(JSONObject object) { if (object == null) { @@ -51,48 +53,35 @@ public boolean handleStart(JSONObject object) } @Override - public boolean handleEntryAndIgnoreChildren(String pathToEntry, Iterator> it, Map.Entry entry) - { - /** - * if the full path to the entry is not contained in any of the paths to retain - remove it from the object - * this step does not remove entries whose full path is contained in a path to retain but are not equal to an - * entry to retain - */ - if (!foundStartsWith(pathToEntry) || entry.getKey().contains(".")) - { - it.remove(); - //the entry has been removed from the traversal iterator, no point in traversing its children - return true; - } - return false; + public boolean traverseEntry(String fullPathToEntry, Entry entry) { + return true; } @Override - public boolean handleDotChar() - { - //need to reach handleEntryAndIgnoreChildren() in order to remove the path containing the dot char + public boolean recurInto(String fullPathToSubtree, Object entryValue) { return true; } @Override - public boolean handleNext() - { - //must traverse the whole object + public boolean recurInto(String fullPathToArrayItem, int arrIndex, Object entryValue) { return true; } @Override - public boolean handleJSONObjectChild() { - return true; + public void handleLeaf(String pathToEntry, Object entryValue) { } @Override - public boolean handleJSONArrayChild(){ - return true; + public void handleLeaf(String fullPathToContainingList, int listIndex, Object listItem) { } @Override - public void handleEnd() + public boolean removeEntry(String fullPathToEntry, Entry entry) { + return discardPath(fullPathToEntry, entry); + } + + @Override + public void end() { // nothing to do } @@ -102,7 +91,22 @@ public Object result() { return result; } - protected boolean foundStartsWith(String path) + /** + * if the full path to the entry is not contained in any of the paths to retain - remove it from the object + * this step does not remove entries whose full path is contained in a path to retain but are not equal to an + * entry to retain + */ + protected boolean discardPath(String pathToEntry, Entry entry) + { + if (!foundAsPrefix(pathToEntry) || !delim.accept(entry.getKey())) + { + //skip traversal of subtree and remove from the traversal iterator + return true; + } + return false; + } + + protected boolean foundAsPrefix(String path) { for (String p : pathsToRetain) { if (p == path || (p != null && path != null && p.startsWith(path))) { diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java deleted file mode 100755 index 6184c6f4..00000000 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/TraverseAction.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.minidev.json.actions.traverse; - -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; - -import java.util.Iterator; -import java.util.Map; - -/** - * An interface for a processing action on the nodes of a {@link JSONObject} while traversing it. - *

- * See package-info for more details - * - * @author adoneitan@gmail.com - */ -public interface TraverseAction -{ - /** - * called before any traversal of the {@link JSONObject} starts - * @return true if traversal should start at all - */ - boolean handleStart(JSONObject object); - - /** - * @return false if encountering a key containing a '.' char on the path should abort the traversal of the path - */ - boolean handleDotChar(); - - /** - * called for each entry in given level of the {@link JSONObject} - * @return false if children of the specified entry should not be traversed - */ - boolean handleEntryAndIgnoreChildren(String fullPathToObject, Iterator> it, Map.Entry entry); - - /** - * called before the next entry in a given level of the {@link JSONObject} is processed - * @return true if siblings of the last entry should be processed - */ - boolean handleNext(); - - /** - * called if the child of the currently processed entry is a {@link JSONObject} - * @return true if a {@link JSONObject} child of the current entry should be processed - */ - boolean handleJSONObjectChild(); - - /** - * called if the child of the currently processed entry is a {@link JSONArray} - * @return true if a {@link JSONArray} child of the current entry should be processed - */ - boolean handleJSONArrayChild(); - - /** - * called after the traversal ends, and just before the traversal method exits - */ - void handleEnd(); - - /** - * holds the result of the traversal, as assigned by the action implementing this interface - */ - Object result(); -} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java new file mode 100755 index 00000000..7ac97d71 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java @@ -0,0 +1,74 @@ +package net.minidev.json.actions.traverse; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * An interface for a processing action on the nodes of a {@link M} tree + * while traversing it. The order in which the callbacks are listed + * below is the order in which they are called by the {@link TreeTraverser} + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public interface TreeTraverseAction, L extends List> +{ + /** + * called before any traversal of the {@link M} tree starts + * @return true if traversal should start at all + */ + boolean start(M object); + + /** + * called when a new entry is encountered and before any processing is performed on it + * @return true if the entry should be processed + */ + boolean traverseEntry(String fullPathToEntry, Entry entry); + + /** + * the last callback for each entry in an {@link M} map. if this method returns true + * the {@link TreeTraverser} removes the entry from the map. there is no further + * handling of the entry. + * @return true if the entry and its subtree should be removed from the {@link M} tree + */ + boolean removeEntry(String fullPathToEntry, Entry entry); + + /** + * called when a non-leaf entry is encountered inside an {@Link M} object + * @return true if the non-leaf entry should be recursively traversed + */ + boolean recurInto(String fullPathToSubtree, Object entryValue); + + /** + * called when a non-leaf item is encountered inside an {@Link L} object + * @return true if the non-leaf item should be recursively traversed + */ + boolean recurInto(String fullPathToContainingList, int listIndex, Object entryValue); + + /** + * called for each leaf of an {@link M} map is encountered + * @param entryValue - the item + */ + void handleLeaf(String fullPathToEntry, Object entryValue); + + /** + * called for each leaf of an {@link L} list is encountered + * @param listItem - the item + * @param listIndex - the ordered location of the item in the list + */ + void handleLeaf(String fullPathToContainingList, int listIndex, Object listItem); + + /** + * called after the traversal ends, + * and just before the {@link #start(M)} method exits + */ + void end(); + + /** + * holds the result of the traversal, + * as assigned by the action implementing this interface + */ + Object result(); +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java new file mode 100755 index 00000000..4fbf84e1 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java @@ -0,0 +1,106 @@ +package net.minidev.json.actions.traverse; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Traverses every node of a tree made up of a combination of {@link Map}s and {@link List}s + *

+ * {@link TreeTraverser} accepts an action and provides callback hooks for it to act + * on the traversed nodes at each significant step. See {@link TreeTraverseAction}. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + * + */ +public class TreeTraverser, L extends List> +{ + private TreeTraverseAction action; + + public TreeTraverser(TreeTraverseAction action) + { + this.action = action; + } + + public void traverse(M map) + { + if (action.start(map)){ + depthFirst("", map); + } + action.end(); + } + + private void depthFirst(String fullPathToMap, M map) + { + if (map == null || map.entrySet() == null || !action.recurInto(fullPathToMap, map)) { + return; + } + Iterator> it = map.entrySet().iterator(); + while (it.hasNext()) + { + Entry entry = it.next(); + String fullPathToEntry = buildPath(fullPathToMap, entry.getKey()); + + if (!action.traverseEntry(fullPathToEntry, entry)) { + continue; + } + else if (action.removeEntry(fullPathToEntry, entry)) + { + it.remove(); + continue; + } + + if (instanceOfMap(entry.getValue())) + { + depthFirst(fullPathToEntry, (M) entry.getValue()); + } + else if (instanceOfList(entry.getValue())) + { + depthFirst(fullPathToEntry, (L) entry.getValue()); + } + else if (!instanceOfMap(entry) && !instanceOfList(entry)) + { + action.handleLeaf(fullPathToEntry, entry); + } + } + } + + private void depthFirst(String fullPathToList, L list) + { + if (!action.recurInto(fullPathToList, list)) { + return; + } + int listIndex = 0; + for (Object listItem : list.toArray()) + { + if (instanceOfMap(listItem) && action.recurInto(fullPathToList, listIndex, listItem)) + { + depthFirst(fullPathToList, (M) listItem); + } + else if (instanceOfList(listItem) && action.recurInto(fullPathToList, listIndex, listItem)) + { + depthFirst(fullPathToList, (L) listItem); + } + else + { + action.handleLeaf(fullPathToList, listIndex, listItem); + } + listIndex++; + } + } + + private String buildPath(String fullPathToObject, String entryKey) { + return "".equals(fullPathToObject) ? entryKey : fullPathToObject + "." + entryKey; + } + + private boolean instanceOfList(Object obj) { + return obj instanceof List; + } + + private boolean instanceOfMap(Object obj) { + return obj instanceof Map; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java index dcb334a3..5fba45b8 100644 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/package-info.java @@ -3,12 +3,12 @@ * Traverse all the nodes in a {@link net.minidev.json.JSONObject} and process them *

* The traversal starts at the root and moves breadth-first down the branches. - * The {@link net.minidev.json.actions.traverse.JSONTraverser} accepts a - * {@link net.minidev.json.actions.traverse.TraverseAction} and provides callback hooks at each significant - * step which the {@link net.minidev.json.actions.traverse.TraverseAction} may use to process + * The {@link net.minidev.json.actions.traverse.TreeTraverser} accepts a + * {@link net.minidev.json.actions.traverse.JSONTraverseAction} and provides callback hooks at each significant + * step which the {@link net.minidev.json.actions.traverse.JSONTraverseAction} may use to process * the nodes. *

- * The {@link net.minidev.json.actions.traverse.JSONTraverser} assumes that paths in the tree which the + * The {@link net.minidev.json.actions.traverse.TreeTraverser} assumes that paths in the tree which the * {@link net.minidev.json.JSONObject} represents are specified in the n-gram format - a list of keys from the * root down separated by dots: *

diff --git a/json-smart/src/test/java/net/minidev/json/actions/traverse/KeysPrintActionTest.java b/json-smart/src/test/java/net/minidev/json/actions/traverse/KeysPrintActionTest.java new file mode 100644 index 00000000..29f580d8 --- /dev/null +++ b/json-smart/src/test/java/net/minidev/json/actions/traverse/KeysPrintActionTest.java @@ -0,0 +1,43 @@ +package net.minidev.json.actions.traverse; + +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.ParseException; +import org.junit.Test; + +/** + * @author adoneitan@gmail.com + * @since 30 May 2016 + */ +public class KeysPrintActionTest +{ + @Test + public void test() throws ParseException + { + KeysPrintAction p = new KeysPrintAction(); + JSONTraverser t = new JSONTraverser(p); + JSONObject jo = (JSONObject) JSONValue.parseWithException( + "{" + + "\"k0\":{" + + "\"k01\":{" + + "\"k011\":\"v2\"" + + "}" + + "}," + + "\"k1\":{" + + "\"k11\":{" + + "\"k111\":\"v5\"" + + "}," + + "\"k12\":{" + + "\"k121\":\"v5\"" + + "}" + + "}," + + "\"k3\":{" + + "\"k31\":{" + + "\"k311\":\"v5\"" + + "}" + + "}" + + "}" + ); + t.traverse(jo); + } +} \ No newline at end of file diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java index 54031296..4fae7f60 100755 --- a/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java +++ b/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java @@ -1,6 +1,8 @@ package net.minidev.json.test.actions; -import net.minidev.json.actions.navigate.JSONPath; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.JSONPath; +import net.minidev.json.actions.path.PathDelimiter; import org.junit.Test; import static org.junit.Assert.assertFalse; @@ -11,10 +13,12 @@ */ public class JSONPathTest { + private static final PathDelimiter delim = new DotDelimiter().withAcceptDelimiterInNodeName(true); + @Test public void testIterator() { - JSONPath jp = new JSONPath("a.b.c"); + JSONPath jp = new JSONPath("a.b.c", delim); assertTrue(jp.nextIndex() == 0); assertTrue(jp.prevIndex() == -1); assertTrue("".equals(jp.curr())); @@ -70,14 +74,14 @@ public void testIterator() @Test public void testSubPath() { - JSONPath jp = new JSONPath("a.b.c"); + JSONPath jp = new JSONPath("a.b.c", delim); assertTrue(jp.subPath(1,2).equals("b.c")); } @Test public void testClone() throws CloneNotSupportedException { - JSONPath jp1 = new JSONPath("a.b.c"); + JSONPath jp1 = new JSONPath("a.b.c", delim); JSONPath jp2 = jp1.clone(); assertTrue(jp1.equals(jp2)); diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java index 890bbfcc..29734110 100755 --- a/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathLocatorTest.java @@ -51,28 +51,28 @@ public static Collection params() {"{}", "", new String[]{} }, {"{}", "k1", new String[]{} }, {"{}", new String[]{}, new String[]{} }, - {"{}", new JSONArray(), new String[]{} }, - {"{}", new ArrayList(0), new String[]{} },//11 + {"{}", new JSONArray(), new String[]{} },//10 + {"{}", new ArrayList(0), new String[]{} }, //simple json, bad/empty keys {"{\"k0\":\"v0\"}", null, new String[]{} }, {"{\"k0\":\"v0\"}", "", new String[]{} }, {"{\"k0\":\"v0\"}", "k1", new String[]{} }, - {"{\"k0\":\"v0\"}", new String[]{}, new String[]{} }, + {"{\"k0\":\"v0\"}", new String[]{}, new String[]{} },//15 {"{\"k0\":\"v0\"}", new JSONArray(), new String[]{} }, - {"{\"k0\":\"v0\"}", new ArrayList(0), new String[]{} },//17 + {"{\"k0\":\"v0\"}", new ArrayList(0), new String[]{} }, //simple json, valid/invalid keys {"{\"k0\":\"v0\"}", "k0", new String[]{"k0"} }, {"{\"k0\":\"v0\"}", "v0", new String[]{} }, - {"{\"k0\":\"v0\"}", "k0.k1", new String[]{} }, + {"{\"k0\":\"v0\"}", "k0.k1", new String[]{} },//20 {"{\"k0\":\"v0\"}", "k1.k0", new String[]{} }, {"{\"k0\":null}", "k0", new String[]{"k0"} }, - {"{\"k0\":null}", null, new String[]{} },//23 + {"{\"k0\":null}", null, new String[]{} }, //key with dot char {"{\"k0.k1\":\"v0\"}", "k0", new String[]{} }, - {"{\"k0.k1\":\"v0\"}", "k1", new String[]{} }, + {"{\"k0.k1\":\"v0\"}", "k1", new String[]{} },//25 {"{\"k0.k1\":\"v0\"}", "k0.k1", new String[]{} }, // key with dot ambiguity @@ -86,19 +86,19 @@ public static Collection params() {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k1.k2", new String[]{"k1.k2"} }, //ignore non-existent keys but keep good keys - {"{\"k0\":\"v0\",\"k1\":\"v1\"}", new String[]{"k0","k2"}, new String[]{"k0"} }, + {"{\"k0\":\"v0\",\"k1\":\"v1\"}", new String[]{"k0","k2"}, new String[]{"k0"} },//35 {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k2"}, new String[]{"k0"} }, {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2"}, new String[]{"k0", "k1.k2"} }, {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2.k3"}, new String[]{"k0"} }, {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1.k2","k1"}, new String[]{"k0","k1","k1.k2"} }, - {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k0.k2"}, new String[]{"k0","k1"} }, + {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k0.k2"}, new String[]{"k0","k1"} },//40 {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k0","k1","k2"}, new String[]{"k0","k1"} }, {"{\"k0\":\"v0\",\"k1\":{\"k2\":\"v2\"}}", new String[]{"k1.k2"}, new String[]{"k1.k2"} }, //arrays - key inside array treated as child {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0", new String[]{"k0"} }, {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1", new String[]{"k0.k1"} }, - {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", new String[]{"k0.k1.k2"} }, + {"{\"k0\":{\"k1\":[1,{\"k2\":\"v2\"},3,4]}}", "k0.k1.k2", new String[]{"k0.k1.k2"} },//45 {"{\"k0\":{\"k1\":[{\"k2\":\"v2\"},{\"k2\":\"v2\"}]}}", "k0.k1.k2", new String[]{"k0.k1.k2", "k0.k1.k2"} }, }); } @@ -107,8 +107,8 @@ public static Collection params() public void test() throws ParseException { JSONObject objectToSearch = jsonToSearch != null ? (JSONObject) JSONValue.parseWithException(jsonToSearch) : null; - PathLocator f = switchKeyToRemove(); - List found = f.find(objectToSearch); + PathLocator locator = switchKeyToRemove(); + List found = locator.locate(objectToSearch); assertEquals(Arrays.asList(expectedFound), found); } diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java index 51d9c813..2f18cfc7 100755 --- a/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathRemoverTest.java @@ -49,11 +49,11 @@ public static Collection params() {"{\"first\": null}", new ArrayList(0), "{\"first\": null}" }, // empty list key {"{\"first\": null}", "first", "{}" }, // remove root key {"{\"first.f1\": null}", "first.f1", "{}" }, // key with dot - {"{\"first.f1\": \"withDot\", \"first\":{\"f1\": null}}", "first.f1", "{\"first\":{}}" }, // key with dot ambiguity + {"{\"first.f1\": \"withDot\", \"first\":{\"f1\": null}}", "first.f1", "{\"first\":{}}" }, //9 key with dot ambiguity {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}", "first.f2.f3.id", "{\"first\":{\"f2\":{\"f3\":{}}}}" }, // nested object remove single leaf {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}", "notfound", "{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}" }, // nested object key does not exist {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3.id", "{\"first\":{\"f2\":{\"f3\":{\"name\":\"me\"}}}}"}, // nested object remove first leaf - {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3.name", "{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}" }, // nested object remove last leaf + {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3.name", "{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\"}}}}" }, //13 nested object remove last leaf {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first.f2.f3", "{\"first\":{\"f2\":{}}}" }, // nested object remove intermediate node {"{\"first\":{\"f2\":{\"f3\":{\"id\":\"id1\",\"name\":\"me\"}}}}", "first", "{}" }, // nested object remove root {"{\"first\":{\"f2\":[[1,{\"id\":\"id1\"},3],4]}}", "first.f2.id", "{\"first\":{\"f2\":[[1,{},3],4]}}" }, // double nested array remove leaf diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java index c6cee591..17daf438 100755 --- a/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java +++ b/json-smart/src/test/java/net/minidev/json/test/actions/PathsRetainerTest.java @@ -4,6 +4,7 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.JSONValue; +import net.minidev.json.actions.path.DotDelimiter; import net.minidev.json.parser.ParseException; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,9 +79,9 @@ public static Collection params() // key with dot ambiguity {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0", "{\"k0\":{}}" },//27 {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k1", "{}" }, - {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0.k1", "{\"k0\":{\"k1\":null}}" }, + {"{\"k0.k1\":\"withDot\",\"k0\":{\"k1\":null}}", "k0.k1", "{\"k0\":{\"k1\":null}}" },//29 {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1", "{\"k0\":{\"k1\":{}}}" }, - {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1.k2", "{\"k0\":{\"k1\":{\"k2\":null}}}" }, + {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k0.k1.k2", "{\"k0\":{\"k1\":{\"k2\":null}}}" },//31 {"{\"k0\":{\"k1.k2\":\"dot\",\"k1\":{\"k2\":null}}}", "k1.k2", "{}" }, {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k0", "{\"k0\":{}}}" }, {"{\"k0\":{\"k1.k2\":\"dot\"},\"k1\":{\"k2\":\"v2\"}}}","k1.k2", "{\"k1\":{\"k2\":\"v2\"}}}" }, @@ -116,8 +117,8 @@ public void test() throws ParseException { JSONObject objectToReduce = jsonToReduce != null ? (JSONObject) JSONValue.parseWithException(jsonToReduce) :null; JSONObject expectedReducedObj = expectedReducedJson != null ? (JSONObject) JSONValue.parseWithException(expectedReducedJson):null; - PathsRetainer reducer = switchKeyToRemove(); - JSONObject reducedObj = reducer.retain(objectToReduce); + PathsRetainer retainer = switchKeyToRemove().with(new DotDelimiter().withAcceptDelimiterInNodeName(false)); + JSONObject reducedObj = retainer.retain(objectToReduce); assertEquals(expectedReducedObj, reducedObj); } From 4de36a492589797d85815651416348d762fc4390 Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Tue, 7 Jun 2016 16:23:31 +0300 Subject: [PATCH 06/10] support any delimiter in traverser --- json-smart/pom.xml | 8 ++++++- .../net/minidev/json/actions/PathLocator.java | 2 +- .../minidev/json/actions/PathsRetainer.java | 2 +- .../json/actions/path/PathDelimiter.java | 2 +- .../json/actions/path/SlashDelimiter.java | 24 +++++++++++++++++++ .../json/actions/traverse/JSONTraverser.java | 9 ++++++- .../json/actions/traverse/TreeTraverser.java | 10 +++++--- 7 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 json-smart/src/main/java/net/minidev/json/actions/path/SlashDelimiter.java diff --git a/json-smart/pom.xml b/json-smart/pom.xml index 6f8ef200..5e35f2f1 100644 --- a/json-smart/pom.xml +++ b/json-smart/pom.xml @@ -3,7 +3,7 @@ 4.0.0 net.minidev json-smart - 2.2.1 + 2.2.1-b-SNAPSHOT JSON Small and Fast Parser JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language. @@ -23,6 +23,12 @@ + + erav + Eitan Raviv + adoneitan@gmail.com + GMT+2 + diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java index e66eea55..773807a7 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java @@ -67,7 +67,7 @@ public PathLocator with(PathDelimiter pathDelimiter) { public List locate(JSONObject object) { JSONTraverseAction action = new LocatePathsJsonAction(this.pathsToFind, pathDelimiter); - JSONTraverser traversal = new JSONTraverser(action); + JSONTraverser traversal = new JSONTraverser(action).with(pathDelimiter); traversal.traverse(object); return (List) action.result(); } diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java index 7e256814..f0d32783 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java @@ -86,7 +86,7 @@ public JSONObject retain(JSONObject object) //now reduce the object using only existing paths JSONTraverseAction retainer = new RetainPathsJsonAction(realPathsToRetain, pathDelimiter); - JSONTraverser t2 = new JSONTraverser(retainer); + JSONTraverser t2 = new JSONTraverser(retainer).with(pathDelimiter); t2.traverse(object); return (JSONObject) retainer.result(); } diff --git a/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java b/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java index 494406cb..2a9fae7d 100644 --- a/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java +++ b/json-smart/src/main/java/net/minidev/json/actions/path/PathDelimiter.java @@ -4,7 +4,7 @@ * Encapsulates the delimiter of the path parts when given in n-gram format. * * @author adoneitan@gmail.com - * @since 31 May2016 + * @since 31 May 2016 */ public abstract class PathDelimiter { diff --git a/json-smart/src/main/java/net/minidev/json/actions/path/SlashDelimiter.java b/json-smart/src/main/java/net/minidev/json/actions/path/SlashDelimiter.java new file mode 100644 index 00000000..1c005f2f --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/path/SlashDelimiter.java @@ -0,0 +1,24 @@ +package net.minidev.json.actions.path; + +/** + * Encapsulates the delimiter '.' of the path parts when the path is specified in n-gram format. + * For example: root.node1.node11.leaf + * + * @author adoneitan@gmail.com + * @since 31 May 2016 + */ +public class SlashDelimiter extends PathDelimiter +{ + protected static final char DELIM_CHAR = '/'; + + public SlashDelimiter() + { + super(DELIM_CHAR); + super.withAcceptDelimiterInNodeName(false); + } + + public String regex() + { + return "\\/"; + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java index 2a6d62a0..62dd307e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java @@ -2,6 +2,8 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.PathDelimiter; /** * Traverses every node of a {@link JSONObject} @@ -23,6 +25,11 @@ public class JSONTraverser extends TreeTraverser public JSONTraverser(JSONTraverseAction action) { - super(action); + super(action, new DotDelimiter()); + } + + public JSONTraverser with(PathDelimiter delim) { + super.delim = delim; + return this; } } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java index 4fbf84e1..6292d489 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java @@ -1,5 +1,7 @@ package net.minidev.json.actions.traverse; +import net.minidev.json.actions.path.PathDelimiter; + import java.util.Iterator; import java.util.List; import java.util.Map; @@ -18,11 +20,13 @@ */ public class TreeTraverser, L extends List> { - private TreeTraverseAction action; + protected PathDelimiter delim; + protected TreeTraverseAction action; - public TreeTraverser(TreeTraverseAction action) + public TreeTraverser(TreeTraverseAction action, PathDelimiter delim) { this.action = action; + this.delim = delim; } public void traverse(M map) @@ -93,7 +97,7 @@ else if (instanceOfList(listItem) && action.recurInto(fullPathToList, listIndex, } private String buildPath(String fullPathToObject, String entryKey) { - return "".equals(fullPathToObject) ? entryKey : fullPathToObject + "." + entryKey; + return "".equals(fullPathToObject) ? entryKey : fullPathToObject + delim.str() + entryKey; } private boolean instanceOfList(Object obj) { From efb8714ede7a7389144ed0ed85336201d76d6dfb Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Wed, 8 Jun 2016 15:46:00 +0300 Subject: [PATCH 07/10] simplif api --- .../net/minidev/json/actions/PathLocator.java | 4 ++-- .../net/minidev/json/actions/PathRemover.java | 2 +- .../net/minidev/json/actions/PathsRetainer.java | 2 +- .../json/actions/navigate/CopyPathsAction.java | 6 +++--- .../json/actions/traverse/JSONTraverser.java | 2 -- .../json/actions/traverse/KeysPrintAction.java | 15 +++++++-------- .../actions/traverse/LocatePathsJSONAction.java | 13 +++++++------ .../traverse/RemoveElementsJSONAction.java | 7 ++++--- .../actions/traverse/RemovePathsJsonAction.java | 13 +++++++------ .../actions/traverse/RetainPathsJsonAction.java | 9 +++++---- .../json/actions/traverse/TreeTraverseAction.java | 7 +++---- .../json/actions/traverse/TreeTraverser.java | 8 ++++---- 12 files changed, 44 insertions(+), 44 deletions(-) diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java index 773807a7..e75d5736 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathLocator.java @@ -30,8 +30,8 @@ */ public class PathLocator { - private List pathsToFind; - private PathDelimiter pathDelimiter = new DotDelimiter().withAcceptDelimiterInNodeName(false); + protected List pathsToFind; + protected PathDelimiter pathDelimiter = new DotDelimiter().withAcceptDelimiterInNodeName(false); public PathLocator(JSONArray pathsToFind) { diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java index f2532da4..c8c4db37 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathRemover.java @@ -36,7 +36,7 @@ */ public class PathRemover { - private List pathsToRemove; + protected List pathsToRemove; public PathRemover(JSONArray pathsToRemove) { diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java index f0d32783..f4fa2496 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathsRetainer.java @@ -36,7 +36,7 @@ public class PathsRetainer { protected List pathsToRetain; - private PathDelimiter pathDelimiter = new DotDelimiter().withAcceptDelimiterInNodeName(false); + protected PathDelimiter pathDelimiter = new DotDelimiter().withAcceptDelimiterInNodeName(false); public PathsRetainer(JSONArray pathsToRetain) { diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java index ba62fafd..20cb4a02 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java @@ -29,9 +29,9 @@ */ public class CopyPathsAction implements NavigateAction { - private JSONObject destTree; - private JSONObject destBranch; - private Stack destNodeStack; + protected JSONObject destTree; + protected JSONObject destBranch; + protected Stack destNodeStack; @Override public boolean handleNavigationStart(JSONObject source, Collection pathsToCopy) diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java index 62dd307e..eb55c092 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/JSONTraverser.java @@ -21,8 +21,6 @@ */ public class JSONTraverser extends TreeTraverser { - private JSONTraverseAction action; - public JSONTraverser(JSONTraverseAction action) { super(action, new DotDelimiter()); diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java index b3e8e993..55b4c47a 100644 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/KeysPrintAction.java @@ -1,10 +1,9 @@ package net.minidev.json.actions.traverse; +import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; -import net.minidev.json.JSONValue; -import net.minidev.json.parser.ParseException; -import java.util.Map; +import java.util.Map.Entry; /** * @author adoneitan@gmail.com @@ -19,24 +18,24 @@ public boolean start(JSONObject object) } @Override - public boolean traverseEntry(String fullPathToEntry, Map.Entry entry) + public boolean traverseEntry(String fullPathToEntry, Entry entry) { System.out.println(entry.getKey()); return true; } @Override - public boolean recurInto(String pathToEntry, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONObject entryValue) { return true; } @Override - public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONArray entryValue) { return true; } @Override - public void handleLeaf(String pathToEntry, Object entryValue) + public void handleLeaf(String pathToEntry, Entry entry) { } @@ -48,7 +47,7 @@ public void handleLeaf(String fullPathToContainingList, int listIndex, Object li } @Override - public boolean removeEntry(String fullPathToEntry, Map.Entry entry) + public boolean removeEntry(String fullPathToEntry, Entry entry) { return false; } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java index c26400d2..f798c03e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/LocatePathsJSONAction.java @@ -1,11 +1,12 @@ package net.minidev.json.actions.traverse; +import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.actions.path.PathDelimiter; import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; /** * Searches for paths in a {@link JSONObject} and returns those found @@ -45,7 +46,7 @@ public boolean start(JSONObject object) } @Override - public boolean traverseEntry(String fullPathToEntry, Map.Entry entry) + public boolean traverseEntry(String fullPathToEntry, Entry entry) { if (!delim.accept(entry.getKey())) { return false; @@ -55,17 +56,17 @@ public boolean traverseEntry(String fullPathToEntry, Map.Entry e } @Override - public boolean recurInto(String pathToEntry, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONObject entryValue) { return true; } @Override - public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONArray entryValue) { return true; } @Override - public void handleLeaf(String pathToEntry, Object entryValue) { + public void handleLeaf(String pathToEntry, Entry entry) { } @@ -75,7 +76,7 @@ public void handleLeaf(String fullPathToContainingList, int listIndex, Object li } @Override - public boolean removeEntry(String fullPathToEntry, Map.Entry entry) + public boolean removeEntry(String fullPathToEntry, Entry entry) { return false; } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java index 20ad762a..f8a4d7d4 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemoveElementsJSONAction.java @@ -1,5 +1,6 @@ package net.minidev.json.actions.traverse; +import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import java.util.Map; @@ -56,17 +57,17 @@ public boolean traverseEntry(String fullPathToEntry, Entry entry } @Override - public boolean recurInto(String pathToEntry, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONObject entryValue) { return true; } @Override - public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONArray entryValue) { return true; } @Override - public void handleLeaf(String pathToEntry, Object entryValue) + public void handleLeaf(String pathToEntry, Entry entry) { } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java index 859bdd5e..fac23fd5 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RemovePathsJsonAction.java @@ -1,9 +1,10 @@ package net.minidev.json.actions.traverse; +import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; /** * Removes branches from a {@link JSONObject}. @@ -36,30 +37,30 @@ public boolean start(JSONObject object) } @Override - public boolean removeEntry(String fullPathToEntry, Map.Entry entry) + public boolean removeEntry(String fullPathToEntry, Entry entry) { return pathsToRemove.contains(fullPathToEntry); } @Override - public boolean traverseEntry(String fullPathToEntry, Map.Entry entry) + public boolean traverseEntry(String fullPathToEntry, Entry entry) { //must traverse the whole object return true; } @Override - public boolean recurInto(String pathToEntry, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONObject entryValue) { return true; } @Override - public boolean recurInto(String pathToEntry, int listIndex, Object entryValue) { + public boolean recurInto(String pathToEntry, JSONArray entryValue) { return true; } @Override - public void handleLeaf(String pathToEntry, Object entryValue) + public void handleLeaf(String pathToEntry, Entry entry) { } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java index 7671a891..1948da93 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/RetainPathsJsonAction.java @@ -1,5 +1,6 @@ package net.minidev.json.actions.traverse; +import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.actions.path.PathDelimiter; @@ -25,7 +26,7 @@ */ public class RetainPathsJsonAction implements JSONTraverseAction { - private final PathDelimiter delim; + protected final PathDelimiter delim; protected JSONObject result; protected List pathsToRetain; @@ -58,17 +59,17 @@ public boolean traverseEntry(String fullPathToEntry, Entry entry } @Override - public boolean recurInto(String fullPathToSubtree, Object entryValue) { + public boolean recurInto(String fullPathToSubtree, JSONObject entryValue) { return true; } @Override - public boolean recurInto(String fullPathToArrayItem, int arrIndex, Object entryValue) { + public boolean recurInto(String fullPathToArrayItem, JSONArray entryValue) { return true; } @Override - public void handleLeaf(String pathToEntry, Object entryValue) { + public void handleLeaf(String pathToEntry, Entry entry) { } @Override diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java index 7ac97d71..61922c8d 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverseAction.java @@ -39,19 +39,18 @@ public interface TreeTraverseAction, L extends Lis * called when a non-leaf entry is encountered inside an {@Link M} object * @return true if the non-leaf entry should be recursively traversed */ - boolean recurInto(String fullPathToSubtree, Object entryValue); + boolean recurInto(String fullPathToSubtree, M entryValue); /** * called when a non-leaf item is encountered inside an {@Link L} object * @return true if the non-leaf item should be recursively traversed */ - boolean recurInto(String fullPathToContainingList, int listIndex, Object entryValue); + boolean recurInto(String fullPathToContainingList, L entryValue); /** * called for each leaf of an {@link M} map is encountered - * @param entryValue - the item */ - void handleLeaf(String fullPathToEntry, Object entryValue); + void handleLeaf(String fullPathToEntry, Entry entry); /** * called for each leaf of an {@link L} list is encountered diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java index 6292d489..89b78fe5 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java @@ -65,7 +65,7 @@ else if (instanceOfList(entry.getValue())) { depthFirst(fullPathToEntry, (L) entry.getValue()); } - else if (!instanceOfMap(entry) && !instanceOfList(entry)) + else { action.handleLeaf(fullPathToEntry, entry); } @@ -74,17 +74,17 @@ else if (!instanceOfMap(entry) && !instanceOfList(entry)) private void depthFirst(String fullPathToList, L list) { - if (!action.recurInto(fullPathToList, list)) { + if (!action.recurInto(fullPathToList, (L) list)) { return; } int listIndex = 0; for (Object listItem : list.toArray()) { - if (instanceOfMap(listItem) && action.recurInto(fullPathToList, listIndex, listItem)) + if (instanceOfMap(listItem)) { depthFirst(fullPathToList, (M) listItem); } - else if (instanceOfList(listItem) && action.recurInto(fullPathToList, listIndex, listItem)) + else if (instanceOfList(listItem)) { depthFirst(fullPathToList, (L) listItem); } From 5391effad93d79854fd580bc3f48603cbfb2df0c Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Sun, 19 Jun 2016 20:52:14 +0300 Subject: [PATCH 08/10] create tree navigator --- .../net/minidev/json/actions/JSONBuilder.java | 17 ++ .../actions/navigate/CopyPathsAction.java | 35 ++-- .../actions/navigate/JSONNavigateAction.java | 16 ++ .../navigate/JSONNavigateActionOld.java | 103 ++++++++++ .../json/actions/navigate/JSONNavigator.java | 180 +---------------- .../actions/navigate/JSONNavigatorOld.java | 191 ++++++++++++++++++ .../json/actions/navigate/NavigateAction.java | 62 +++--- .../json/actions/navigate/TreeNavigator.java | 171 ++++++++++++++++ .../json/actions/navigate/package-info.java | 4 +- .../path/{JSONPath.java => TreePath.java} | 34 ++-- .../json/actions/traverse/TreeTraverser.java | 45 ++--- .../{JSONPathTest.java => TreePathTest.java} | 16 +- 12 files changed, 603 insertions(+), 271 deletions(-) create mode 100644 json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java mode change 100755 => 100644 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java create mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java rename json-smart/src/main/java/net/minidev/json/actions/path/{JSONPath.java => TreePath.java} (87%) rename json-smart/src/test/java/net/minidev/json/test/actions/{JSONPathTest.java => TreePathTest.java} (87%) diff --git a/json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java b/json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java new file mode 100644 index 00000000..23e65245 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java @@ -0,0 +1,17 @@ +package net.minidev.json.actions; + +import net.minidev.json.JSONObject; + +/** + * @author adoneitan@gmail.com + * @since 14 June 2016 + */ +public class JSONBuilder +{ + protected JSONObject result; + + public void append(String path, String value) + { + + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java index 20cb4a02..42d0797c 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java @@ -2,7 +2,7 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; -import net.minidev.json.actions.path.JSONPath; +import net.minidev.json.actions.path.TreePath; import java.util.Collection; import java.util.Stack; @@ -27,14 +27,14 @@ * @author adoneitan@gmail.com * */ -public class CopyPathsAction implements NavigateAction +public class CopyPathsAction implements JSONNavigateAction { protected JSONObject destTree; protected JSONObject destBranch; protected Stack destNodeStack; @Override - public boolean handleNavigationStart(JSONObject source, Collection pathsToCopy) + public boolean start(JSONObject source, Collection pathsToCopy) { if (source == null) { @@ -55,15 +55,18 @@ public boolean handleNavigationStart(JSONObject source, Collection paths } @Override - public boolean handleJSONObject(JSONPath jp, JSONObject o) + public boolean recurInto(TreePath jp, JSONObject o) { //reached JSONObject node - instantiate it and recur handleNewNode(jp, new JSONObject()); return true; } - private void handleNewNode(JSONPath jp, Object node) + private void handleNewNode(TreePath jp, Object node) { + if (!jp.hasPrev()) { + return; + } if (destNodeStack.peek() instanceof JSONObject) { ((JSONObject) destNodeStack.peek()).put(jp.curr(), node); } @@ -74,7 +77,7 @@ else if (destNodeStack.peek() instanceof JSONArray) { } @Override - public boolean handleJSONArrray(JSONPath jp, JSONArray o) + public boolean recurInto(TreePath jp, JSONArray o) { //reached JSONArray node - instantiate it and recur handleNewNode(jp, new JSONArray()); @@ -82,32 +85,32 @@ public boolean handleJSONArrray(JSONPath jp, JSONArray o) } @Override - public void handlePrematureNavigatedBranchEnd(JSONPath jp, Object source) { + public void handlePrematureNavigatedBranchEnd(TreePath jp, Object source) { throw new IllegalArgumentException("branch is shorter than path - path not found in source: '" + jp.origin() + "'"); } @Override - public void handleJSONObjectLeaf(JSONPath jp, Object o) { + public void handleLeaf(TreePath jp, Object o) { ((JSONObject) destNodeStack.peek()).put(jp.curr(), o); } @Override - public void handleJSONArrayLeaf(int arrIndex, Object o) { + public void handleLeaf(TreePath jp, int arrIndex, Object o) { ((JSONArray) destNodeStack.peek()).add(o); } @Override - public void handleJSONArrayEnd(JSONPath jp) { + public void recurEnd(TreePath jp, JSONObject jo) { destNodeStack.pop(); } @Override - public void handleObjectEnd(JSONPath jp) { + public void recurEnd(TreePath jp, JSONArray ja) { destNodeStack.pop(); } @Override - public boolean handleNextPath(String path) + public boolean pathStart(String path) { destBranch = new JSONObject(); destNodeStack = new Stack(); @@ -116,22 +119,22 @@ public boolean handleNextPath(String path) } @Override - public void handlePathEnd(String path) { + public void pathEnd(String path) { destTree.merge(destBranch); } @Override - public boolean failPathSilently(String path, Exception e) { + public boolean failSilently(String path, Exception e) { return false; } @Override - public boolean failPathFast(String path, Exception e) { + public boolean failFast(String path, Exception e) { return false; } @Override - public void handleNavigationEnd() { + public void end() { } diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java new file mode 100755 index 00000000..eff79f8d --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java @@ -0,0 +1,16 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; + +/** + * An interface for a processing action on the nodes of a {@link JSONObject} while navigating its branches. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public interface JSONNavigateAction extends NavigateAction +{ + +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java new file mode 100755 index 00000000..c46aa5fe --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java @@ -0,0 +1,103 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.TreePath; + +import java.util.Collection; + +/** + * An interface for a processing action on the nodes of a {@link JSONObject} while navigating its branches. + *

+ * See package-info for more details + * + * @author adoneitan@gmail.com + */ +public interface JSONNavigateActionOld +{ + /** + * called before any navigation of the {@link JSONObject} starts + * @return true if navigation should start at all + */ + boolean handleNavigationStart(JSONObject objectToNavigate, Collection pathsToNavigate); + + /** + * called before navigation of a new path starts + * @return true if the specified path should be navigated + */ + boolean handleNextPath(String path); + + /** + * reached end of branch in source before end of specified json path - + * the specified path does not exist in the source. + */ + void handlePrematureNavigatedBranchEnd(TreePath jp, Object source); + + /** + * called after the navigation of a path ends + */ + void handlePathEnd(String path); + + /** + * called if navigation of a path throws an exception + * @return true if the failure on this path should not abort the rest of the navigation + */ + boolean failPathSilently(String path, Exception e); + + /** + * called if navigation of a path throws an exception + * @return true if the failure on this path should abort the rest of the navigation + */ + boolean failPathFast(String path, Exception e); + + /** + * called when an object node is encountered on the path + * @return true if the navigator should navigate into the object + */ + boolean handleJSONObject(TreePath jp, JSONObject sourceNode); + + /** + * called when an array node is encountered on the path + * @return true if the navigator should navigate into the array + */ + boolean handleJSONArrray(TreePath jp, JSONArray sourceNode); + + /** + * called when a leaf node is reached in a JSONObject. + * a leaf in a JSONObject is a key-value pair where the value is not a container itself + * (it is not a JSONObject nor a JSONArray) + * @param jp - the JsonPath pointing to the leaf + */ + void handleJSONObjectLeaf(TreePath jp, Object value); + + /** + * called when a leaf in a JSONArray is reached. + * a leaf in a JSONArray is a non-container item + * (it is not a JSONObject nor a JSONArray) + * @param arrIndex - the index of the item in the JSONArray + * @param arrItem - the item + */ + void handleJSONArrayLeaf(int arrIndex, Object arrItem); + + /** + * called after all the items of an array have been visited + * @param jp - the JsonPath pointing to the array + */ + void handleJSONArrayEnd(TreePath jp); + + /** + * called after all the entries of a JSONObject have been visited + * @param jp - the JsonPath pointing to the object + */ + void handleObjectEnd(TreePath jp); + + /** + * called after all navigation ends, and just before the navigation method exits + */ + void handleNavigationEnd(); + + /** + * holds the result of the navigation, as assigned by the action implementing this interface + */ + Object result(); +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java old mode 100755 new mode 100644 index 06da109f..4030be92 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java @@ -2,190 +2,22 @@ import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; -import net.minidev.json.actions.path.DotDelimiter; -import net.minidev.json.actions.path.JSONPath; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; /** - * Navigates only the branches of a {@link JSONObject} corresponding to the paths specified. - *

- * For each specified path to navigate, the {@link JSONNavigator} only traverses the matching - * branch. - *

- * The navigator accepts an action and provides callback hooks for it to act on the traversed - * nodes at each significant step. See {@link NavigateAction}. - *

- * See package-info for more details - *

- * Example: - *

- * To navigate the branch k1.k2 of the object {"k1":{"k2":"v1"}, "k3":{"k4":"v2"}} instantiate - * the navigator like so: new JSONNavigator("k1.k2") - * - * @author adoneitan@gmail.com - * + * Created by eitanraviv on 6/15/16. */ -public class JSONNavigator +public class JSONNavigator extends TreeNavigator { - protected List pathsToNavigate; - protected NavigateAction action; - public JSONNavigator(NavigateAction action, JSONArray pathsToNavigate) + public JSONNavigator(JSONNavigateAction action, List pathsToNavigate) { - if (action == null) { - throw new IllegalArgumentException("NavigateAction cannot be null"); - } - this.action = action; - if (pathsToNavigate == null || pathsToNavigate.isEmpty()) { - this.pathsToNavigate = Collections.emptyList(); - } - else - { - this.pathsToNavigate = new LinkedList(); - for (Object s : pathsToNavigate) { - this.pathsToNavigate.add((String) s); - } - } + super(action, pathsToNavigate); } - public JSONNavigator(NavigateAction action, List pathsToNavigate) + public JSONNavigator(JSONNavigateAction action, String... pathsToNavigate) { - if (action == null) { - throw new IllegalArgumentException("NavigateAction cannot be null"); - } - this.action = action; - this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.size() == 0 ? - Collections.emptyList() : pathsToNavigate; - } - - public JSONNavigator(NavigateAction action, String... pathsToNavigate) - { - if (action == null) { - throw new IllegalArgumentException("NavigateAction cannot be null"); - } - this.action = action; - this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.length == 0 ? - Collections.emptyList() : new LinkedList(Arrays.asList(pathsToNavigate)); - } - - public void nav(JSONObject object) throws Exception - { - if (action.handleNavigationStart(object, pathsToNavigate)) - { - for (String path: pathsToNavigate) - { - try - { - if (path != null && !path.equals("") && action.handleNextPath(path)) - { - JSONPath jp = new JSONPath(path, new DotDelimiter().withAcceptDelimiterInNodeName(true)); - nav(object, jp); - action.handlePathEnd(path); - } - } - catch (Exception e) - { - if (action.failPathSilently(path ,e)) { - break; - } - else if (action.failPathFast(path, e)) { - throw e; - } - } - } - } - action.handleNavigationEnd(); - } - - private void nav(JSONObject source, JSONPath jp) - { - if (jp.hasNext()) - { - if (source == null) - { - //source is null - navigation impossible - return; - } - String next = jp.next(); - if (!source.containsKey(next)) - { - // reached end of branch in source before end of specified json path - - // the specified path is illegal because it does not exist in the source. - action.handlePrematureNavigatedBranchEnd(jp, source); - } - else if (source.get(next) instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) source.get(next))) - { - //reached JSONObject node - handle it and recur into it - nav((JSONObject) source.get(next), jp); - } - else if (source.get(next) instanceof JSONArray && action.handleJSONArrray(jp, (JSONArray) source.get(next))) - { - //reached JSONArray node - handle it and recur into it - nav((JSONArray) source.get(next), jp); - } - else if (jp.hasNext()) - { - // reached leaf node (not a container) in source but specified path expects children - - // the specified path is illegal because it does not exist in the source. - action.handlePrematureNavigatedBranchEnd(jp, source.get(next)); - } - else if (!jp.hasNext()) - { - //reached leaf in source and specified path is also at leaf -> handle it - action.handleJSONObjectLeaf(jp, source.get(next)); - } - else - { - throw new IllegalStateException("fatal: unreachable code reached at '" + jp.origin() + "'"); - } - } - action.handleObjectEnd(jp); - } - - private void nav(JSONArray source, JSONPath jp) - { - if (source == null) - { - //array is null - navigation impossible - return; - } - int arrIndex = 0; - for (Object arrItem : source.toArray()) - { - if (arrItem instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) arrItem)) - { - // clone the path so that for each JSONObject in the array, - // the iterator continues from the same position in the path - JSONPath jpClone = getClone(jp); - nav((JSONObject) arrItem, jpClone); - } - else if (arrItem instanceof JSONArray) - { - throw new IllegalArgumentException("illegal json - found array nested inside array at: '" + jp.origin() + "'"); - } - else if (!jp.hasNext()) - { - //reached leaf - handle it - action.handleJSONArrayLeaf(arrIndex, arrItem); - } - arrIndex++; - } - action.handleJSONArrayEnd(jp); - - } - - private JSONPath getClone(JSONPath jp) - { - try - { - return jp.clone(); - } - catch (CloneNotSupportedException e) { - throw new RuntimeException("failed to clone json path", e); - } + super(action, pathsToNavigate); } } diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java new file mode 100755 index 00000000..2b4467f9 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java @@ -0,0 +1,191 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.TreePath; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Navigates only the branches of a {@link JSONObject} corresponding to the paths specified. + *

+ * For each specified path to navigate, the {@link JSONNavigatorOld} only traverses the matching + * branch. + *

+ * The navigator accepts an action and provides callback hooks for it to act on the traversed + * nodes at each significant step. See {@link JSONNavigateActionOld}. + *

+ * See package-info for more details + *

+ * Example: + *

+ * To navigate the branch k1.k2 of the object {"k1":{"k2":"v1"}, "k3":{"k4":"v2"}} instantiate + * the navigator like so: new JSONNavigator("k1.k2") + * + * @author adoneitan@gmail.com + * + */ +public class JSONNavigatorOld +{ + protected List pathsToNavigate; + protected JSONNavigateActionOld action; + + public JSONNavigatorOld(JSONNavigateActionOld action, JSONArray pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("JSONNavigateActionOld cannot be null"); + } + this.action = action; + if (pathsToNavigate == null || pathsToNavigate.isEmpty()) { + this.pathsToNavigate = Collections.emptyList(); + } + else + { + this.pathsToNavigate = new LinkedList(); + for (Object s : pathsToNavigate) { + this.pathsToNavigate.add((String) s); + } + } + } + + public JSONNavigatorOld(JSONNavigateActionOld action, List pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("JSONNavigateActionOld cannot be null"); + } + this.action = action; + this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.size() == 0 ? + Collections.emptyList() : pathsToNavigate; + } + + public JSONNavigatorOld(JSONNavigateActionOld action, String... pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("JSONNavigateActionOld cannot be null"); + } + this.action = action; + this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.length == 0 ? + Collections.emptyList() : new LinkedList(Arrays.asList(pathsToNavigate)); + } + + public void nav(JSONObject object) throws Exception + { + if (action.handleNavigationStart(object, pathsToNavigate)) + { + for (String path: pathsToNavigate) + { + try + { + if (path != null && !path.equals("") && action.handleNextPath(path)) + { + TreePath jp = new TreePath(path, new DotDelimiter().withAcceptDelimiterInNodeName(true)); + nav(object, jp); + action.handlePathEnd(path); + } + } + catch (Exception e) + { + if (action.failPathSilently(path ,e)) { + break; + } + else if (action.failPathFast(path, e)) { + throw e; + } + } + } + } + action.handleNavigationEnd(); + } + + private void nav(JSONObject source, TreePath jp) + { + if (jp.hasNext()) + { + if (source == null) + { + //source is null - navigation impossible + return; + } + String next = jp.next(); + if (!source.containsKey(next)) + { + // reached end of branch in source before end of specified json path - + // the specified path is illegal because it does not exist in the source. + action.handlePrematureNavigatedBranchEnd(jp, source); + } + else if (source.get(next) instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) source.get(next))) + { + //reached JSONObject node - handle it and recur into it + nav((JSONObject) source.get(next), jp); + } + else if (source.get(next) instanceof JSONArray && action.handleJSONArrray(jp, (JSONArray) source.get(next))) + { + //reached JSONArray node - handle it and recur into it + nav((JSONArray) source.get(next), jp); + } + else if (jp.hasNext()) + { + // reached leaf node (not a container) in source but specified path expects children - + // the specified path is illegal because it does not exist in the source. + action.handlePrematureNavigatedBranchEnd(jp, source.get(next)); + } + else if (!jp.hasNext()) + { + //reached leaf in source and specified path is also at leaf -> handle it + action.handleJSONObjectLeaf(jp, source.get(next)); + } + else + { + throw new IllegalStateException("fatal: unreachable code reached at '" + jp.origin() + "'"); + } + } + action.handleObjectEnd(jp); + } + + private void nav(JSONArray source, TreePath jp) + { + if (source == null) + { + //array is null - navigation impossible + return; + } + int arrIndex = 0; + for (Object arrItem : source.toArray()) + { + if (arrItem instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) arrItem)) + { + // clone the path so that for each JSONObject in the array, + // the iterator continues from the same position in the path + TreePath jpClone = getClone(jp); + nav((JSONObject) arrItem, jpClone); + } + else if (arrItem instanceof JSONArray) + { + throw new IllegalArgumentException("illegal json - found array nested inside array at: '" + jp.origin() + "'"); + } + else if (!jp.hasNext()) + { + //reached leaf - handle it + action.handleJSONArrayLeaf(arrIndex, arrItem); + } + arrIndex++; + } + action.handleJSONArrayEnd(jp); + + } + + private TreePath getClone(TreePath jp) + { + try + { + return jp.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("failed to clone json path", e); + } + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java index eddf5438..0d2059b4 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java @@ -1,100 +1,100 @@ package net.minidev.json.actions.navigate; -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.actions.path.JSONPath; +import net.minidev.json.actions.path.TreePath; import java.util.Collection; +import java.util.List; +import java.util.Map; /** - * An interface for a processing action on the nodes of a {@link JSONObject} while navigating its branches. + * An interface for a processing action on the nodes of a {@link M} while navigating its branches. *

* See package-info for more details * * @author adoneitan@gmail.com */ -public interface NavigateAction +public interface NavigateAction, L extends List> { /** - * called before any navigation of the {@link JSONObject} starts + * called before any navigation of the {@link M} starts * @return true if navigation should start at all */ - boolean handleNavigationStart(JSONObject objectToNavigate, Collection pathsToNavigate); + boolean start(M objectToNavigate, Collection pathsToNavigate); /** * called before navigation of a new path starts * @return true if the specified path should be navigated */ - boolean handleNextPath(String path); + boolean pathStart(String path); /** * reached end of branch in source before end of specified json path - * the specified path does not exist in the source. */ - void handlePrematureNavigatedBranchEnd(JSONPath jp, Object source); + void handlePrematureNavigatedBranchEnd(TreePath tp, Object source); /** * called after the navigation of a path ends */ - void handlePathEnd(String path); + void pathEnd(String path); /** * called if navigation of a path throws an exception * @return true if the failure on this path should not abort the rest of the navigation */ - boolean failPathSilently(String path, Exception e); + boolean failSilently(String path, Exception e); /** * called if navigation of a path throws an exception * @return true if the failure on this path should abort the rest of the navigation */ - boolean failPathFast(String path, Exception e); + boolean failFast(String path, Exception e); /** * called when an object node is encountered on the path * @return true if the navigator should navigate into the object */ - boolean handleJSONObject(JSONPath jp, JSONObject sourceNode); + boolean recurInto(TreePath tp, M sourceNode); /** * called when an array node is encountered on the path * @return true if the navigator should navigate into the array */ - boolean handleJSONArrray(JSONPath jp, JSONArray sourceNode); + boolean recurInto(TreePath tp, L sourceNode); /** - * called when a leaf node is reached in a JSONObject. - * a leaf in a JSONObject is a key-value pair where the value is not a container itself - * (it is not a JSONObject nor a JSONArray) - * @param jp - the JsonPath pointing to the leaf + * called when a leaf node is reached in a M. + * a leaf in a M is a key-value pair where the value is not a container itself + * (it is not a M nor a L) + * @param tp - the JsonPath pointing to the leaf */ - void handleJSONObjectLeaf(JSONPath jp, Object value); + void handleLeaf(TreePath tp, Object value); /** - * called when a leaf in a JSONArray is reached. - * a leaf in a JSONArray is a non-container item - * (it is not a JSONObject nor a JSONArray) - * @param arrIndex - the index of the item in the JSONArray + * called when a leaf in a L is reached. + * a leaf in a L is a non-container item + * (it is not a M nor a L) + * @param arrIndex - the index of the item in the L * @param arrItem - the item */ - void handleJSONArrayLeaf(int arrIndex, Object arrItem); + void handleLeaf(TreePath tp, int arrIndex, Object arrItem); /** - * called after all the items of an array have been visited - * @param jp - the JsonPath pointing to the array + * called when navigation of an {@link M} type object ends + * @param tp the path pointing to the object */ - void handleJSONArrayEnd(JSONPath jp); + void recurEnd(TreePath tp, M m); /** - * called after all the entries of a JSONObject have been visited - * @param jp - the JsonPath pointing to the object + * called when navigation of an {@link L} type object ends + * @param tp the path pointing to the object */ - void handleObjectEnd(JSONPath jp); + void recurEnd(TreePath tp, L l); /** * called after all navigation ends, and just before the navigation method exits */ - void handleNavigationEnd(); + void end(); /** * holds the result of the navigation, as assigned by the action implementing this interface diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java new file mode 100755 index 00000000..48274fa8 --- /dev/null +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java @@ -0,0 +1,171 @@ +package net.minidev.json.actions.navigate; + +import net.minidev.json.JSONObject; +import net.minidev.json.actions.path.DotDelimiter; +import net.minidev.json.actions.path.TreePath; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * Navigates only the branches of a {@link JSONObject} corresponding to the paths specified. + *

+ * For each specified path to navigate, the {@link TreeNavigator} only traverses the matching + * branch. + *

+ * The navigator accepts an action and provides callback hooks for it to act on the traversed + * nodes at each significant step. See {@link NavigateAction}. + *

+ * See package-info for more details + *

+ * Example: + *

+ * To navigate the branch k1.k2 of the object {"k1":{"k2":"v1"}, "k3":{"k4":"v2"}} instantiate + * the navigator like so: new JSONNavigator("k1.k2") + * + * @author adoneitan@gmail.com + * + */ +public class TreeNavigator, L extends List> +{ + protected List pathsToNavigate; + protected NavigateAction action; + protected String pathPrefix = ""; + + public TreeNavigator(NavigateAction action, List pathsToNavigate) + { + if (action == null) { + throw new IllegalArgumentException("NavigateAction cannot be null"); + } + this.action = action; + this.pathsToNavigate = pathsToNavigate; + } + + public TreeNavigator with(String pathPrefix) { + this.pathPrefix = pathPrefix; + return this; + } + + public TreeNavigator(NavigateAction action, String... pathsToNavigate) + { + this(action, Arrays.asList(pathsToNavigate)); + } + + public void nav(M object) throws Exception + { + if (action.start(object, pathsToNavigate)) + { + for (String path: pathsToNavigate) + { + try + { + if (path != null && !path.equals("") && action.pathStart(path)) + { + TreePath jp = new TreePath(path, new DotDelimiter().withAcceptDelimiterInNodeName(true)); + nav(jp, object); + action.pathEnd(path); + } + } + catch (Exception e) + { + if (action.failSilently(path ,e)) { + break; + } + else if (action.failFast(path, e)) { + throw e; + } + } + } + } + action.end(); + } + + private void nav(TreePath jp, M map) + { + if (map == null || !action.recurInto(jp, map)) + { + //source is null - navigation impossible + return; + } + + if (jp.hasNext()) + { + String key = jp.next(); + if (!map.containsKey(key)) + { + // reached end of branch in source before end of specified json path - + // the specified path is illegal because it does not exist in the source. + action.handlePrematureNavigatedBranchEnd(jp, map); + } + else if (map.get(key) instanceof Map) + { + //reached Map type node - handle it and recur into it + nav(jp, (M) map.get(key)); + } + else if (map.get(key) instanceof List) + { + //reached List type node - handle it and recur into it + nav(jp, (L) map.get(key)); + } + else if (jp.hasNext()) + { + // reached leaf node (not a container) in source but specified path expects children - + // the specified path is illegal because it does not exist in the source. + action.handlePrematureNavigatedBranchEnd(jp, map.get(key)); + } + else if (!jp.hasNext()) + { + //reached leaf in source and specified path is also at leaf -> handle it + action.handleLeaf(jp, map.get(key)); + } + else + { + throw new IllegalStateException("fatal: unreachable code reached at '" + jp.origin() + "'"); + } + } + action.recurEnd(jp, (M) map); + } + + private void nav(TreePath jp, L list) + { + if (list == null || !action.recurInto(jp, (L) list)) + { + //list is null - navigation impossible + return; + } + int arrIndex = 0; + for (Object arrItem : list.toArray()) + { + if (arrItem instanceof Map) + { + // clone the path so that for each object in the array, + // the iterator continues from the same position in the path + TreePath jpClone = getClone(jp); + nav(jpClone, (M) arrItem); + } + else if (arrItem instanceof List) + { + nav(jp, (L) arrItem); + } + else if (!jp.hasNext()) + { + //reached leaf - handle it + action.handleLeaf(jp, arrIndex, arrItem); + } + arrIndex++; + } + action.recurEnd(jp, (L) list); + } + + private TreePath getClone(TreePath jp) + { + try + { + return jp.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("failed to clone path", e); + } + } +} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java index 537cf36e..fef786e1 100644 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java @@ -1,10 +1,10 @@ /** * Navigate user-specified paths in a {@link net.minidev.json.JSONObject} and process them *

- * {@link net.minidev.json.actions.navigate.JSONNavigator} only navigates through branches corresponding + * {@link net.minidev.json.actions.navigate.JSONNavigatorOld} only navigates through branches corresponding * to user-specified paths. For each path, the navigation starts at the root and moves down the branch. *

- * The {@link net.minidev.json.actions.navigate.JSONNavigator} accepts a + * The {@link net.minidev.json.actions.navigate.JSONNavigatorOld} accepts a * {@link net.minidev.json.actions.navigate.NavigateAction} and provides callback hooks at each significant * step which the {@link net.minidev.json.actions.navigate.NavigateAction} may use to process * the nodes. diff --git a/json-smart/src/main/java/net/minidev/json/actions/path/JSONPath.java b/json-smart/src/main/java/net/minidev/json/actions/path/TreePath.java similarity index 87% rename from json-smart/src/main/java/net/minidev/json/actions/path/JSONPath.java rename to json-smart/src/main/java/net/minidev/json/actions/path/TreePath.java index 2ab7d85a..a2d4f0bd 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/path/JSONPath.java +++ b/json-smart/src/main/java/net/minidev/json/actions/path/TreePath.java @@ -1,21 +1,21 @@ package net.minidev.json.actions.path; -import net.minidev.json.JSONObject; - import java.util.Arrays; +import java.util.Map; import java.util.List; import java.util.ListIterator; /** - * {@link JSONPath} represents an n-gram formatted path - * corresponding to a branch in a {@link JSONObject} + * {@link TreePath} represents an n-gram formatted path + * corresponding to a branch in a tree of {@link Map}s + * and {@link List}s *

* See package-info for more details * * @author adoneitan@gmail.com */ -public class JSONPath +public class TreePath { protected enum Step {NONE, NEXT, PREV} @@ -28,7 +28,7 @@ protected enum Step {NONE, NEXT, PREV} protected StringBuilder remainder; protected PathDelimiter delim; - public JSONPath(String path, PathDelimiter delim) + public TreePath(String path, PathDelimiter delim) { this.delim = delim; checkPath(path); @@ -197,9 +197,9 @@ private void checkPath(String path) } @Override - public JSONPath clone() throws CloneNotSupportedException + public TreePath clone() throws CloneNotSupportedException { - JSONPath cloned = new JSONPath(this.path, this.delim); + TreePath cloned = new TreePath(this.path, this.delim); while (cloned.nextIndex() != this.nextIndex()) { cloned.next(); } @@ -218,16 +218,16 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - JSONPath jsonPath = (JSONPath) o; + TreePath treePath = (TreePath) o; - return path().equals(jsonPath.path()) && - hasNext() == jsonPath.hasNext() && - hasPrev() == jsonPath.hasPrev() && - curr().equals(jsonPath.curr()) && - origin().equals(jsonPath.origin()) && - remainder().equals(jsonPath.remainder()) && - lastStep == jsonPath.lastStep && - delim.equals(jsonPath.delim); + return path().equals(treePath.path()) && + hasNext() == treePath.hasNext() && + hasPrev() == treePath.hasPrev() && + curr().equals(treePath.curr()) && + origin().equals(treePath.origin()) && + remainder().equals(treePath.remainder()) && + lastStep == treePath.lastStep && + delim.equals(treePath.delim); } diff --git a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java index 89b78fe5..39fbbfcf 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java +++ b/json-smart/src/main/java/net/minidev/json/actions/traverse/TreeTraverser.java @@ -20,8 +20,9 @@ */ public class TreeTraverser, L extends List> { - protected PathDelimiter delim; protected TreeTraverseAction action; + protected PathDelimiter delim; + protected String pathPrefix = ""; public TreeTraverser(TreeTraverseAction action, PathDelimiter delim) { @@ -29,24 +30,29 @@ public TreeTraverser(TreeTraverseAction action, PathDelimiter delim) this.delim = delim; } + public TreeTraverser with(String pathPrefix) { + this.pathPrefix = pathPrefix; + return this; + } + public void traverse(M map) { if (action.start(map)){ - depthFirst("", map); + depthFirst(pathPrefix, map); } action.end(); } - private void depthFirst(String fullPathToMap, M map) + private void depthFirst(String fullPath, M map) { - if (map == null || map.entrySet() == null || !action.recurInto(fullPathToMap, map)) { + if (map == null || map.entrySet() == null || !action.recurInto(fullPath, map)) { return; } Iterator> it = map.entrySet().iterator(); while (it.hasNext()) { Entry entry = it.next(); - String fullPathToEntry = buildPath(fullPathToMap, entry.getKey()); + String fullPathToEntry = buildPath(fullPath, entry.getKey()); if (!action.traverseEntry(fullPathToEntry, entry)) { continue; @@ -57,11 +63,11 @@ else if (action.removeEntry(fullPathToEntry, entry)) continue; } - if (instanceOfMap(entry.getValue())) + if (entry.getValue() instanceof Map) { depthFirst(fullPathToEntry, (M) entry.getValue()); } - else if (instanceOfList(entry.getValue())) + else if (entry.getValue() instanceof List) { depthFirst(fullPathToEntry, (L) entry.getValue()); } @@ -72,39 +78,32 @@ else if (instanceOfList(entry.getValue())) } } - private void depthFirst(String fullPathToList, L list) + private void depthFirst(String fullPath, L list) { - if (!action.recurInto(fullPathToList, (L) list)) { + if (!action.recurInto(fullPath, (L) list)) { return; } int listIndex = 0; for (Object listItem : list.toArray()) { - if (instanceOfMap(listItem)) + if (listItem instanceof Map) { - depthFirst(fullPathToList, (M) listItem); + depthFirst(fullPath, (M) listItem); } - else if (instanceOfList(listItem)) + else if (listItem instanceof List) { - depthFirst(fullPathToList, (L) listItem); + depthFirst(fullPath, (L) listItem); } else { - action.handleLeaf(fullPathToList, listIndex, listItem); + action.handleLeaf(fullPath, listIndex, listItem); } listIndex++; } } - private String buildPath(String fullPathToObject, String entryKey) { - return "".equals(fullPathToObject) ? entryKey : fullPathToObject + delim.str() + entryKey; + private String buildPath(String fullPath, String entryKey) { + return pathPrefix.equals(fullPath) ? pathPrefix + entryKey : fullPath + delim.str() + entryKey; } - private boolean instanceOfList(Object obj) { - return obj instanceof List; - } - - private boolean instanceOfMap(Object obj) { - return obj instanceof Map; - } } diff --git a/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java b/json-smart/src/test/java/net/minidev/json/test/actions/TreePathTest.java similarity index 87% rename from json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java rename to json-smart/src/test/java/net/minidev/json/test/actions/TreePathTest.java index 4fae7f60..876bfb3f 100755 --- a/json-smart/src/test/java/net/minidev/json/test/actions/JSONPathTest.java +++ b/json-smart/src/test/java/net/minidev/json/test/actions/TreePathTest.java @@ -1,7 +1,7 @@ package net.minidev.json.test.actions; import net.minidev.json.actions.path.DotDelimiter; -import net.minidev.json.actions.path.JSONPath; +import net.minidev.json.actions.path.TreePath; import net.minidev.json.actions.path.PathDelimiter; import org.junit.Test; @@ -11,14 +11,14 @@ /** * @author adoneitan@gmail.com */ -public class JSONPathTest +public class TreePathTest { private static final PathDelimiter delim = new DotDelimiter().withAcceptDelimiterInNodeName(true); @Test public void testIterator() { - JSONPath jp = new JSONPath("a.b.c", delim); + TreePath jp = new TreePath("a.b.c", delim); assertTrue(jp.nextIndex() == 0); assertTrue(jp.prevIndex() == -1); assertTrue("".equals(jp.curr())); @@ -74,23 +74,23 @@ public void testIterator() @Test public void testSubPath() { - JSONPath jp = new JSONPath("a.b.c", delim); + TreePath jp = new TreePath("a.b.c", delim); assertTrue(jp.subPath(1,2).equals("b.c")); } @Test public void testClone() throws CloneNotSupportedException { - JSONPath jp1 = new JSONPath("a.b.c", delim); - JSONPath jp2 = jp1.clone(); + TreePath jp1 = new TreePath("a.b.c", delim); + TreePath jp2 = jp1.clone(); assertTrue(jp1.equals(jp2)); jp1.next(); - JSONPath jp3 = jp1.clone(); + TreePath jp3 = jp1.clone(); assertTrue(jp1.equals(jp3)); jp1.prev(); - JSONPath jp4 = jp1.clone(); + TreePath jp4 = jp1.clone(); assertTrue(jp1.equals(jp4)); } From 1649130966ab907557eeea7c24fff72894308ea3 Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Sun, 19 Jun 2016 21:10:01 +0300 Subject: [PATCH 09/10] house cleaning --- .../minidev/json/actions/PathReplicator.java | 1 + .../actions/navigate/CopyPathsAction.java | 1 + .../actions/navigate/JSONNavigateAction.java | 1 + .../navigate/JSONNavigateActionOld.java | 103 ---------- .../json/actions/navigate/JSONNavigator.java | 3 +- .../actions/navigate/JSONNavigatorOld.java | 191 ------------------ .../json/actions/navigate/NavigateAction.java | 13 +- .../json/actions/navigate/TreeNavigator.java | 1 + .../json/actions/navigate/package-info.java | 6 +- 9 files changed, 16 insertions(+), 304 deletions(-) delete mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java delete mode 100755 json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java diff --git a/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java b/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java index b708374f..65173a35 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/PathReplicator.java @@ -34,6 +34,7 @@ * see unit tests for more examples * * @author adoneitan@gmail.com + * @since 15 March 2016. */ public class PathReplicator { diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java index 42d0797c..252c5659 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java @@ -25,6 +25,7 @@ * See unit tests for more examples * * @author adoneitan@gmail.com + * @since 15 March 2016. * */ public class CopyPathsAction implements JSONNavigateAction diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java index eff79f8d..8c1adb8a 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateAction.java @@ -9,6 +9,7 @@ * See package-info for more details * * @author adoneitan@gmail.com + * @since 15 June 2016. */ public interface JSONNavigateAction extends NavigateAction { diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java deleted file mode 100755 index c46aa5fe..00000000 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigateActionOld.java +++ /dev/null @@ -1,103 +0,0 @@ -package net.minidev.json.actions.navigate; - -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.actions.path.TreePath; - -import java.util.Collection; - -/** - * An interface for a processing action on the nodes of a {@link JSONObject} while navigating its branches. - *

- * See package-info for more details - * - * @author adoneitan@gmail.com - */ -public interface JSONNavigateActionOld -{ - /** - * called before any navigation of the {@link JSONObject} starts - * @return true if navigation should start at all - */ - boolean handleNavigationStart(JSONObject objectToNavigate, Collection pathsToNavigate); - - /** - * called before navigation of a new path starts - * @return true if the specified path should be navigated - */ - boolean handleNextPath(String path); - - /** - * reached end of branch in source before end of specified json path - - * the specified path does not exist in the source. - */ - void handlePrematureNavigatedBranchEnd(TreePath jp, Object source); - - /** - * called after the navigation of a path ends - */ - void handlePathEnd(String path); - - /** - * called if navigation of a path throws an exception - * @return true if the failure on this path should not abort the rest of the navigation - */ - boolean failPathSilently(String path, Exception e); - - /** - * called if navigation of a path throws an exception - * @return true if the failure on this path should abort the rest of the navigation - */ - boolean failPathFast(String path, Exception e); - - /** - * called when an object node is encountered on the path - * @return true if the navigator should navigate into the object - */ - boolean handleJSONObject(TreePath jp, JSONObject sourceNode); - - /** - * called when an array node is encountered on the path - * @return true if the navigator should navigate into the array - */ - boolean handleJSONArrray(TreePath jp, JSONArray sourceNode); - - /** - * called when a leaf node is reached in a JSONObject. - * a leaf in a JSONObject is a key-value pair where the value is not a container itself - * (it is not a JSONObject nor a JSONArray) - * @param jp - the JsonPath pointing to the leaf - */ - void handleJSONObjectLeaf(TreePath jp, Object value); - - /** - * called when a leaf in a JSONArray is reached. - * a leaf in a JSONArray is a non-container item - * (it is not a JSONObject nor a JSONArray) - * @param arrIndex - the index of the item in the JSONArray - * @param arrItem - the item - */ - void handleJSONArrayLeaf(int arrIndex, Object arrItem); - - /** - * called after all the items of an array have been visited - * @param jp - the JsonPath pointing to the array - */ - void handleJSONArrayEnd(TreePath jp); - - /** - * called after all the entries of a JSONObject have been visited - * @param jp - the JsonPath pointing to the object - */ - void handleObjectEnd(TreePath jp); - - /** - * called after all navigation ends, and just before the navigation method exits - */ - void handleNavigationEnd(); - - /** - * holds the result of the navigation, as assigned by the action implementing this interface - */ - Object result(); -} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java index 4030be92..3a366c21 100644 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigator.java @@ -6,7 +6,8 @@ import java.util.List; /** - * Created by eitanraviv on 6/15/16. + * @author adoneitan@gmail.com + * @since 15 June 2016. */ public class JSONNavigator extends TreeNavigator { diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java deleted file mode 100755 index 2b4467f9..00000000 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/JSONNavigatorOld.java +++ /dev/null @@ -1,191 +0,0 @@ -package net.minidev.json.actions.navigate; - -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.actions.path.DotDelimiter; -import net.minidev.json.actions.path.TreePath; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * Navigates only the branches of a {@link JSONObject} corresponding to the paths specified. - *

- * For each specified path to navigate, the {@link JSONNavigatorOld} only traverses the matching - * branch. - *

- * The navigator accepts an action and provides callback hooks for it to act on the traversed - * nodes at each significant step. See {@link JSONNavigateActionOld}. - *

- * See package-info for more details - *

- * Example: - *

- * To navigate the branch k1.k2 of the object {"k1":{"k2":"v1"}, "k3":{"k4":"v2"}} instantiate - * the navigator like so: new JSONNavigator("k1.k2") - * - * @author adoneitan@gmail.com - * - */ -public class JSONNavigatorOld -{ - protected List pathsToNavigate; - protected JSONNavigateActionOld action; - - public JSONNavigatorOld(JSONNavigateActionOld action, JSONArray pathsToNavigate) - { - if (action == null) { - throw new IllegalArgumentException("JSONNavigateActionOld cannot be null"); - } - this.action = action; - if (pathsToNavigate == null || pathsToNavigate.isEmpty()) { - this.pathsToNavigate = Collections.emptyList(); - } - else - { - this.pathsToNavigate = new LinkedList(); - for (Object s : pathsToNavigate) { - this.pathsToNavigate.add((String) s); - } - } - } - - public JSONNavigatorOld(JSONNavigateActionOld action, List pathsToNavigate) - { - if (action == null) { - throw new IllegalArgumentException("JSONNavigateActionOld cannot be null"); - } - this.action = action; - this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.size() == 0 ? - Collections.emptyList() : pathsToNavigate; - } - - public JSONNavigatorOld(JSONNavigateActionOld action, String... pathsToNavigate) - { - if (action == null) { - throw new IllegalArgumentException("JSONNavigateActionOld cannot be null"); - } - this.action = action; - this.pathsToNavigate = pathsToNavigate == null || pathsToNavigate.length == 0 ? - Collections.emptyList() : new LinkedList(Arrays.asList(pathsToNavigate)); - } - - public void nav(JSONObject object) throws Exception - { - if (action.handleNavigationStart(object, pathsToNavigate)) - { - for (String path: pathsToNavigate) - { - try - { - if (path != null && !path.equals("") && action.handleNextPath(path)) - { - TreePath jp = new TreePath(path, new DotDelimiter().withAcceptDelimiterInNodeName(true)); - nav(object, jp); - action.handlePathEnd(path); - } - } - catch (Exception e) - { - if (action.failPathSilently(path ,e)) { - break; - } - else if (action.failPathFast(path, e)) { - throw e; - } - } - } - } - action.handleNavigationEnd(); - } - - private void nav(JSONObject source, TreePath jp) - { - if (jp.hasNext()) - { - if (source == null) - { - //source is null - navigation impossible - return; - } - String next = jp.next(); - if (!source.containsKey(next)) - { - // reached end of branch in source before end of specified json path - - // the specified path is illegal because it does not exist in the source. - action.handlePrematureNavigatedBranchEnd(jp, source); - } - else if (source.get(next) instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) source.get(next))) - { - //reached JSONObject node - handle it and recur into it - nav((JSONObject) source.get(next), jp); - } - else if (source.get(next) instanceof JSONArray && action.handleJSONArrray(jp, (JSONArray) source.get(next))) - { - //reached JSONArray node - handle it and recur into it - nav((JSONArray) source.get(next), jp); - } - else if (jp.hasNext()) - { - // reached leaf node (not a container) in source but specified path expects children - - // the specified path is illegal because it does not exist in the source. - action.handlePrematureNavigatedBranchEnd(jp, source.get(next)); - } - else if (!jp.hasNext()) - { - //reached leaf in source and specified path is also at leaf -> handle it - action.handleJSONObjectLeaf(jp, source.get(next)); - } - else - { - throw new IllegalStateException("fatal: unreachable code reached at '" + jp.origin() + "'"); - } - } - action.handleObjectEnd(jp); - } - - private void nav(JSONArray source, TreePath jp) - { - if (source == null) - { - //array is null - navigation impossible - return; - } - int arrIndex = 0; - for (Object arrItem : source.toArray()) - { - if (arrItem instanceof JSONObject && action.handleJSONObject(jp, (JSONObject) arrItem)) - { - // clone the path so that for each JSONObject in the array, - // the iterator continues from the same position in the path - TreePath jpClone = getClone(jp); - nav((JSONObject) arrItem, jpClone); - } - else if (arrItem instanceof JSONArray) - { - throw new IllegalArgumentException("illegal json - found array nested inside array at: '" + jp.origin() + "'"); - } - else if (!jp.hasNext()) - { - //reached leaf - handle it - action.handleJSONArrayLeaf(arrIndex, arrItem); - } - arrIndex++; - } - action.handleJSONArrayEnd(jp); - - } - - private TreePath getClone(TreePath jp) - { - try - { - return jp.clone(); - } - catch (CloneNotSupportedException e) { - throw new RuntimeException("failed to clone json path", e); - } - } -} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java index 0d2059b4..fbb8361e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java @@ -12,21 +12,22 @@ * See package-info for more details * * @author adoneitan@gmail.com + * @since 15 June 2016 */ public interface NavigateAction, L extends List> { - /** - * called before any navigation of the {@link M} starts - * @return true if navigation should start at all - */ - boolean start(M objectToNavigate, Collection pathsToNavigate); - /** * called before navigation of a new path starts * @return true if the specified path should be navigated */ boolean pathStart(String path); + /** + * called before any navigation of the {@link M} starts + * @return true if navigation should start at all + */ + boolean start(M objectToNavigate, Collection pathsToNavigate); + /** * reached end of branch in source before end of specified json path - * the specified path does not exist in the source. diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java index 48274fa8..bcd4e304 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/TreeNavigator.java @@ -25,6 +25,7 @@ * the navigator like so: new JSONNavigator("k1.k2") * * @author adoneitan@gmail.com + * @since 15 June 2016. * */ public class TreeNavigator, L extends List> diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java index fef786e1..40727e40 100644 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/package-info.java @@ -1,10 +1,10 @@ /** - * Navigate user-specified paths in a {@link net.minidev.json.JSONObject} and process them + * Navigate user-specified paths in a tree made up of {@link java.util.Map}s and {@link java.util.List} and process them *

- * {@link net.minidev.json.actions.navigate.JSONNavigatorOld} only navigates through branches corresponding + * {@link net.minidev.json.actions.navigate.TreeNavigator} only navigates through branches corresponding * to user-specified paths. For each path, the navigation starts at the root and moves down the branch. *

- * The {@link net.minidev.json.actions.navigate.JSONNavigatorOld} accepts a + * The {@link net.minidev.json.actions.navigate.TreeNavigator} accepts a * {@link net.minidev.json.actions.navigate.NavigateAction} and provides callback hooks at each significant * step which the {@link net.minidev.json.actions.navigate.NavigateAction} may use to process * the nodes. From ad93cc532a7b0ad45aaf7633eacf5539ff61deb7 Mon Sep 17 00:00:00 2001 From: "adoneitan@gmail.com" Date: Tue, 21 Jun 2016 06:43:49 +0300 Subject: [PATCH 10/10] finalize tree navigator --- .../net/minidev/json/actions/JSONBuilder.java | 17 ----------------- .../json/actions/navigate/CopyPathsAction.java | 13 ++++++------- .../json/actions/navigate/NavigateAction.java | 13 ++++++++++--- .../json/actions/navigate/TreeNavigator.java | 12 ++++++------ .../actions}/KeysPrintActionTest.java | 4 +++- 5 files changed, 25 insertions(+), 34 deletions(-) delete mode 100644 json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java rename json-smart/src/test/java/net/minidev/json/{actions/traverse => test/actions}/KeysPrintActionTest.java (84%) diff --git a/json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java b/json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java deleted file mode 100644 index 23e65245..00000000 --- a/json-smart/src/main/java/net/minidev/json/actions/JSONBuilder.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.minidev.json.actions; - -import net.minidev.json.JSONObject; - -/** - * @author adoneitan@gmail.com - * @since 14 June 2016 - */ -public class JSONBuilder -{ - protected JSONObject result; - - public void append(String path, String value) - { - - } -} diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java index 252c5659..52d5df7e 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/CopyPathsAction.java @@ -42,16 +42,10 @@ public boolean start(JSONObject source, Collection pathsToCopy) destTree = null; return false; } - destTree = new JSONObject(); if (pathsToCopy == null || pathsToCopy.size() == 0) { return false; } - - /** - * using ARRAY_PUSH which adds any new entry encountered in arrays, effectively - * allowing duplicate objects in the array, which is supported by Gigya - */ return true; } @@ -86,10 +80,15 @@ public boolean recurInto(TreePath jp, JSONArray o) } @Override - public void handlePrematureNavigatedBranchEnd(TreePath jp, Object source) { + public void foundLeafBeforePathEnd(TreePath jp, Object obj) { throw new IllegalArgumentException("branch is shorter than path - path not found in source: '" + jp.origin() + "'"); } + @Override + public void pathTailNotFound(TreePath jp, Object source) { + throw new IllegalArgumentException("cannot find next element of path - path not found in source: '" + jp.origin() + "'"); + } + @Override public void handleLeaf(TreePath jp, Object o) { ((JSONObject) destNodeStack.peek()).put(jp.curr(), o); diff --git a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java index fbb8361e..51195e1a 100755 --- a/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java +++ b/json-smart/src/main/java/net/minidev/json/actions/navigate/NavigateAction.java @@ -29,10 +29,10 @@ public interface NavigateAction, L extends List pathsToNavigate); /** - * reached end of branch in source before end of specified json path - - * the specified path does not exist in the source. + * reached end of branch in source before end of specified path - + * the specified path does not exist in the source */ - void handlePrematureNavigatedBranchEnd(TreePath tp, Object source); + void pathTailNotFound(TreePath tp, Object source); /** * called after the navigation of a path ends @@ -63,6 +63,12 @@ public interface NavigateAction, L extends List, L extends List