Browse Source
Split ValueNode into a separate class to avoid possible race-condition in class loading. Modified ArrayPathToken to be super-class of ArrayIndex/ArraySlice token because IntelliJ warned about null pointer exception possibilities. Removed redundant boolean checks. Removed use of StringBuffer -> StringBuilder. Removed dangling ;pull/416/head
Elias Ross
7 years ago
23 changed files with 893 additions and 838 deletions
@ -0,0 +1,662 @@
|
||||
package com.jayway.jsonpath.internal.filter; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import com.jayway.jsonpath.Configuration; |
||||
import com.jayway.jsonpath.JsonPathException; |
||||
import com.jayway.jsonpath.Option; |
||||
import com.jayway.jsonpath.PathNotFoundException; |
||||
import com.jayway.jsonpath.Predicate; |
||||
import com.jayway.jsonpath.internal.Path; |
||||
import com.jayway.jsonpath.internal.Utils; |
||||
import com.jayway.jsonpath.internal.path.PathCompiler; |
||||
import com.jayway.jsonpath.internal.path.PredicateContextImpl; |
||||
import com.jayway.jsonpath.spi.json.JsonProvider; |
||||
import net.minidev.json.parser.JSONParser; |
||||
import net.minidev.json.parser.ParseException; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** |
||||
* Moved these nodes out of the ValueNode abstract class. |
||||
* This is to avoid this possible issue: |
||||
* |
||||
* Classes that refer to their own subclasses in their static initializers or in static fields. |
||||
* Such references can cause JVM-level deadlocks in multithreaded environment, when |
||||
* one thread tries to load superclass and another thread tries to load subclass at the same time. |
||||
*/ |
||||
public interface ValueNodes { |
||||
|
||||
NullNode NULL_NODE = new NullNode(); |
||||
BooleanNode TRUE = new BooleanNode("true"); |
||||
BooleanNode FALSE = new BooleanNode("false"); |
||||
UndefinedNode UNDEFINED = new UndefinedNode(); |
||||
|
||||
//----------------------------------------------------
|
||||
//
|
||||
// ValueNode Implementations
|
||||
//
|
||||
//----------------------------------------------------
|
||||
class PatternNode extends ValueNode { |
||||
private final String pattern; |
||||
private final Pattern compiledPattern; |
||||
|
||||
PatternNode(CharSequence charSequence) { |
||||
String tmp = charSequence.toString(); |
||||
int begin = tmp.indexOf('/'); |
||||
int end = tmp.lastIndexOf('/'); |
||||
int flags = tmp.endsWith("/i") ? Pattern.CASE_INSENSITIVE : 0; |
||||
this.pattern = tmp.substring(begin + 1, end); |
||||
this.compiledPattern = Pattern.compile(pattern, flags); |
||||
} |
||||
|
||||
PatternNode(Pattern pattern) { |
||||
this.pattern = pattern.pattern(); |
||||
this.compiledPattern = pattern; |
||||
} |
||||
|
||||
|
||||
Pattern getCompiledPattern() { |
||||
return compiledPattern; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Void.TYPE; |
||||
} |
||||
|
||||
public boolean isPatternNode() { |
||||
return true; |
||||
} |
||||
|
||||
public PatternNode asPatternNode() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
|
||||
String flags = ""; |
||||
if((compiledPattern.flags() & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE){ |
||||
flags = "i"; |
||||
} |
||||
if(!pattern.startsWith("/")){ |
||||
return "/" + pattern + "/" + flags; |
||||
} else { |
||||
return pattern; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof PatternNode)) return false; |
||||
|
||||
PatternNode that = (PatternNode) o; |
||||
|
||||
return !(compiledPattern != null ? !compiledPattern.equals(that.compiledPattern) : that.compiledPattern != null); |
||||
|
||||
} |
||||
} |
||||
|
||||
class JsonNode extends ValueNode { |
||||
private final Object json; |
||||
private final boolean parsed; |
||||
|
||||
JsonNode(CharSequence charSequence) { |
||||
json = charSequence.toString(); |
||||
parsed = false; |
||||
} |
||||
|
||||
JsonNode(Object parsedJson) { |
||||
json = parsedJson; |
||||
parsed = true; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
if(isArray(ctx)) return List.class; |
||||
else if(isMap(ctx)) return Map.class; |
||||
else if(parse(ctx) instanceof Number) return Number.class; |
||||
else if(parse(ctx) instanceof String) return String.class; |
||||
else if(parse(ctx) instanceof Boolean) return Boolean.class; |
||||
else return Void.class; |
||||
} |
||||
|
||||
public boolean isJsonNode() { |
||||
return true; |
||||
} |
||||
|
||||
public JsonNode asJsonNode() { |
||||
return this; |
||||
} |
||||
|
||||
public ValueNode asValueListNode(Predicate.PredicateContext ctx){ |
||||
if(!isArray(ctx)){ |
||||
return UNDEFINED; |
||||
} else { |
||||
return new ValueListNode(Collections.unmodifiableList((List) parse(ctx))); |
||||
} |
||||
} |
||||
|
||||
public Object parse(Predicate.PredicateContext ctx){ |
||||
try { |
||||
return parsed ? json : new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json.toString()); |
||||
} catch (ParseException e) { |
||||
throw new IllegalArgumentException(e); |
||||
} |
||||
} |
||||
|
||||
public boolean isParsed() { |
||||
return parsed; |
||||
} |
||||
|
||||
public Object getJson() { |
||||
return json; |
||||
} |
||||
|
||||
public boolean isArray(Predicate.PredicateContext ctx) { |
||||
return parse(ctx) instanceof List; |
||||
} |
||||
|
||||
public boolean isMap(Predicate.PredicateContext ctx) { |
||||
return parse(ctx) instanceof Map; |
||||
} |
||||
|
||||
public int length(Predicate.PredicateContext ctx) { |
||||
return isArray(ctx) ? ((List<?>) parse(ctx)).size() : -1; |
||||
} |
||||
|
||||
public boolean isEmpty(Predicate.PredicateContext ctx) { |
||||
if (isArray(ctx) || isMap(ctx)) return ((Collection<?>) parse(ctx)).size() == 0; |
||||
else if((parse(ctx) instanceof String)) return ((String)parse(ctx)).length() == 0; |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return json.toString(); |
||||
} |
||||
|
||||
public boolean equals(JsonNode jsonNode, Predicate.PredicateContext ctx) { |
||||
if (this == jsonNode) return true; |
||||
return !(json != null ? !json.equals(jsonNode.parse(ctx)) : jsonNode.json != null); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof JsonNode)) return false; |
||||
|
||||
JsonNode jsonNode = (JsonNode) o; |
||||
|
||||
return !(json != null ? !json.equals(jsonNode.json) : jsonNode.json != null); |
||||
} |
||||
} |
||||
|
||||
class StringNode extends ValueNode { |
||||
private final String string; |
||||
private boolean useSingleQuote = true; |
||||
|
||||
StringNode(CharSequence charSequence, boolean escape) { |
||||
if (escape && charSequence.length() > 1) { |
||||
char open = charSequence.charAt(0); |
||||
char close = charSequence.charAt(charSequence.length()-1); |
||||
if (open == '\'' && close == '\'') { |
||||
charSequence = charSequence.subSequence(1, charSequence.length()-1); |
||||
} else if (open == '"' && close == '"') { |
||||
charSequence = charSequence.subSequence(1, charSequence.length()-1); |
||||
useSingleQuote = false; |
||||
} |
||||
string = Utils.unescape(charSequence.toString()); |
||||
} else { |
||||
string = charSequence.toString(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public NumberNode asNumberNode() { |
||||
BigDecimal number = null; |
||||
try { |
||||
number = new BigDecimal(string); |
||||
} catch (NumberFormatException nfe){ |
||||
return NumberNode.NAN; |
||||
} |
||||
return new NumberNode(number); |
||||
} |
||||
|
||||
public String getString() { |
||||
return string; |
||||
} |
||||
|
||||
public int length(){ |
||||
return getString().length(); |
||||
} |
||||
|
||||
public boolean isEmpty(){ |
||||
return getString().isEmpty(); |
||||
} |
||||
|
||||
public boolean contains(String str) { |
||||
return getString().contains(str); |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return String.class; |
||||
} |
||||
|
||||
public boolean isStringNode() { |
||||
return true; |
||||
} |
||||
|
||||
public StringNode asStringNode() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
String quote = useSingleQuote ? "'" : "\""; |
||||
return quote + Utils.escape(string, true) + quote; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof StringNode) && !(o instanceof NumberNode)) return false; |
||||
|
||||
StringNode that = ((ValueNode) o).asStringNode(); |
||||
|
||||
return !(string != null ? !string.equals(that.getString()) : that.getString() != null); |
||||
|
||||
} |
||||
} |
||||
|
||||
class NumberNode extends ValueNode { |
||||
|
||||
public static NumberNode NAN = new NumberNode((BigDecimal)null); |
||||
|
||||
private final BigDecimal number; |
||||
|
||||
NumberNode(BigDecimal number) { |
||||
this.number = number; |
||||
} |
||||
NumberNode(CharSequence num) { |
||||
number = new BigDecimal(num.toString()); |
||||
} |
||||
|
||||
@Override |
||||
public StringNode asStringNode() { |
||||
return new StringNode(number.toString(), false); |
||||
} |
||||
|
||||
public BigDecimal getNumber() { |
||||
return number; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Number.class; |
||||
} |
||||
|
||||
public boolean isNumberNode() { |
||||
return true; |
||||
} |
||||
|
||||
public NumberNode asNumberNode() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return number.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof NumberNode) && !(o instanceof StringNode)) return false; |
||||
|
||||
NumberNode that = ((ValueNode)o).asNumberNode(); |
||||
|
||||
if(that == NumberNode.NAN){ |
||||
return false; |
||||
} else { |
||||
return number.compareTo(that.number) == 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
class BooleanNode extends ValueNode { |
||||
private final Boolean value; |
||||
|
||||
private BooleanNode(CharSequence boolValue) { |
||||
value = Boolean.parseBoolean(boolValue.toString()); |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Boolean.class; |
||||
} |
||||
|
||||
public boolean isBooleanNode() { |
||||
return true; |
||||
} |
||||
|
||||
public BooleanNode asBooleanNode() { |
||||
return this; |
||||
} |
||||
|
||||
public boolean getBoolean() { |
||||
return value; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return value.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof BooleanNode)) return false; |
||||
|
||||
BooleanNode that = (BooleanNode) o; |
||||
|
||||
return !(value != null ? !value.equals(that.value) : that.value != null); |
||||
} |
||||
} |
||||
|
||||
class ClassNode extends ValueNode { |
||||
private final Class clazz; |
||||
|
||||
ClassNode(Class clazz) { |
||||
this.clazz = clazz; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Class.class; |
||||
} |
||||
|
||||
public boolean isClassNode() { |
||||
return true; |
||||
} |
||||
|
||||
public ClassNode asClassNode() { |
||||
return this; |
||||
} |
||||
|
||||
public Class getClazz() { |
||||
return clazz; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return clazz.getName(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof ClassNode)) return false; |
||||
|
||||
ClassNode that = (ClassNode) o; |
||||
|
||||
return !(clazz != null ? !clazz.equals(that.clazz) : that.clazz != null); |
||||
} |
||||
} |
||||
|
||||
class NullNode extends ValueNode { |
||||
|
||||
private NullNode() {} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Void.class; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isNullNode() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public NullNode asNullNode() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "null"; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof NullNode)) return false; |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
class UndefinedNode extends ValueNode { |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Void.class; |
||||
} |
||||
|
||||
public UndefinedNode asUndefinedNode() { |
||||
return this; |
||||
} |
||||
|
||||
public boolean isUndefinedNode() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
class PredicateNode extends ValueNode { |
||||
|
||||
private final Predicate predicate; |
||||
|
||||
public PredicateNode(Predicate predicate) { |
||||
this.predicate = predicate; |
||||
} |
||||
|
||||
public Predicate getPredicate() { |
||||
return predicate; |
||||
} |
||||
|
||||
public PredicateNode asPredicateNode() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Void.class; |
||||
} |
||||
|
||||
public boolean isPredicateNode() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return predicate.toString(); |
||||
} |
||||
} |
||||
|
||||
class ValueListNode extends ValueNode implements Iterable<ValueNode> { |
||||
|
||||
private List<ValueNode> nodes = new ArrayList<ValueNode>(); |
||||
|
||||
public ValueListNode(Collection<?> values) { |
||||
for (Object value : values) { |
||||
nodes.add(toValueNode(value)); |
||||
} |
||||
} |
||||
|
||||
public boolean contains(ValueNode node){ |
||||
return nodes.contains(node); |
||||
} |
||||
|
||||
public boolean subsetof(ValueListNode right) { |
||||
for (ValueNode leftNode : nodes) { |
||||
if (!right.nodes.contains(leftNode)) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public List<ValueNode> getNodes() { |
||||
return Collections.unmodifiableList(nodes); |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return List.class; |
||||
} |
||||
|
||||
public boolean isValueListNode() { |
||||
return true; |
||||
} |
||||
|
||||
public ValueListNode asValueListNode() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "[" + Utils.join(",", nodes) + "]"; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (!(o instanceof ValueListNode)) return false; |
||||
|
||||
ValueListNode that = (ValueListNode) o; |
||||
|
||||
return nodes.equals(that.nodes); |
||||
} |
||||
|
||||
@Override |
||||
public Iterator<ValueNode> iterator() { |
||||
return nodes.iterator(); |
||||
} |
||||
} |
||||
|
||||
class PathNode extends ValueNode { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PathNode.class); |
||||
|
||||
private final Path path; |
||||
private final boolean existsCheck; |
||||
private final boolean shouldExist; |
||||
|
||||
PathNode(Path path) { |
||||
this(path, false, false); |
||||
} |
||||
|
||||
PathNode(CharSequence charSequence, boolean existsCheck, boolean shouldExist) { |
||||
this(PathCompiler.compile(charSequence.toString()), existsCheck, shouldExist); |
||||
} |
||||
|
||||
PathNode(Path path, boolean existsCheck, boolean shouldExist) { |
||||
this.path = path; |
||||
this.existsCheck = existsCheck; |
||||
this.shouldExist = shouldExist; |
||||
logger.trace("PathNode {} existsCheck: {}", path, existsCheck); |
||||
} |
||||
|
||||
public Path getPath() { |
||||
return path; |
||||
} |
||||
|
||||
public boolean isExistsCheck() { |
||||
return existsCheck; |
||||
} |
||||
|
||||
public boolean shouldExists() { |
||||
return shouldExist; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> type(Predicate.PredicateContext ctx) { |
||||
return Void.class; |
||||
} |
||||
|
||||
public boolean isPathNode() { |
||||
return true; |
||||
} |
||||
|
||||
public PathNode asPathNode() { |
||||
return this; |
||||
} |
||||
|
||||
public PathNode asExistsCheck(boolean shouldExist) { |
||||
return new PathNode(path, true, shouldExist); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return existsCheck && ! shouldExist ? Utils.concat("!" , path.toString()) : path.toString(); |
||||
} |
||||
|
||||
public ValueNode evaluate(Predicate.PredicateContext ctx) { |
||||
if (isExistsCheck()) { |
||||
try { |
||||
Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build(); |
||||
Object result = path.evaluate(ctx.item(), ctx.root(), c).getValue(false); |
||||
return result == JsonProvider.UNDEFINED ? FALSE : TRUE; |
||||
} catch (PathNotFoundException e) { |
||||
return FALSE; |
||||
} |
||||
} else { |
||||
try { |
||||
Object res; |
||||
if (ctx instanceof PredicateContextImpl) { |
||||
//This will use cache for document ($) queries
|
||||
PredicateContextImpl ctxi = (PredicateContextImpl) ctx; |
||||
res = ctxi.evaluate(path); |
||||
} else { |
||||
Object doc = path.isRootPath() ? ctx.root() : ctx.item(); |
||||
res = path.evaluate(doc, ctx.root(), ctx.configuration()).getValue(); |
||||
} |
||||
res = ctx.configuration().jsonProvider().unwrap(res); |
||||
|
||||
if (res instanceof Number) return ValueNode.createNumberNode(res.toString()); |
||||
else if (res instanceof String) return ValueNode.createStringNode(res.toString(), false); |
||||
else if (res instanceof Boolean) return ValueNode.createBooleanNode(res.toString()); |
||||
else if (res == null) return NULL_NODE; |
||||
else if (ctx.configuration().jsonProvider().isArray(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, List.class, ctx.configuration())); |
||||
else if (ctx.configuration().jsonProvider().isMap(res)) return ValueNode.createJsonNode(ctx.configuration().mappingProvider().map(res, Map.class, ctx.configuration())); |
||||
else throw new JsonPathException("Could not convert " + res.toString() + " to a ValueNode"); |
||||
} catch (PathNotFoundException e) { |
||||
return UNDEFINED; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
} |
@ -0,0 +1,52 @@
|
||||
/* |
||||
* 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.internal.PathRef; |
||||
|
||||
import static java.lang.String.format; |
||||
|
||||
public class ArrayIndexToken extends ArrayPathToken { |
||||
|
||||
private final ArrayIndexOperation arrayIndexOperation; |
||||
|
||||
ArrayIndexToken(final ArrayIndexOperation arrayIndexOperation) { |
||||
this.arrayIndexOperation = arrayIndexOperation; |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(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); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String getPathFragment() { |
||||
return arrayIndexOperation.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return arrayIndexOperation.isSingleIndexOperation(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,114 @@
|
||||
/* |
||||
* 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.internal.PathRef; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class ArraySliceToken extends ArrayPathToken { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ArraySliceToken.class); |
||||
|
||||
private final ArraySliceOperation operation; |
||||
|
||||
ArraySliceToken(final ArraySliceOperation operation) { |
||||
this.operation = operation; |
||||
} |
||||
|
||||
@Override |
||||
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { |
||||
if (!checkArrayModel(currentPath, model, ctx)) |
||||
return; |
||||
switch (operation.operation()) { |
||||
case SLICE_FROM: |
||||
sliceFrom(currentPath, parent, model, ctx); |
||||
break; |
||||
case SLICE_BETWEEN: |
||||
sliceBetween(currentPath, parent, model, ctx); |
||||
break; |
||||
case SLICE_TO: |
||||
sliceTo(currentPath, parent, model, ctx); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
private void sliceFrom(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); |
||||
} |
||||
} |
||||
|
||||
private void sliceBetween(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); |
||||
} |
||||
} |
||||
|
||||
private void sliceTo(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() { |
||||
return operation.toString(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTokenDefinite() { |
||||
return false; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue