Browse Source
Extracted all of the PathToken::evaluate methods out to *Evaluator classes, now called by a (configurable) DefaultEvaluator. If you want to write your own custom evaluator, you now need to implement the EvaluatorDispatcher interface (one function) and then traverse the PathToken nodes as fits your need. This presumes, of course, that you want to use the `EvaluationContext` as-is. Otherwise, get the root node and do your own thing.pull/169/head
Rick Osborne
9 years ago
41 changed files with 1074 additions and 792 deletions
@ -1,189 +0,0 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path; |
||||
|
||||
import com.jayway.jsonpath.InvalidPathException; |
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import static java.lang.String.format; |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
public class ArrayPathToken extends PathToken { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class); |
||||
|
||||
private final ArraySliceOperation arraySliceOperation; |
||||
private final ArrayIndexOperation arrayIndexOperation; |
||||
|
||||
ArrayPathToken(final ArraySliceOperation arraySliceOperation) { |
||||
this.arraySliceOperation = arraySliceOperation; |
||||
this.arrayIndexOperation = null; |
||||
} |
||||
|
||||
ArrayPathToken(final ArrayIndexOperation arrayIndexOperation) { |
||||
this.arrayIndexOperation = arrayIndexOperation; |
||||
this.arraySliceOperation = null; |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
if (! checkArrayModel(currentPath, model, ctx)) |
||||
return; |
||||
if(arraySliceOperation != null){ |
||||
evaluateSliceOperation(currentPath, parent, model, ctx); |
||||
} else { |
||||
evaluateIndexOperation(currentPath, parent, model, ctx); |
||||
} |
||||
|
||||
} |
||||
|
||||
public void evaluateIndexOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
|
||||
if (! checkArrayModel(currentPath, model, ctx)) |
||||
return; |
||||
|
||||
if(arrayIndexOperation.isSingleIndexOperation()){ |
||||
handleArrayIndex(arrayIndexOperation.indexes().get(0), currentPath, model, ctx); |
||||
} else { |
||||
for (Integer index : arrayIndexOperation.indexes()) { |
||||
handleArrayIndex(index, currentPath, model, ctx); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void evaluateSliceOperation(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
|
||||
if (! checkArrayModel(currentPath, model, ctx)) |
||||
return; |
||||
|
||||
switch (arraySliceOperation.operation()) { |
||||
case SLICE_FROM: |
||||
sliceFrom(arraySliceOperation, currentPath, parent, model, ctx); |
||||
break; |
||||
case SLICE_BETWEEN: |
||||
sliceBetween(arraySliceOperation, currentPath, parent, model, ctx); |
||||
break; |
||||
case SLICE_TO: |
||||
sliceTo(arraySliceOperation, currentPath, parent, model, ctx); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public void sliceFrom(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
int length = ctx.jsonProvider().length(model); |
||||
int from = operation.from(); |
||||
if (from < 0) { |
||||
//calculate slice start from array length
|
||||
from = length + from; |
||||
} |
||||
from = Math.max(0, from); |
||||
|
||||
logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString()); |
||||
|
||||
if (length == 0 || from >= length) { |
||||
return; |
||||
} |
||||
for (int i = from; i < length; i++) { |
||||
handleArrayIndex(i, currentPath, model, ctx); |
||||
} |
||||
} |
||||
|
||||
public void sliceBetween(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
int length = ctx.jsonProvider().length(model); |
||||
int from = operation.from(); |
||||
int to = operation.to(); |
||||
|
||||
to = Math.min(length, to); |
||||
|
||||
if (from >= to || length == 0) { |
||||
return; |
||||
} |
||||
|
||||
logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString()); |
||||
|
||||
for (int i = from; i < to; i++) { |
||||
handleArrayIndex(i, currentPath, model, ctx); |
||||
} |
||||
} |
||||
|
||||
public void sliceTo(ArraySliceOperation operation, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
int length = ctx.jsonProvider().length(model); |
||||
if (length == 0) { |
||||
return; |
||||
} |
||||
int to = operation.to(); |
||||
if (to < 0) { |
||||
//calculate slice end from array length
|
||||
to = length + to; |
||||
} |
||||
to = Math.min(length, to); |
||||
|
||||
logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString()); |
||||
|
||||
for (int i = 0; i < to; i++) { |
||||
handleArrayIndex(i, currentPath, model, ctx); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
if(arrayIndexOperation != null){ |
||||
return arrayIndexOperation.toString(); |
||||
} else { |
||||
return arraySliceOperation.toString(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
if(arrayIndexOperation != null){ |
||||
return arrayIndexOperation.isSingleIndexOperation(); |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Check if model is non-null and array. |
||||
* @param currentPath |
||||
* @param model |
||||
* @param ctx |
||||
* @return false if current evaluation call must be skipped, true otherwise |
||||
* @throws PathNotFoundException if model is null and evaluation must be interrupted |
||||
* @throws InvalidPathException if model is not an array and evaluation must be interrupted |
||||
*/ |
||||
protected boolean checkArrayModel(String currentPath, Object model, EvaluationContextImpl ctx) { |
||||
if (model == null){ |
||||
if (! isUpstreamDefinite()) { |
||||
return false; |
||||
} else { |
||||
throw new PathNotFoundException("The path " + currentPath + " is null"); |
||||
} |
||||
} |
||||
if (!ctx.jsonProvider().isArray(model)) { |
||||
if (! isUpstreamDefinite()) { |
||||
return false; |
||||
} else { |
||||
throw new PathNotFoundException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model)); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,8 @@
|
||||
package com.jayway.jsonpath.internal.path; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.token.PathToken; |
||||
|
||||
public interface EvaluatorDispatcher { |
||||
void evaluate(PathToken token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); |
||||
} |
@ -1,217 +0,0 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path; |
||||
|
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.Utils; |
||||
import com.jayway.jsonpath.internal.function.PathFunction; |
||||
import com.jayway.jsonpath.spi.json.JsonProvider; |
||||
|
||||
import java.util.List; |
||||
|
||||
public abstract class PathToken { |
||||
|
||||
private PathToken prev; |
||||
private PathToken next; |
||||
private Boolean definite = null; |
||||
private Boolean upstreamDefinite = null; |
||||
|
||||
PathToken appendTailToken(PathToken next) { |
||||
this.next = next; |
||||
this.next.prev = this; |
||||
return next; |
||||
} |
||||
|
||||
void handleObjectProperty(String currentPath, Object model, EvaluationContextImpl ctx, List<String> properties) { |
||||
|
||||
if(properties.size() == 1) { |
||||
String property = properties.get(0); |
||||
String evalPath = Utils.concat(currentPath, "['", property, "']"); |
||||
Object propertyVal = readObjectProperty(property, model, ctx); |
||||
if(propertyVal == JsonProvider.UNDEFINED){ |
||||
// Conditions below heavily depend on current token type (and its logic) and are not "universal",
|
||||
// so this code is quite dangerous (I'd rather rewrite it & move to PropertyPathToken and implemented
|
||||
// WildcardPathToken as a dynamic multi prop case of PropertyPathToken).
|
||||
// Better safe than sorry.
|
||||
assert this instanceof PropertyPathToken : "only PropertyPathToken is supported"; |
||||
|
||||
if(isLeaf()) { |
||||
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ |
||||
propertyVal = null; |
||||
} else { |
||||
if(ctx.options().contains(Option.SUPPRESS_EXCEPTIONS) || |
||||
!ctx.options().contains(Option.REQUIRE_PROPERTIES)){ |
||||
return; |
||||
} else { |
||||
throw new PathNotFoundException("No results for path: " + evalPath); |
||||
} |
||||
} |
||||
} else { |
||||
if (! (isUpstreamDefinite() && isTokenDefinite()) && |
||||
!ctx.options().contains(Option.REQUIRE_PROPERTIES) || |
||||
ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)){ |
||||
// If there is some indefiniteness in the path and properties are not required - we'll ignore
|
||||
// absent property. And also in case of exception suppression - so that other path evaluation
|
||||
// branches could be examined.
|
||||
return; |
||||
} else { |
||||
throw new PathNotFoundException("Missing property in path " + evalPath); |
||||
} |
||||
} |
||||
} |
||||
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP; |
||||
if (isLeaf()) { |
||||
ctx.addResult(evalPath, pathRef, propertyVal); |
||||
} |
||||
else { |
||||
next().evaluate(evalPath, pathRef, propertyVal, ctx); |
||||
} |
||||
} else { |
||||
String evalPath = currentPath + "[" + Utils.join(", ", "'", properties) + "]"; |
||||
|
||||
assert isLeaf() : "non-leaf multi props handled elsewhere"; |
||||
|
||||
Object merged = ctx.jsonProvider().createMap(); |
||||
for (String property : properties) { |
||||
Object propertyVal; |
||||
if(hasProperty(property, model, ctx)) { |
||||
propertyVal = readObjectProperty(property, model, ctx); |
||||
if(propertyVal == JsonProvider.UNDEFINED){ |
||||
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) { |
||||
propertyVal = null; |
||||
} else { |
||||
continue; |
||||
} |
||||
} |
||||
} else { |
||||
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ |
||||
propertyVal = null; |
||||
} else if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) { |
||||
throw new PathNotFoundException("Missing property in path " + evalPath); |
||||
} else { |
||||
continue; |
||||
} |
||||
} |
||||
ctx.jsonProvider().setProperty(merged, property, propertyVal); |
||||
} |
||||
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, properties) : PathRef.NO_OP; |
||||
ctx.addResult(evalPath, pathRef, merged); |
||||
} |
||||
} |
||||
|
||||
private static boolean hasProperty(String property, Object model, EvaluationContextImpl ctx) { |
||||
return ctx.jsonProvider().getPropertyKeys(model).contains(property); |
||||
} |
||||
|
||||
private static Object readObjectProperty(String property, Object model, EvaluationContextImpl ctx) { |
||||
return ctx.jsonProvider().getMapValue(model, property); |
||||
} |
||||
|
||||
|
||||
protected void handleArrayIndex(int index, String currentPath, Object model, EvaluationContextImpl ctx) { |
||||
String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]"); |
||||
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP; |
||||
try { |
||||
Object evalHit = ctx.jsonProvider().getArrayIndex(model, index); |
||||
if (isLeaf()) { |
||||
ctx.addResult(evalPath, pathRef, evalHit); |
||||
} else { |
||||
next().evaluate(evalPath, pathRef, evalHit, ctx); |
||||
} |
||||
} catch (IndexOutOfBoundsException e) { |
||||
} |
||||
} |
||||
|
||||
PathToken prev(){ |
||||
return prev; |
||||
} |
||||
|
||||
PathToken next() { |
||||
if (isLeaf()) { |
||||
throw new IllegalStateException("Current path token is a leaf"); |
||||
} |
||||
return next; |
||||
} |
||||
|
||||
boolean isLeaf() { |
||||
return next == null; |
||||
} |
||||
|
||||
boolean isRoot() { |
||||
return prev == null; |
||||
} |
||||
|
||||
boolean isUpstreamDefinite() { |
||||
if (upstreamDefinite == null) { |
||||
upstreamDefinite = isRoot() || prev.isTokenDefinite() && prev.isUpstreamDefinite(); |
||||
} |
||||
return upstreamDefinite; |
||||
} |
||||
|
||||
public int getTokenCount() { |
||||
int cnt = 1; |
||||
PathToken token = this; |
||||
|
||||
while (!token.isLeaf()){ |
||||
token = token.next(); |
||||
cnt++; |
||||
} |
||||
return cnt; |
||||
} |
||||
|
||||
public boolean isPathDefinite() { |
||||
if(definite != null){ |
||||
return definite.booleanValue(); |
||||
} |
||||
boolean isDefinite = isTokenDefinite(); |
||||
if (isDefinite && !isLeaf()) { |
||||
isDefinite = next.isPathDefinite(); |
||||
} |
||||
definite = isDefinite; |
||||
return isDefinite; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
if (isLeaf()) { |
||||
return getPathFragment(); |
||||
} else { |
||||
return getPathFragment() + next().toString(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return toString().hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
return super.equals(obj); |
||||
} |
||||
|
||||
public void invoke(PathFunction pathFunction, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
ctx.addResult(currentPath, parent, pathFunction.invoke(currentPath, parent, model, ctx)); |
||||
} |
||||
|
||||
public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); |
||||
|
||||
public abstract boolean isTokenDefinite(); |
||||
|
||||
protected abstract String getPathFragment(); |
||||
|
||||
} |
@ -1,5 +1,7 @@
|
||||
package com.jayway.jsonpath.internal.path; |
||||
|
||||
import com.jayway.jsonpath.internal.path.token.PathToken; |
||||
|
||||
public interface PathTokenAppender { |
||||
PathTokenAppender appendPathToken(PathToken next); |
||||
} |
||||
|
@ -1,206 +0,0 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path; |
||||
|
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.spi.json.JsonProvider; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
public class ScanPathToken extends PathToken { |
||||
|
||||
ScanPathToken() { |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
|
||||
PathToken pt = next(); |
||||
|
||||
walk(pt, currentPath, parent, model, ctx, createScanPredicate(pt, ctx)); |
||||
} |
||||
|
||||
public static void walk(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, Predicate predicate) { |
||||
if (ctx.jsonProvider().isMap(model)) { |
||||
walkObject(pt, currentPath, parent, model, ctx, predicate); |
||||
} else if (ctx.jsonProvider().isArray(model)) { |
||||
walkArray(pt, currentPath, parent, model, ctx, predicate); |
||||
} |
||||
} |
||||
|
||||
public static void walkArray(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, Predicate predicate) { |
||||
|
||||
if (predicate.matches(model)) { |
||||
if (pt.isLeaf()) { |
||||
pt.evaluate(currentPath, parent, model, ctx); |
||||
} else { |
||||
PathToken next = pt.next(); |
||||
Iterable<?> models = ctx.jsonProvider().toIterable(model); |
||||
int idx = 0; |
||||
for (Object evalModel : models) { |
||||
String evalPath = currentPath + "[" + idx + "]"; |
||||
next.evaluate(evalPath, parent, evalModel, ctx); |
||||
idx++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
Iterable<?> models = ctx.jsonProvider().toIterable(model); |
||||
int idx = 0; |
||||
for (Object evalModel : models) { |
||||
String evalPath = currentPath + "[" + idx + "]"; |
||||
walk(pt, evalPath, PathRef.create(model, idx), evalModel, ctx, predicate); |
||||
idx++; |
||||
} |
||||
} |
||||
|
||||
public static void walkObject(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, Predicate predicate) { |
||||
|
||||
if (predicate.matches(model)) { |
||||
pt.evaluate(currentPath, parent, model, ctx); |
||||
} |
||||
Collection<String> properties = ctx.jsonProvider().getPropertyKeys(model); |
||||
|
||||
for (String property : properties) { |
||||
String evalPath = currentPath + "['" + property + "']"; |
||||
Object propertyModel = ctx.jsonProvider().getMapValue(model, property); |
||||
if (propertyModel != JsonProvider.UNDEFINED) { |
||||
walk(pt, evalPath, PathRef.create(model, property), propertyModel, ctx, predicate); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static Predicate createScanPredicate(final PathToken target, final EvaluationContextImpl ctx) { |
||||
if (target instanceof PropertyPathToken) { |
||||
return new PropertyPathTokenPredicate(target, ctx); |
||||
} else if (target instanceof ArrayPathToken) { |
||||
return new ArrayPathTokenPredicate(ctx); |
||||
} else if (target instanceof WildcardPathToken) { |
||||
return new WildcardPathTokenPredicate(); |
||||
} else if (target instanceof PredicatePathToken) { |
||||
return new FilterPathTokenPredicate(target, ctx); |
||||
} else { |
||||
return FALSE_PREDICATE; |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return ".."; |
||||
} |
||||
|
||||
private interface Predicate { |
||||
boolean matches(Object model); |
||||
} |
||||
|
||||
private static final Predicate FALSE_PREDICATE = new Predicate() { |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
private static final class FilterPathTokenPredicate implements Predicate { |
||||
private final EvaluationContextImpl ctx; |
||||
private PredicatePathToken predicatePathToken; |
||||
|
||||
private FilterPathTokenPredicate(PathToken target, EvaluationContextImpl ctx) { |
||||
this.ctx = ctx; |
||||
predicatePathToken = (PredicatePathToken) target; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration(), ctx); |
||||
} |
||||
} |
||||
|
||||
private static final class WildcardPathTokenPredicate implements Predicate { |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
private static final class ArrayPathTokenPredicate implements Predicate { |
||||
private final EvaluationContextImpl ctx; |
||||
|
||||
private ArrayPathTokenPredicate(EvaluationContextImpl ctx) { |
||||
this.ctx = ctx; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return ctx.jsonProvider().isArray(model); |
||||
} |
||||
} |
||||
|
||||
private static final class PropertyPathTokenPredicate implements Predicate { |
||||
private final EvaluationContextImpl ctx; |
||||
private PropertyPathToken propertyPathToken; |
||||
|
||||
private PropertyPathTokenPredicate(PathToken target, EvaluationContextImpl ctx) { |
||||
this.ctx = ctx; |
||||
propertyPathToken = (PropertyPathToken) target; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
|
||||
if (! ctx.jsonProvider().isMap(model)) { |
||||
return false; |
||||
} |
||||
|
||||
//
|
||||
// The commented code below makes it really hard understand, use and predict the result
|
||||
// of deep scanning operations. It might be correct but was decided to be
|
||||
// left out until the behavior of REQUIRE_PROPERTIES is more strictly defined
|
||||
// in a deep scanning scenario. For details read conversation in commit
|
||||
// https://github.com/jayway/JsonPath/commit/1a72fc078deb16995e323442bfb681bd715ce45a#commitcomment-14616092
|
||||
//
|
||||
// if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) {
|
||||
// // Have to require properties defined in path when an indefinite path is evaluated,
|
||||
// // so have to go there and search for it.
|
||||
// return true;
|
||||
// }
|
||||
|
||||
if (! propertyPathToken.isTokenDefinite()) { |
||||
// It's responsibility of PropertyPathToken code to handle indefinite scenario of properties,
|
||||
// so we'll allow it to do its job.
|
||||
return true; |
||||
} |
||||
|
||||
if (propertyPathToken.isLeaf() && ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) { |
||||
// In case of DEFAULT_PATH_LEAF_TO_NULL missing properties is not a problem.
|
||||
return true; |
||||
} |
||||
|
||||
Collection<String> keys = ctx.jsonProvider().getPropertyKeys(model); |
||||
return keys.containsAll(propertyPathToken.getProperties()); |
||||
} |
||||
} |
||||
} |
@ -1,60 +0,0 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path; |
||||
|
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
|
||||
import static java.util.Arrays.asList; |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
public class WildcardPathToken extends PathToken { |
||||
|
||||
WildcardPathToken() { |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
if (ctx.jsonProvider().isMap(model)) { |
||||
for (String property : ctx.jsonProvider().getPropertyKeys(model)) { |
||||
handleObjectProperty(currentPath, model, ctx, asList(property)); |
||||
} |
||||
} else if (ctx.jsonProvider().isArray(model)) { |
||||
for (int idx = 0; idx < ctx.jsonProvider().length(model); idx++) { |
||||
try { |
||||
handleArrayIndex(idx, currentPath, model, ctx); |
||||
} catch (PathNotFoundException p){ |
||||
if(ctx.options().contains(Option.REQUIRE_PROPERTIES)){ |
||||
throw p; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return "[*]"; |
||||
} |
||||
} |
@ -0,0 +1,52 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.*; |
||||
|
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
public class DefaultEvaluator implements EvaluatorDispatcher { |
||||
/** |
||||
* Type safe cache abstraction |
||||
*/ |
||||
protected class EvaluatorCache { |
||||
protected final LinkedHashMap<Class<? extends PathToken>, ITokenEvaluator> evaluators = new LinkedHashMap<Class<? extends PathToken>, ITokenEvaluator>(); |
||||
|
||||
public <P extends PathToken, E extends ITokenEvaluator<P>> void put(final Class<P> tokenClass, final E evaluator) { |
||||
evaluators.put(tokenClass, evaluator); |
||||
} |
||||
|
||||
public <P extends PathToken, E extends ITokenEvaluator<P>> E get(final P token) { |
||||
final Class<? extends PathToken> wantClass = token.getClass(); |
||||
for (final Map.Entry<Class<? extends PathToken>, ITokenEvaluator> pair : evaluators.entrySet()) { |
||||
if (pair.getKey().isAssignableFrom(wantClass)) { |
||||
//noinspection unchecked
|
||||
return (E) pair.getValue(); |
||||
} |
||||
} |
||||
throw new IllegalArgumentException("Unknown PathToken type: " + wantClass.getCanonicalName()); |
||||
} |
||||
} |
||||
|
||||
protected final EvaluatorCache evaluators; |
||||
|
||||
public DefaultEvaluator() { |
||||
evaluators = new EvaluatorCache(); |
||||
evaluators.put(IndexArrayPathToken.class, new IndexArrayTokenEvaluator(this)); |
||||
evaluators.put(SliceArrayPathToken.class, new SliceArrayTokenEvaluator(this)); |
||||
evaluators.put(FunctionPathToken.class, new FunctionTokenEvaluator(this)); |
||||
evaluators.put(PredicatePathToken.class, new PredicateTokenEvaluator(this)); |
||||
evaluators.put(PropertyPathToken.class, new PropertyTokenEvaluator(this)); |
||||
evaluators.put(RootPathToken.class, new RootTokenEvaluator(this)); |
||||
evaluators.put(ScanPathToken.class, new ScanTokenEvaluator(this)); |
||||
evaluators.put(WildcardPathToken.class, new WildcardTokenEvaluator(this)); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final PathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
evaluators.get(token).evaluate(token, currentPath, parent, model, ctx); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.function.PathFunction; |
||||
import com.jayway.jsonpath.internal.function.PathFunctionFactory; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.FunctionPathToken; |
||||
|
||||
public class FunctionTokenEvaluator extends TokenEvaluator<FunctionPathToken> { |
||||
public FunctionTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final FunctionPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
PathFunction pathFunction = PathFunctionFactory.newFunction(token.getFunctionName()); |
||||
Object result = pathFunction.invoke(currentPath, parent, model, ctx); |
||||
ctx.addResult(currentPath, parent, result); |
||||
} |
||||
} |
@ -0,0 +1,9 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.token.PathToken; |
||||
|
||||
public interface ITokenEvaluator<P extends PathToken> { |
||||
void evaluate(P token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); |
||||
} |
@ -0,0 +1,36 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.operation.ArrayIndexOperation; |
||||
import com.jayway.jsonpath.internal.path.token.IndexArrayPathToken; |
||||
|
||||
public class IndexArrayTokenEvaluator extends TokenEvaluator<IndexArrayPathToken> { |
||||
public IndexArrayTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final IndexArrayPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
if (! checkArrayModel(token, currentPath, model, ctx)) |
||||
return; |
||||
evaluateIndexOperation(token, currentPath, parent, model, ctx); |
||||
} |
||||
|
||||
public void evaluateIndexOperation(final IndexArrayPathToken token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
|
||||
if (! checkArrayModel(token, currentPath, model, ctx)) |
||||
return; |
||||
|
||||
final ArrayIndexOperation operation = token.operation(); |
||||
if(operation.isSingleIndexOperation()){ |
||||
handleArrayIndex(token, operation.indexes().get(0), currentPath, model, ctx); |
||||
} else { |
||||
for (Integer index : operation.indexes()) { |
||||
handleArrayIndex(token, index, currentPath, model, ctx); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,43 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.InvalidPathException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.PredicatePathToken; |
||||
|
||||
import static java.lang.String.format; |
||||
|
||||
public class PredicateTokenEvaluator extends TokenEvaluator<PredicatePathToken> { |
||||
public PredicateTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final PredicatePathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
if (ctx.jsonProvider().isMap(model)) { |
||||
if (token.accept(model, ctx.rootDocument(), ctx.configuration(), ctx)) { |
||||
PathRef op = ctx.forUpdate() ? parent : PathRef.NO_OP; |
||||
if (token.isLeaf()) { |
||||
ctx.addResult(currentPath, op, model); |
||||
} else { |
||||
dispatcher.evaluate(token.next(), currentPath, op, model, ctx); |
||||
} |
||||
} |
||||
} else if (ctx.jsonProvider().isArray(model)){ |
||||
int idx = 0; |
||||
Iterable<?> objects = ctx.jsonProvider().toIterable(model); |
||||
|
||||
for (Object idxModel : objects) { |
||||
if (token.accept(idxModel, ctx.rootDocument(), ctx.configuration(), ctx)) { |
||||
handleArrayIndex(token, idx, currentPath, model, ctx); |
||||
} |
||||
idx++; |
||||
} |
||||
} else { |
||||
if (token.isUpstreamDefinite()) { |
||||
throw new InvalidPathException(format("Filter: %s can not be applied to primitives. Current context is: %s", toString(), model)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,50 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.PropertyPathToken; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import static com.jayway.jsonpath.internal.Utils.onlyOneIsTrueNonThrow; |
||||
|
||||
public class PropertyTokenEvaluator extends TokenEvaluator<PropertyPathToken> { |
||||
public PropertyTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final PropertyPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
// Can't assert it in ctor because isLeaf() could be changed later on.
|
||||
assert onlyOneIsTrueNonThrow(token.singlePropertyCase(), token.multiPropertyMergeCase(), token.multiPropertyIterationCase()); |
||||
|
||||
if (!ctx.jsonProvider().isMap(model)) { |
||||
if (! token.isUpstreamDefinite()) { |
||||
return; |
||||
} else { |
||||
String m = model == null ? "null" : model.getClass().getName(); |
||||
|
||||
throw new PathNotFoundException(String.format( |
||||
"Expected to find an object with property %s in path %s but found '%s'. " + |
||||
"This is not a json object according to the JsonProvider: '%s'.", |
||||
token.getPathFragment(), currentPath, m, ctx.configuration().jsonProvider().getClass().getName())); |
||||
} |
||||
} |
||||
|
||||
if (token.singlePropertyCase() || token.multiPropertyMergeCase()) { |
||||
handleObjectProperty(token, currentPath, model, ctx, token.getProperties()); |
||||
return; |
||||
} |
||||
|
||||
assert token.multiPropertyIterationCase(); |
||||
final List<String> currentlyHandledProperty = new ArrayList<String>(1); |
||||
currentlyHandledProperty.add(null); |
||||
for (final String property : token.getProperties()) { |
||||
currentlyHandledProperty.set(0, property); |
||||
handleObjectProperty(token, currentPath, model, ctx, currentlyHandledProperty); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.RootPathToken; |
||||
|
||||
public class RootTokenEvaluator extends TokenEvaluator<RootPathToken> { |
||||
public RootTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final RootPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
if (token.isLeaf()) { |
||||
PathRef op = ctx.forUpdate() ? parent : PathRef.NO_OP; |
||||
ctx.addResult(token.getPathFragment(), op, model); |
||||
} else { |
||||
dispatcher.evaluate(token.next(), token.getPathFragment(), parent, model, ctx); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,95 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.*; |
||||
import com.jayway.jsonpath.internal.path.predicate.*; |
||||
import com.jayway.jsonpath.internal.path.token.*; |
||||
import com.jayway.jsonpath.spi.json.JsonProvider; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
public class ScanTokenEvaluator extends TokenEvaluator<ScanPathToken> { |
||||
public ScanTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final ScanPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
PathToken pt = token.next(); |
||||
|
||||
walk(pt, currentPath, parent, model, ctx, createScanPredicate(pt, ctx)); |
||||
|
||||
} |
||||
|
||||
public void walk(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, PathTokenPredicate predicate) { |
||||
if (ctx.jsonProvider().isMap(model)) { |
||||
walkObject(pt, currentPath, parent, model, ctx, predicate); |
||||
} else if (ctx.jsonProvider().isArray(model)) { |
||||
walkArray(pt, currentPath, parent, model, ctx, predicate); |
||||
} |
||||
} |
||||
|
||||
public void walkArray(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, PathTokenPredicate predicate) { |
||||
|
||||
if (predicate.matches(model)) { |
||||
if (pt.isLeaf()) { |
||||
dispatcher.evaluate(pt, currentPath, parent, model, ctx); |
||||
} else { |
||||
PathToken next = pt.next(); |
||||
Iterable<?> models = ctx.jsonProvider().toIterable(model); |
||||
int idx = 0; |
||||
for (Object evalModel : models) { |
||||
String evalPath = currentPath + "[" + idx + "]"; |
||||
dispatcher.evaluate(next, evalPath, parent, evalModel, ctx); |
||||
idx++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
Iterable<?> models = ctx.jsonProvider().toIterable(model); |
||||
int idx = 0; |
||||
for (Object evalModel : models) { |
||||
String evalPath = currentPath + "[" + idx + "]"; |
||||
walk(pt, evalPath, PathRef.create(model, idx), evalModel, ctx, predicate); |
||||
idx++; |
||||
} |
||||
} |
||||
|
||||
public void walkObject(PathToken pt, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx, PathTokenPredicate predicate) { |
||||
|
||||
if (predicate.matches(model)) { |
||||
dispatcher.evaluate(pt, currentPath, parent, model, ctx); |
||||
} |
||||
Collection<String> properties = ctx.jsonProvider().getPropertyKeys(model); |
||||
|
||||
for (String property : properties) { |
||||
String evalPath = currentPath + "['" + property + "']"; |
||||
Object propertyModel = ctx.jsonProvider().getMapValue(model, property); |
||||
if (propertyModel != JsonProvider.UNDEFINED) { |
||||
walk(pt, evalPath, PathRef.create(model, property), propertyModel, ctx, predicate); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static PathTokenPredicate createScanPredicate(final PathToken target, final EvaluationContextImpl ctx) { |
||||
if (target instanceof PropertyPathToken) { |
||||
return new PropertyPathTokenPredicate(target, ctx); |
||||
} else if (target instanceof ArrayPathToken) { |
||||
return new ArrayPathTokenPredicate(ctx); |
||||
} else if (target instanceof WildcardPathToken) { |
||||
return new WildcardPathTokenPredicate(); |
||||
} else if (target instanceof PredicatePathToken) { |
||||
return new FilterPathTokenPredicate(target, ctx); |
||||
} else { |
||||
return FALSE_PREDICATE; |
||||
} |
||||
} |
||||
|
||||
private static final PathTokenPredicate FALSE_PREDICATE = new PathTokenPredicate() { |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return false; |
||||
} |
||||
}; |
||||
} |
@ -0,0 +1,99 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.*; |
||||
import com.jayway.jsonpath.internal.path.operation.ArraySliceOperation; |
||||
import com.jayway.jsonpath.internal.path.token.SliceArrayPathToken; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class SliceArrayTokenEvaluator extends TokenEvaluator<SliceArrayPathToken> { |
||||
private static final Logger logger = LoggerFactory.getLogger(SliceArrayTokenEvaluator.class); |
||||
|
||||
public SliceArrayTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final SliceArrayPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
if (! checkArrayModel(token, currentPath, model, ctx)) |
||||
return; |
||||
evaluateSliceOperation(token, currentPath, parent, model, ctx); |
||||
} |
||||
|
||||
public void evaluateSliceOperation(final SliceArrayPathToken token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
|
||||
if (! checkArrayModel(token, currentPath, model, ctx)) |
||||
return; |
||||
|
||||
final ArraySliceOperation operation = token.operation(); |
||||
switch (operation.operation()) { |
||||
case SLICE_FROM: |
||||
sliceFrom(token, currentPath, parent, model, ctx); |
||||
break; |
||||
case SLICE_BETWEEN: |
||||
sliceBetween(token, currentPath, parent, model, ctx); |
||||
break; |
||||
case SLICE_TO: |
||||
sliceTo(token, currentPath, parent, model, ctx); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public void sliceFrom(final SliceArrayPathToken token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
int length = ctx.jsonProvider().length(model); |
||||
int from = token.operation().from(); |
||||
if (from < 0) { |
||||
//calculate slice start from array length
|
||||
from = length + from; |
||||
} |
||||
from = Math.max(0, from); |
||||
|
||||
logger.debug("Slice from index on array with length: {}. From index: {} to: {}. Input: {}", length, from, length - 1, toString()); |
||||
|
||||
if (length == 0 || from >= length) { |
||||
return; |
||||
} |
||||
for (int i = from; i < length; i++) { |
||||
handleArrayIndex(token, i, currentPath, model, ctx); |
||||
} |
||||
} |
||||
|
||||
public void sliceBetween(final SliceArrayPathToken token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
int length = ctx.jsonProvider().length(model); |
||||
int from = token.operation().from(); |
||||
int to = token.operation().to(); |
||||
|
||||
to = Math.min(length, to); |
||||
|
||||
if (from >= to || length == 0) { |
||||
return; |
||||
} |
||||
|
||||
logger.debug("Slice between indexes on array with length: {}. From index: {} to: {}. Input: {}", length, from, to, toString()); |
||||
|
||||
for (int i = from; i < to; i++) { |
||||
handleArrayIndex(token, i, currentPath, model, ctx); |
||||
} |
||||
} |
||||
|
||||
public void sliceTo(final SliceArrayPathToken token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
int length = ctx.jsonProvider().length(model); |
||||
if (length == 0) { |
||||
return; |
||||
} |
||||
int to = token.operation().to(); |
||||
if (to < 0) { |
||||
//calculate slice end from array length
|
||||
to = length + to; |
||||
} |
||||
to = Math.min(length, to); |
||||
|
||||
logger.debug("Slice to index on array with length: {}. From index: 0 to: {}. Input: {}", length, to, toString()); |
||||
|
||||
for (int i = 0; i < to; i++) { |
||||
handleArrayIndex(token, i, currentPath, model, ctx); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,156 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.InvalidPathException; |
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.Utils; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.ArrayPathToken; |
||||
import com.jayway.jsonpath.internal.path.token.PathToken; |
||||
import com.jayway.jsonpath.internal.path.token.PropertyPathToken; |
||||
import com.jayway.jsonpath.spi.json.JsonProvider; |
||||
|
||||
import java.util.List; |
||||
|
||||
import static java.lang.String.format; |
||||
|
||||
public abstract class TokenEvaluator<P extends PathToken> implements ITokenEvaluator<P> { |
||||
|
||||
protected final EvaluatorDispatcher dispatcher; |
||||
|
||||
public TokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
this.dispatcher = dispatcher; |
||||
} |
||||
|
||||
public abstract void evaluate(final P token, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); |
||||
|
||||
/** |
||||
* Check if model is non-null and array. |
||||
* @param currentPath |
||||
* @param model |
||||
* @param ctx |
||||
* @return false if current evaluation call must be skipped, true otherwise |
||||
* @throws PathNotFoundException if model is null and evaluation must be interrupted |
||||
* @throws InvalidPathException if model is not an array and evaluation must be interrupted |
||||
*/ |
||||
public boolean checkArrayModel(final ArrayPathToken token, String currentPath, Object model, EvaluationContextImpl ctx) { |
||||
if (model == null){ |
||||
if (! token.isUpstreamDefinite()) { |
||||
return false; |
||||
} else { |
||||
throw new PathNotFoundException("The path " + currentPath + " is null"); |
||||
} |
||||
} |
||||
if (!ctx.jsonProvider().isArray(model)) { |
||||
if (! token.isUpstreamDefinite()) { |
||||
return false; |
||||
} else { |
||||
throw new PathNotFoundException(format("Filter: %s can only be applied to arrays. Current context is: %s", toString(), model)); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
|
||||
public void handleObjectProperty(final P token, String currentPath, Object model, EvaluationContextImpl ctx, List<String> properties) { |
||||
|
||||
if(properties.size() == 1) { |
||||
String property = properties.get(0); |
||||
String evalPath = Utils.concat(currentPath, "['", property, "']"); |
||||
Object propertyVal = readObjectProperty(property, model, ctx); |
||||
if(propertyVal == JsonProvider.UNDEFINED){ |
||||
// Conditions below heavily depend on current token type (and its logic) and are not "universal",
|
||||
// so this code is quite dangerous (I'd rather rewrite it & move to PropertyPathToken and implemented
|
||||
// WildcardPathToken as a dynamic multi prop case of PropertyPathToken).
|
||||
// Better safe than sorry.
|
||||
assert token instanceof PropertyPathToken : "only PropertyPathToken is supported"; |
||||
|
||||
if(token.isLeaf()) { |
||||
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ |
||||
propertyVal = null; |
||||
} else { |
||||
if(ctx.options().contains(Option.SUPPRESS_EXCEPTIONS) || |
||||
!ctx.options().contains(Option.REQUIRE_PROPERTIES)){ |
||||
return; |
||||
} else { |
||||
throw new PathNotFoundException("No results for path: " + evalPath); |
||||
} |
||||
} |
||||
} else { |
||||
if (! (token.isUpstreamDefinite() && token.isTokenDefinite()) && |
||||
!ctx.options().contains(Option.REQUIRE_PROPERTIES) || |
||||
ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)){ |
||||
// If there is some indefiniteness in the path and properties are not required - we'll ignore
|
||||
// absent property. And also in case of exception suppression - so that other path evaluation
|
||||
// branches could be examined.
|
||||
return; |
||||
} else { |
||||
throw new PathNotFoundException("Missing property in path " + evalPath); |
||||
} |
||||
} |
||||
} |
||||
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, property) : PathRef.NO_OP; |
||||
if (token.isLeaf()) { |
||||
ctx.addResult(evalPath, pathRef, propertyVal); |
||||
} |
||||
else { |
||||
dispatcher.evaluate(token.next(), evalPath, pathRef, propertyVal, ctx); |
||||
} |
||||
} else { |
||||
String evalPath = currentPath + "[" + Utils.join(", ", "'", properties) + "]"; |
||||
|
||||
assert token.isLeaf() : "non-leaf multi props handled elsewhere"; |
||||
|
||||
Object merged = ctx.jsonProvider().createMap(); |
||||
for (String property : properties) { |
||||
Object propertyVal; |
||||
if(hasProperty(property, model, ctx)) { |
||||
propertyVal = readObjectProperty(property, model, ctx); |
||||
if(propertyVal == JsonProvider.UNDEFINED){ |
||||
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) { |
||||
propertyVal = null; |
||||
} else { |
||||
continue; |
||||
} |
||||
} |
||||
} else { |
||||
if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ |
||||
propertyVal = null; |
||||
} else if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) { |
||||
throw new PathNotFoundException("Missing property in path " + evalPath); |
||||
} else { |
||||
continue; |
||||
} |
||||
} |
||||
ctx.jsonProvider().setProperty(merged, property, propertyVal); |
||||
} |
||||
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, properties) : PathRef.NO_OP; |
||||
ctx.addResult(evalPath, pathRef, merged); |
||||
} |
||||
} |
||||
|
||||
private static boolean hasProperty(String property, Object model, EvaluationContextImpl ctx) { |
||||
return ctx.jsonProvider().getPropertyKeys(model).contains(property); |
||||
} |
||||
|
||||
private static Object readObjectProperty(String property, Object model, EvaluationContextImpl ctx) { |
||||
return ctx.jsonProvider().getMapValue(model, property); |
||||
} |
||||
|
||||
|
||||
public void handleArrayIndex(final P token, int index, String currentPath, Object model, EvaluationContextImpl ctx) { |
||||
String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]"); |
||||
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP; |
||||
try { |
||||
Object evalHit = ctx.jsonProvider().getArrayIndex(model, index); |
||||
if (token.isLeaf()) { |
||||
ctx.addResult(evalPath, pathRef, evalHit); |
||||
} else { |
||||
dispatcher.evaluate(token.next(), evalPath, pathRef, evalHit, ctx); |
||||
} |
||||
} catch (IndexOutOfBoundsException e) { |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,35 @@
|
||||
package com.jayway.jsonpath.internal.path.eval; |
||||
|
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.EvaluatorDispatcher; |
||||
import com.jayway.jsonpath.internal.path.token.WildcardPathToken; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
public class WildcardTokenEvaluator extends TokenEvaluator<WildcardPathToken> { |
||||
public WildcardTokenEvaluator(final EvaluatorDispatcher dispatcher) { |
||||
super(dispatcher); |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(final WildcardPathToken token, final String currentPath, final PathRef parent, final Object model, final EvaluationContextImpl ctx) { |
||||
if (ctx.jsonProvider().isMap(model)) { |
||||
for (String property : ctx.jsonProvider().getPropertyKeys(model)) { |
||||
handleObjectProperty(token, currentPath, model, ctx, Collections.singletonList(property)); |
||||
} |
||||
} else if (ctx.jsonProvider().isArray(model)) { |
||||
for (int idx = 0; idx < ctx.jsonProvider().length(model); idx++) { |
||||
try { |
||||
handleArrayIndex(token, idx, currentPath, model, ctx); |
||||
} catch (PathNotFoundException p){ |
||||
if(ctx.options().contains(Option.REQUIRE_PROPERTIES)){ |
||||
throw p; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,4 +1,4 @@
|
||||
package com.jayway.jsonpath.internal.path; |
||||
package com.jayway.jsonpath.internal.path.operation; |
||||
|
||||
import com.jayway.jsonpath.InvalidPathException; |
||||
import com.jayway.jsonpath.internal.Utils; |
@ -0,0 +1,12 @@
|
||||
package com.jayway.jsonpath.internal.path.operation; |
||||
|
||||
public class SliceBetweenOperation extends ArraySliceOperation { |
||||
protected SliceBetweenOperation(final Integer from, final Integer to) { |
||||
super(from, to); |
||||
} |
||||
|
||||
@Override |
||||
public Operation operation() { |
||||
return Operation.SLICE_BETWEEN; |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
package com.jayway.jsonpath.internal.path.operation; |
||||
|
||||
public class SliceFromOperation extends ArraySliceOperation { |
||||
protected SliceFromOperation(Integer from) { |
||||
super(from, null); |
||||
} |
||||
|
||||
@Override |
||||
public Operation operation() { |
||||
return Operation.SLICE_FROM; |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
package com.jayway.jsonpath.internal.path.operation; |
||||
|
||||
public class SliceToOperation extends ArraySliceOperation { |
||||
protected SliceToOperation(final Integer to) { |
||||
super(null, to); |
||||
} |
||||
|
||||
@Override |
||||
public Operation operation() { |
||||
return Operation.SLICE_TO; |
||||
} |
||||
} |
@ -0,0 +1,16 @@
|
||||
package com.jayway.jsonpath.internal.path.predicate; |
||||
|
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
|
||||
public final class ArrayPathTokenPredicate implements PathTokenPredicate { |
||||
private final EvaluationContextImpl ctx; |
||||
|
||||
public ArrayPathTokenPredicate(EvaluationContextImpl ctx) { |
||||
this.ctx = ctx; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return ctx.jsonProvider().isArray(model); |
||||
} |
||||
} |
@ -0,0 +1,20 @@
|
||||
package com.jayway.jsonpath.internal.path.predicate; |
||||
|
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.token.PathToken; |
||||
import com.jayway.jsonpath.internal.path.token.PredicatePathToken; |
||||
|
||||
public final class FilterPathTokenPredicate implements PathTokenPredicate { |
||||
private final EvaluationContextImpl ctx; |
||||
private PredicatePathToken predicatePathToken; |
||||
|
||||
public FilterPathTokenPredicate(PathToken target, EvaluationContextImpl ctx) { |
||||
this.ctx = ctx; |
||||
predicatePathToken = (PredicatePathToken) target; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration(), ctx); |
||||
} |
||||
} |
@ -0,0 +1,5 @@
|
||||
package com.jayway.jsonpath.internal.path.predicate; |
||||
|
||||
public interface PathTokenPredicate { |
||||
boolean matches(Object model); |
||||
} |
@ -0,0 +1,52 @@
|
||||
package com.jayway.jsonpath.internal.path.predicate; |
||||
|
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
import com.jayway.jsonpath.internal.path.token.PathToken; |
||||
import com.jayway.jsonpath.internal.path.token.PropertyPathToken; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
public final class PropertyPathTokenPredicate implements PathTokenPredicate { |
||||
private final EvaluationContextImpl ctx; |
||||
private PropertyPathToken propertyPathToken; |
||||
|
||||
public PropertyPathTokenPredicate(PathToken target, EvaluationContextImpl ctx) { |
||||
this.ctx = ctx; |
||||
propertyPathToken = (PropertyPathToken) target; |
||||
} |
||||
|
||||
@Override |
||||
public boolean matches(Object model) { |
||||
if (! ctx.jsonProvider().isMap(model)) { |
||||
return false; |
||||
} |
||||
|
||||
//
|
||||
// The commented code below makes it really hard understand, use and predict the result
|
||||
// of deep scanning operations. It might be correct but was decided to be
|
||||
// left out until the behavior of REQUIRE_PROPERTIES is more strictly defined
|
||||
// in a deep scanning scenario. For details read conversation in commit
|
||||
// https://github.com/jayway/JsonPath/commit/1a72fc078deb16995e323442bfb681bd715ce45a#commitcomment-14616092
|
||||
//
|
||||
// if (ctx.options().contains(Option.REQUIRE_PROPERTIES)) {
|
||||
// // Have to require properties defined in path when an indefinite path is evaluated,
|
||||
// // so have to go there and search for it.
|
||||
// return true;
|
||||
// }
|
||||
|
||||
if (! propertyPathToken.isTokenDefinite()) { |
||||
// It's responsibility of PropertyPathToken code to handle indefinite scenario of properties,
|
||||
// so we'll allow it to do its job.
|
||||
return true; |
||||
} |
||||
|
||||
if (propertyPathToken.isLeaf() && ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)) { |
||||
// In case of DEFAULT_PATH_LEAF_TO_NULL missing properties is not a problem.
|
||||
return true; |
||||
} |
||||
|
||||
Collection<String> keys = ctx.jsonProvider().getPropertyKeys(model); |
||||
return keys.containsAll(propertyPathToken.getProperties()); |
||||
} |
||||
} |
@ -0,0 +1,8 @@
|
||||
package com.jayway.jsonpath.internal.path.predicate; |
||||
|
||||
public final class WildcardPathTokenPredicate implements PathTokenPredicate { |
||||
@Override |
||||
public boolean matches(Object model) { |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path.token; |
||||
|
||||
/** |
||||
* Placeholder common ancestor for {@link IndexArrayPathToken} |
||||
* and {@link SliceArrayPathToken} |
||||
*/ |
||||
public abstract class ArrayPathToken extends PathToken { |
||||
} |
@ -0,0 +1,27 @@
|
||||
package com.jayway.jsonpath.internal.path.token; |
||||
|
||||
import com.jayway.jsonpath.internal.path.operation.ArrayIndexOperation; |
||||
|
||||
public class IndexArrayPathToken extends ArrayPathToken { |
||||
protected final ArrayIndexOperation arrayIndexOperation; |
||||
|
||||
public IndexArrayPathToken(final ArrayIndexOperation arrayIndexOperation) { |
||||
super(); |
||||
if (arrayIndexOperation == null) throw new IllegalArgumentException(); |
||||
this.arrayIndexOperation = arrayIndexOperation; |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return arrayIndexOperation.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return arrayIndexOperation.isSingleIndexOperation(); |
||||
} |
||||
|
||||
public ArrayIndexOperation operation() { |
||||
return arrayIndexOperation; |
||||
} |
||||
} |
@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path.token; |
||||
|
||||
import com.jayway.jsonpath.internal.PathRef; |
||||
import com.jayway.jsonpath.internal.function.PathFunction; |
||||
import com.jayway.jsonpath.internal.path.EvaluationContextImpl; |
||||
|
||||
public abstract class PathToken { |
||||
|
||||
private PathToken prev; |
||||
private PathToken next; |
||||
private Boolean definite = null; |
||||
private Boolean upstreamDefinite = null; |
||||
|
||||
public PathToken appendTailToken(PathToken next) { |
||||
this.next = next; |
||||
this.next.prev = this; |
||||
return next; |
||||
} |
||||
|
||||
public PathToken prev(){ |
||||
return prev; |
||||
} |
||||
|
||||
public PathToken next() { |
||||
if (isLeaf()) { |
||||
throw new IllegalStateException("Current path token is a leaf"); |
||||
} |
||||
return next; |
||||
} |
||||
|
||||
public boolean isLeaf() { |
||||
return next == null; |
||||
} |
||||
|
||||
public boolean isRoot() { |
||||
return prev == null; |
||||
} |
||||
|
||||
public boolean isUpstreamDefinite() { |
||||
if (upstreamDefinite == null) { |
||||
upstreamDefinite = isRoot() || prev.isTokenDefinite() && prev.isUpstreamDefinite(); |
||||
} |
||||
return upstreamDefinite; |
||||
} |
||||
|
||||
public int getTokenCount() { |
||||
int cnt = 1; |
||||
PathToken token = this; |
||||
|
||||
while (!token.isLeaf()){ |
||||
token = token.next(); |
||||
cnt++; |
||||
} |
||||
return cnt; |
||||
} |
||||
|
||||
public boolean isPathDefinite() { |
||||
if(definite != null){ |
||||
return definite.booleanValue(); |
||||
} |
||||
boolean isDefinite = isTokenDefinite(); |
||||
if (isDefinite && !isLeaf()) { |
||||
isDefinite = next.isPathDefinite(); |
||||
} |
||||
definite = isDefinite; |
||||
return isDefinite; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
if (isLeaf()) { |
||||
return getPathFragment(); |
||||
} else { |
||||
return getPathFragment() + next().toString(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return toString().hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
return super.equals(obj); |
||||
} |
||||
|
||||
public void invoke(PathFunction pathFunction, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
ctx.addResult(currentPath, parent, pathFunction.invoke(currentPath, parent, model, ctx)); |
||||
} |
||||
|
||||
public abstract boolean isTokenDefinite(); |
||||
|
||||
protected abstract String getPathFragment(); |
||||
|
||||
} |
@ -0,0 +1,35 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path.token; |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
public class ScanPathToken extends PathToken { |
||||
|
||||
public ScanPathToken() { |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return ".."; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,27 @@
|
||||
package com.jayway.jsonpath.internal.path.token; |
||||
|
||||
import com.jayway.jsonpath.internal.path.operation.ArraySliceOperation; |
||||
|
||||
public class SliceArrayPathToken extends ArrayPathToken { |
||||
protected final ArraySliceOperation arraySliceOperation; |
||||
|
||||
public SliceArrayPathToken(final ArraySliceOperation arraySliceOperation) { |
||||
super(); |
||||
if (arraySliceOperation == null) throw new IllegalArgumentException(); |
||||
this.arraySliceOperation = arraySliceOperation; |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return arraySliceOperation.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return false; |
||||
} |
||||
|
||||
public ArraySliceOperation operation() { |
||||
return arraySliceOperation; |
||||
} |
||||
} |
@ -0,0 +1,34 @@
|
||||
/* |
||||
* Copyright 2011 the original author or authors. |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.jayway.jsonpath.internal.path.token; |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
public class WildcardPathToken extends PathToken { |
||||
|
||||
public WildcardPathToken() { |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return "[*]"; |
||||
} |
||||
} |
Loading…
Reference in new issue