|
|
@ -2,14 +2,15 @@ |
|
|
|
package com.jayway.jsonpath.internal.token; |
|
|
|
package com.jayway.jsonpath.internal.token; |
|
|
|
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
|
import java.util.*; |
|
|
|
import java.util.logging.Level; |
|
|
|
|
|
|
|
import java.util.logging.Logger; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import net.minidev.json.JSONArray; |
|
|
|
|
|
|
|
import net.minidev.json.JSONObject; |
|
|
|
import com.jayway.jsonpath.Configuration; |
|
|
|
import com.jayway.jsonpath.Configuration; |
|
|
|
import com.jayway.jsonpath.EvaluationCallback; |
|
|
|
import com.jayway.jsonpath.EvaluationCallback; |
|
|
|
import com.jayway.jsonpath.internal.Path; |
|
|
|
import com.jayway.jsonpath.internal.Path; |
|
|
|
import com.fasterxml.jackson.core.JsonParser; |
|
|
|
import com.fasterxml.jackson.core.JsonParser; |
|
|
|
import com.fasterxml.jackson.core.JsonToken; |
|
|
|
import org.slf4j.Logger; |
|
|
|
|
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* |
|
|
|
* |
|
|
@ -17,10 +18,11 @@ import com.fasterxml.jackson.core.JsonToken; |
|
|
|
**/ |
|
|
|
**/ |
|
|
|
public class TokenStack |
|
|
|
public class TokenStack |
|
|
|
{ |
|
|
|
{ |
|
|
|
protected static Logger log = Logger.getLogger("com.jayway.jsonpath"); |
|
|
|
private static final Logger log = LoggerFactory.getLogger(TokenStack.class); |
|
|
|
|
|
|
|
|
|
|
|
protected Configuration conf; |
|
|
|
protected Configuration conf; |
|
|
|
protected Stack<TokenStackElement> elements; |
|
|
|
protected Stack<TokenStackElement> elements; |
|
|
|
|
|
|
|
protected Stack<Object> objStack; |
|
|
|
protected List<Path> paths; |
|
|
|
protected List<Path> paths; |
|
|
|
protected Map<TokenStackElement, Path> matchedPaths; |
|
|
|
protected Map<TokenStackElement, Path> matchedPaths; |
|
|
|
|
|
|
|
|
|
|
@ -33,6 +35,7 @@ public class TokenStack |
|
|
|
paths = new ArrayList<Path>(); |
|
|
|
paths = new ArrayList<Path>(); |
|
|
|
matchedPaths = new HashMap<TokenStackElement, Path>(); |
|
|
|
matchedPaths = new HashMap<TokenStackElement, Path>(); |
|
|
|
elements = new Stack<TokenStackElement>(); |
|
|
|
elements = new Stack<TokenStackElement>(); |
|
|
|
|
|
|
|
objStack = new Stack<Object>(); |
|
|
|
rootMatch = null; |
|
|
|
rootMatch = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -49,14 +52,21 @@ public class TokenStack |
|
|
|
paths.add(path); |
|
|
|
paths.add(path); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void read(JsonParser parser, EvaluationCallback callback) |
|
|
|
|
|
|
|
throws Exception |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
read(parser,callback,true); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* reads from stream and notifies the callback of matched registered paths |
|
|
|
* reads from stream and notifies the callback of matched registered paths |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void read(JsonParser parser, EvaluationCallback callback) |
|
|
|
public void read(JsonParser parser, EvaluationCallback callback, boolean getParents) |
|
|
|
throws Exception |
|
|
|
throws Exception |
|
|
|
{ |
|
|
|
{ |
|
|
|
assert(callback != null); |
|
|
|
assert(callback != null); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object obj = null; |
|
|
|
boolean needsPathCheck = false; |
|
|
|
boolean needsPathCheck = false; |
|
|
|
/* |
|
|
|
/* |
|
|
|
if (null == curr && elements.empty()) { |
|
|
|
if (null == curr && elements.empty()) { |
|
|
@ -71,6 +81,7 @@ public class TokenStack |
|
|
|
} |
|
|
|
} |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
while (parser.nextToken() != null) { |
|
|
|
while (parser.nextToken() != null) { |
|
|
|
|
|
|
|
//log.debug("type/name/val: " + parser.getCurrentToken() + " " + parser.getCurrentName() + " " + parser.getText());
|
|
|
|
boolean saveMatch = false; |
|
|
|
boolean saveMatch = false; |
|
|
|
switch (parser.getCurrentToken()) { |
|
|
|
switch (parser.getCurrentToken()) { |
|
|
|
case START_ARRAY: |
|
|
|
case START_ARRAY: |
|
|
@ -85,33 +96,42 @@ public class TokenStack |
|
|
|
saveMatch = true; |
|
|
|
saveMatch = true; |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
elements.push(curr); |
|
|
|
elements.push(curr); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
obj = stackPush(parser.getCurrentName(),new JSONArray()); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case END_ARRAY: |
|
|
|
case END_ARRAY: |
|
|
|
{ |
|
|
|
{ |
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
if (match != null) { |
|
|
|
if (match != null) { |
|
|
|
callback.resultFoundExit(match); |
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
} |
|
|
|
} |
|
|
|
elements.pop(); |
|
|
|
elements.pop(); |
|
|
|
if (elements.empty()) curr = null; |
|
|
|
if (elements.empty()) curr = null; |
|
|
|
else curr = elements.peek(); |
|
|
|
else curr = elements.peek(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
obj = stackPop(callback, curr, JSONArray.class, match); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case VALUE_EMBEDDED_OBJECT: |
|
|
|
case VALUE_EMBEDDED_OBJECT: |
|
|
|
case START_OBJECT: |
|
|
|
case START_OBJECT: |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (curr != null && curr.getType() == TokenType.ARRAY_TOKEN) { |
|
|
|
obj = stackPush(parser.getCurrentName(), new JSONObject()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isArray(curr)) { |
|
|
|
if (((ArrayToken)curr).getValue() != null && |
|
|
|
if (((ArrayToken)curr).getValue() != null && |
|
|
|
matchedPaths.containsKey(curr)) |
|
|
|
matchedPaths.containsKey(curr)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
callback.resultFoundExit(match); |
|
|
|
if (getParents) { |
|
|
|
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (match.checkForMatch(this)) { |
|
|
|
if (match.checkForMatch(this)) { |
|
|
|
|
|
|
|
|
|
|
|
matchedPaths.put(curr, match); |
|
|
|
matchedPaths.put(curr, match); |
|
|
|
callback.resultFound(match); |
|
|
|
//callback.resultFound(match);
|
|
|
|
|
|
|
|
curr.setMatched(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (null == curr && elements.empty()) { |
|
|
|
} else if (null == curr && elements.empty()) { |
|
|
@ -119,7 +139,7 @@ public class TokenStack |
|
|
|
for (Path path : paths) { |
|
|
|
for (Path path : paths) { |
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
matchedPaths.put(curr, path); |
|
|
|
matchedPaths.put(curr, path); |
|
|
|
callback.resultFound(path); |
|
|
|
//callback.resultFound(path);
|
|
|
|
rootMatch = path; |
|
|
|
rootMatch = path; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -139,20 +159,24 @@ public class TokenStack |
|
|
|
} |
|
|
|
} |
|
|
|
case END_OBJECT: |
|
|
|
case END_OBJECT: |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
if (getParents) { |
|
|
|
if (!"$".equals(curr)) { |
|
|
|
if (!"$".equals(curr)) { |
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
if (match != null) { |
|
|
|
if (match != null) { |
|
|
|
callback.resultFoundExit(match); |
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
Path match = matchedPaths.get("$"); |
|
|
|
Path match = matchedPaths.get("$"); |
|
|
|
if (match != null) { |
|
|
|
if (match != null) { |
|
|
|
callback.resultFoundExit(match); |
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
elements.pop(); |
|
|
|
elements.pop(); |
|
|
|
if (elements.empty()) curr = null; |
|
|
|
if (elements.empty()) curr = null; |
|
|
|
else curr = elements.peek(); |
|
|
|
else curr = elements.peek(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
obj = stackPop(callback, curr, JSONObject.class, null); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case FIELD_NAME: |
|
|
|
case FIELD_NAME: |
|
|
@ -166,6 +190,7 @@ public class TokenStack |
|
|
|
StringToken newToken = new StringToken("FALSE"); |
|
|
|
StringToken newToken = new StringToken("FALSE"); |
|
|
|
curr.setValue(newToken); |
|
|
|
curr.setValue(newToken); |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case VALUE_TRUE: |
|
|
|
case VALUE_TRUE: |
|
|
@ -173,6 +198,7 @@ public class TokenStack |
|
|
|
StringToken newToken = new StringToken("TRUE"); |
|
|
|
StringToken newToken = new StringToken("TRUE"); |
|
|
|
curr.setValue(newToken); |
|
|
|
curr.setValue(newToken); |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case VALUE_NUMBER_FLOAT: |
|
|
|
case VALUE_NUMBER_FLOAT: |
|
|
@ -181,6 +207,7 @@ public class TokenStack |
|
|
|
new FloatToken((float)parser.getValueAsDouble()); |
|
|
|
new FloatToken((float)parser.getValueAsDouble()); |
|
|
|
curr.setValue(newToken); |
|
|
|
curr.setValue(newToken); |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case VALUE_NUMBER_INT: |
|
|
|
case VALUE_NUMBER_INT: |
|
|
@ -188,6 +215,7 @@ public class TokenStack |
|
|
|
IntToken newToken = new IntToken(parser.getValueAsInt()); |
|
|
|
IntToken newToken = new IntToken(parser.getValueAsInt()); |
|
|
|
curr.setValue(newToken); |
|
|
|
curr.setValue(newToken); |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case VALUE_STRING: |
|
|
|
case VALUE_STRING: |
|
|
@ -195,12 +223,14 @@ public class TokenStack |
|
|
|
StringToken newToken = new StringToken(parser.getText()); |
|
|
|
StringToken newToken = new StringToken(parser.getText()); |
|
|
|
curr.setValue(newToken); |
|
|
|
curr.setValue(newToken); |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
|
|
|
|
objPutVal(obj, curr, parser.getText()); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
case VALUE_NULL: |
|
|
|
case VALUE_NULL: |
|
|
|
{ |
|
|
|
{ |
|
|
|
curr.setValue(null); |
|
|
|
curr.setValue(null); |
|
|
|
needsPathCheck = true; |
|
|
|
needsPathCheck = true; |
|
|
|
|
|
|
|
objPutVal(obj, curr, null); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
default: |
|
|
|
default: |
|
|
@ -211,18 +241,113 @@ public class TokenStack |
|
|
|
for (Path path : paths) { |
|
|
|
for (Path path : paths) { |
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
if (saveMatch) matchedPaths.put(curr, path); |
|
|
|
if (saveMatch) matchedPaths.put(curr, path); |
|
|
|
callback.resultFound(path); |
|
|
|
curr.setMatched(); |
|
|
|
|
|
|
|
if (getParents) { |
|
|
|
|
|
|
|
callback.resultFound(parser.getCurrentToken(), obj, path); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
needsPathCheck = false; |
|
|
|
needsPathCheck = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (rootMatch != null && elements.empty()) { |
|
|
|
if (rootMatch != null && elements.empty() && getParents) { |
|
|
|
callback.resultFoundExit(rootMatch); |
|
|
|
if (isArray(curr)) { |
|
|
|
|
|
|
|
obj = new JSONArray(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, rootMatch); |
|
|
|
rootMatch = null; |
|
|
|
rootMatch = null; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Object stackPush(String key, Object jsObj) throws Exception { |
|
|
|
|
|
|
|
if (jsObj == null) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.objStack.size() > 0) { |
|
|
|
|
|
|
|
Object obj = this.objStack.peek(); |
|
|
|
|
|
|
|
//log.trace("PP : Push[" + this.objStack.size() + "] " + jsObj.getClass().getSimpleName() + " -> " + obj.getClass());
|
|
|
|
|
|
|
|
if (obj.getClass() == JSONArray.class) { |
|
|
|
|
|
|
|
((JSONArray)obj).add(jsObj); |
|
|
|
|
|
|
|
} else if (obj.getClass() == JSONObject.class) { |
|
|
|
|
|
|
|
((JSONObject)obj).put(key, jsObj); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
throw new Exception("Unhandled type: " + obj.getClass()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
//log.trace("PP : Push " + jsObj.getClass().getSimpleName() + " -> ROOT");
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.objStack.add(jsObj); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return jsObj; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private <T> Object stackPop(EvaluationCallback callback, TokenStackElement tse, T jsObj, Path path) throws Exception { |
|
|
|
|
|
|
|
if (jsObj == null) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object obj = null; |
|
|
|
|
|
|
|
Object popObj = null; |
|
|
|
|
|
|
|
if (this.objStack.size() > 0) { |
|
|
|
|
|
|
|
popObj = this.objStack.peek(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//log.trace("PP : Pop " +( popObj != null ? popObj.getClass() : " null"));
|
|
|
|
|
|
|
|
if (popObj.getClass() != jsObj) { |
|
|
|
|
|
|
|
throw new Exception("Unexpected type : " + popObj.getClass()); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
popObj = this.objStack.pop(); |
|
|
|
|
|
|
|
if (this.objStack.size() > 0) { |
|
|
|
|
|
|
|
obj = objStack.peek(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
//log.debug("PP : Now ROOT");
|
|
|
|
|
|
|
|
obj = null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (tse != null && tse.getMatched()) { |
|
|
|
|
|
|
|
callback.resultFound("Stack", popObj, path); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//log.info("PP : Parent now[" + this.objStack.size() + "]: " + objShow(obj));
|
|
|
|
|
|
|
|
return obj; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean isArray(TokenStackElement current) { |
|
|
|
|
|
|
|
return current != null && current.getType() == TokenType.ARRAY_TOKEN; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public String objShow(Object obj) { |
|
|
|
|
|
|
|
if (obj != null) { |
|
|
|
|
|
|
|
Class cls = obj.getClass(); |
|
|
|
|
|
|
|
if (cls == JSONObject.class) { |
|
|
|
|
|
|
|
return "JSONObject: " + ((JSONObject)obj).toString(); |
|
|
|
|
|
|
|
} else if (cls == JSONArray.class) { |
|
|
|
|
|
|
|
return "JSONArray: " + Arrays.toString(((JSONArray)obj).toArray()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return "NA"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void objPutVal(Object objIn, TokenStackElement el, Object value) throws Exception { |
|
|
|
|
|
|
|
if (objIn == null) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Class objInCls = objIn.getClass(); |
|
|
|
|
|
|
|
if (objInCls == JSONObject.class) { |
|
|
|
|
|
|
|
JSONObject obj = (JSONObject)objIn; |
|
|
|
|
|
|
|
obj.put(((ObjectToken)el).key, value); |
|
|
|
|
|
|
|
} else if (objInCls == JSONArray.class) { |
|
|
|
|
|
|
|
JSONArray obj = (JSONArray)objIn; |
|
|
|
|
|
|
|
obj.add(value); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
throw new Exception("Unhandled type: " + objInCls); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public String toString() |
|
|
|
public String toString() |
|
|
|
{ |
|
|
|
{ |
|
|
|