|
|
|
@ -2,13 +2,14 @@
|
|
|
|
|
package com.jayway.jsonpath.internal.token; |
|
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
|
|
import java.util.logging.Level; |
|
|
|
|
import java.util.logging.Logger; |
|
|
|
|
|
|
|
|
|
import com.jayway.jsonpath.Configuration; |
|
|
|
|
import com.jayway.jsonpath.EvaluationCallback; |
|
|
|
|
import com.jayway.jsonpath.internal.Path; |
|
|
|
|
import com.fasterxml.jackson.core.JsonParser; |
|
|
|
|
import org.slf4j.Logger; |
|
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
|
import com.fasterxml.jackson.core.JsonToken; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* |
|
|
|
@ -16,18 +17,15 @@ import org.slf4j.LoggerFactory;
|
|
|
|
|
**/ |
|
|
|
|
public class TokenStack |
|
|
|
|
{ |
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(TokenStack.class); |
|
|
|
|
protected static Logger log = Logger.getLogger("com.jayway.jsonpath"); |
|
|
|
|
|
|
|
|
|
protected Configuration conf; |
|
|
|
|
protected Stack<TokenStackElement> elements; |
|
|
|
|
protected Stack<Object> objStack; |
|
|
|
|
protected List<Path> paths; |
|
|
|
|
protected Map<TokenStackElement, Path> matchedPaths; |
|
|
|
|
|
|
|
|
|
private TokenStackElement curr; |
|
|
|
|
private Path rootMatch; |
|
|
|
|
private Class jsonArrayType; |
|
|
|
|
private Class jsonObjectType; |
|
|
|
|
|
|
|
|
|
public TokenStack(Configuration conf) |
|
|
|
|
{ |
|
|
|
@ -35,11 +33,7 @@ public class TokenStack
|
|
|
|
|
paths = new ArrayList<Path>(); |
|
|
|
|
matchedPaths = new HashMap<TokenStackElement, Path>(); |
|
|
|
|
elements = new Stack<TokenStackElement>(); |
|
|
|
|
objStack = new Stack<Object>(); |
|
|
|
|
rootMatch = null; |
|
|
|
|
|
|
|
|
|
this.jsonArrayType = this.getNewJsonArray().getClass(); |
|
|
|
|
this.jsonObjectType = this.getNewJsonObject().getClass(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Stack<TokenStackElement> getStack() |
|
|
|
@ -55,26 +49,28 @@ public class TokenStack
|
|
|
|
|
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, with results. |
|
|
|
|
* Note: GSON not supported |
|
|
|
|
* reads from stream and notifies the callback of matched registered paths |
|
|
|
|
*/ |
|
|
|
|
public void read(JsonParser parser, EvaluationCallback callback, boolean getParents) |
|
|
|
|
public void read(JsonParser parser, EvaluationCallback callback) |
|
|
|
|
throws Exception |
|
|
|
|
{ |
|
|
|
|
assert(callback != null); |
|
|
|
|
|
|
|
|
|
Object obj = null; |
|
|
|
|
boolean needsPathCheck = false; |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
if (null == curr && elements.empty()) { |
|
|
|
|
// check for $ patterns
|
|
|
|
|
for (Path path : paths) { |
|
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
|
matchedPaths.put(curr, path); |
|
|
|
|
callback.resultFound(path); |
|
|
|
|
rootMatch = path; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
while (parser.nextToken() != null) { |
|
|
|
|
//log.debug("type/name/val: " + parser.getCurrentToken() + " " + parser.getCurrentName() + " " + parser.getText());
|
|
|
|
|
boolean saveMatch = false; |
|
|
|
|
switch (parser.getCurrentToken()) { |
|
|
|
|
case START_ARRAY: |
|
|
|
@ -89,42 +85,33 @@ public class TokenStack
|
|
|
|
|
saveMatch = true; |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
elements.push(curr); |
|
|
|
|
|
|
|
|
|
obj = stackPush(parser.getCurrentName(),getNewJsonArray()); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case END_ARRAY: |
|
|
|
|
{ |
|
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
|
if (match != null) { |
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
|
callback.resultFoundExit(match); |
|
|
|
|
} |
|
|
|
|
elements.pop(); |
|
|
|
|
if (elements.empty()) curr = null; |
|
|
|
|
else curr = elements.peek(); |
|
|
|
|
|
|
|
|
|
obj = stackPop(callback, curr, getJsonArrayType(), match); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case VALUE_EMBEDDED_OBJECT: |
|
|
|
|
case START_OBJECT: |
|
|
|
|
{ |
|
|
|
|
obj = stackPush(parser.getCurrentName(), getNewJsonObject()); |
|
|
|
|
|
|
|
|
|
if (isArray(curr)) { |
|
|
|
|
if (curr != null && curr.getType() == TokenType.ARRAY_TOKEN) { |
|
|
|
|
if (((ArrayToken)curr).getValue() != null && |
|
|
|
|
matchedPaths.containsKey(curr)) |
|
|
|
|
{ |
|
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
|
if (getParents) { |
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
|
} |
|
|
|
|
callback.resultFoundExit(match); |
|
|
|
|
|
|
|
|
|
if (match.checkForMatch(this)) { |
|
|
|
|
|
|
|
|
|
matchedPaths.put(curr, match); |
|
|
|
|
//callback.resultFound(match);
|
|
|
|
|
curr.setMatched(); |
|
|
|
|
callback.resultFound(match); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else if (null == curr && elements.empty()) { |
|
|
|
@ -132,7 +119,7 @@ public class TokenStack
|
|
|
|
|
for (Path path : paths) { |
|
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
|
matchedPaths.put(curr, path); |
|
|
|
|
//callback.resultFound(path);
|
|
|
|
|
callback.resultFound(path); |
|
|
|
|
rootMatch = path; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -152,24 +139,20 @@ public class TokenStack
|
|
|
|
|
} |
|
|
|
|
case END_OBJECT: |
|
|
|
|
{ |
|
|
|
|
if (getParents) { |
|
|
|
|
if (!"$".equals(curr)) { |
|
|
|
|
Path match = matchedPaths.remove(curr); |
|
|
|
|
if (match != null) { |
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
|
callback.resultFoundExit(match); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
Path match = matchedPaths.get("$"); |
|
|
|
|
if (match != null) { |
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, match); |
|
|
|
|
callback.resultFoundExit(match); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
elements.pop(); |
|
|
|
|
if (elements.empty()) curr = null; |
|
|
|
|
else curr = elements.peek(); |
|
|
|
|
|
|
|
|
|
obj = stackPop(callback, curr, this.getJsonObjectType(), null); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case FIELD_NAME: |
|
|
|
@ -183,7 +166,6 @@ public class TokenStack
|
|
|
|
|
StringToken newToken = new StringToken("FALSE"); |
|
|
|
|
curr.setValue(newToken); |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case VALUE_TRUE: |
|
|
|
@ -191,7 +173,6 @@ public class TokenStack
|
|
|
|
|
StringToken newToken = new StringToken("TRUE"); |
|
|
|
|
curr.setValue(newToken); |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case VALUE_NUMBER_FLOAT: |
|
|
|
@ -200,7 +181,6 @@ public class TokenStack
|
|
|
|
|
new FloatToken((float)parser.getValueAsDouble()); |
|
|
|
|
curr.setValue(newToken); |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case VALUE_NUMBER_INT: |
|
|
|
@ -208,7 +188,6 @@ public class TokenStack
|
|
|
|
|
IntToken newToken = new IntToken(parser.getValueAsInt()); |
|
|
|
|
curr.setValue(newToken); |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
objPutVal(obj, curr, newToken.value); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case VALUE_STRING: |
|
|
|
@ -216,14 +195,12 @@ public class TokenStack
|
|
|
|
|
StringToken newToken = new StringToken(parser.getText()); |
|
|
|
|
curr.setValue(newToken); |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
objPutVal(obj, curr, parser.getText()); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case VALUE_NULL: |
|
|
|
|
{ |
|
|
|
|
curr.setValue(null); |
|
|
|
|
needsPathCheck = true; |
|
|
|
|
objPutVal(obj, curr, null); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
default: |
|
|
|
@ -234,117 +211,19 @@ public class TokenStack
|
|
|
|
|
for (Path path : paths) { |
|
|
|
|
if (path.checkForMatch(this)) { |
|
|
|
|
if (saveMatch) matchedPaths.put(curr, path); |
|
|
|
|
curr.setMatched(); |
|
|
|
|
if (getParents) { |
|
|
|
|
callback.resultFound(parser.getCurrentToken(), obj, path); |
|
|
|
|
} |
|
|
|
|
callback.resultFound(path); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
needsPathCheck = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (rootMatch != null && elements.empty() && getParents) { |
|
|
|
|
if (isArray(curr)) { |
|
|
|
|
obj = this.getNewJsonArray(); |
|
|
|
|
} |
|
|
|
|
callback.resultFoundExit(parser.getCurrentToken(), obj, rootMatch); |
|
|
|
|
if (rootMatch != null && elements.empty()) { |
|
|
|
|
callback.resultFoundExit(rootMatch); |
|
|
|
|
rootMatch = null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Object getNewJsonObject() { |
|
|
|
|
return conf.jsonProvider().createMap(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Object getJsonArrayType() { |
|
|
|
|
return this.jsonArrayType; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Object getNewJsonArray() { |
|
|
|
|
return conf.jsonProvider().createArray(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Class<? extends Object> getJsonObjectType() { |
|
|
|
|
return this.jsonObjectType; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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() == getJsonArrayType()) { |
|
|
|
|
((List)obj).add(jsObj); |
|
|
|
|
} else if (obj.getClass() == getJsonObjectType()) { |
|
|
|
|
((HashMap<String,Object>)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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return obj; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean isArray(TokenStackElement current) { |
|
|
|
|
return current != null && current.getType() == TokenType.ARRAY_TOKEN; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void objPutVal(Object objIn, TokenStackElement el, Object value) throws Exception { |
|
|
|
|
if (objIn == null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Class objInCls = objIn.getClass(); |
|
|
|
|
if (objInCls == this.getJsonObjectType()) { |
|
|
|
|
HashMap obj = (HashMap)objIn; |
|
|
|
|
obj.put(((ObjectToken)el).key, value); |
|
|
|
|
} else if (objInCls == this.getJsonArrayType()) { |
|
|
|
|
ArrayList obj = (ArrayList)objIn; |
|
|
|
|
obj.add(value); |
|
|
|
|
} else { |
|
|
|
|
throw new Exception("Unhandled type: " + objInCls); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public String toString() |
|
|
|
|
{ |
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
|