Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions json-path/src/main/java/com/jayway/jsonpath/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
package com.jayway.jsonpath;

import com.jayway.jsonpath.internal.DefaultsImpl;
import com.jayway.jsonpath.internal.function.JsonPathFunctionFactory;
import com.jayway.jsonpath.internal.function.MapJsonPathFunctionFactory;
import com.jayway.jsonpath.internal.function.PathFunction;
import com.jayway.jsonpath.internal.function.PathFunctionFactory;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;

Expand All @@ -23,6 +27,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static com.jayway.jsonpath.internal.Utils.notNull;
Expand Down Expand Up @@ -55,16 +61,24 @@ private static Defaults getEffectiveDefaults(){
private final MappingProvider mappingProvider;
private final Set<Option> options;
private final Collection<EvaluationListener> evaluationListeners;

private Configuration(JsonProvider jsonProvider, MappingProvider mappingProvider, EnumSet<Option> options, Collection<EvaluationListener> evaluationListeners) {
private final JsonPathFunctionFactory jsonPathFunctionFactory;

private Configuration(
JsonProvider jsonProvider,
MappingProvider mappingProvider,
EnumSet<Option> options,
Collection<EvaluationListener> evaluationListeners,
final JsonPathFunctionFactory jsonPathFunctionFactory) {
notNull(jsonProvider, "jsonProvider can not be null");
notNull(mappingProvider, "mappingProvider can not be null");
notNull(options, "setOptions can not be null");
notNull(evaluationListeners, "evaluationListeners can not be null");
notNull(jsonPathFunctionFactory, "jsonPathFunctionFactory can not be null");
this.jsonProvider = jsonProvider;
this.mappingProvider = mappingProvider;
this.options = Collections.unmodifiableSet(options);
this.evaluationListeners = Collections.unmodifiableCollection(evaluationListeners);
this.jsonPathFunctionFactory = jsonPathFunctionFactory;
}

/**
Expand Down Expand Up @@ -127,6 +141,14 @@ public MappingProvider mappingProvider() {
return mappingProvider;
}

/**
* Returns {@link com.jayway.jsonpath.internal.function.JsonPathFunctionFactory} used by this configuration
* @return pathFunctionFactory used
*/
public JsonPathFunctionFactory pathFunctionFactory() {
return this.jsonPathFunctionFactory;
}

/**
* Creates a new configuration by adding the new options to the options used in this configuration.
* @param options options to add
Expand Down Expand Up @@ -187,11 +209,16 @@ public static ConfigurationBuilder builder() {
*/
public static class ConfigurationBuilder {

private static final JsonPathFunctionFactory DEFAULT_FUNCTION_FACTORY =
new MapJsonPathFunctionFactory(PathFunctionFactory.FUNCTIONS);

private JsonProvider jsonProvider;
private MappingProvider mappingProvider;
private EnumSet<Option> options = EnumSet.noneOf(Option.class);
private Collection<EvaluationListener> evaluationListener = new ArrayList<EvaluationListener>();

private JsonPathFunctionFactory jsonPathFunctionFactory = DEFAULT_FUNCTION_FACTORY;

public ConfigurationBuilder jsonProvider(JsonProvider provider) {
this.jsonProvider = provider;
return this;
Expand Down Expand Up @@ -224,6 +251,28 @@ public ConfigurationBuilder evaluationListener(Collection<EvaluationListener> li
return this;
}

public ConfigurationBuilder pathFunctionFactory(final JsonPathFunctionFactory jsonPathFunctionFactory) {
if(jsonPathFunctionFactory == null) {
this.jsonPathFunctionFactory = DEFAULT_FUNCTION_FACTORY;
}
else {
this.jsonPathFunctionFactory = jsonPathFunctionFactory;
}
return this;
}

public ConfigurationBuilder pathFunctions(final Map<String, Class<? extends PathFunction>> functions) {
if(functions == null) {
this.jsonPathFunctionFactory = DEFAULT_FUNCTION_FACTORY;
}
else {
this.jsonPathFunctionFactory = new MapJsonPathFunctionFactory(
new HashMap<String, Class<? extends PathFunction>>(functions)
);
}
return this;
}

public Configuration build() {
if (jsonProvider == null || mappingProvider == null) {
final Defaults defaults = getEffectiveDefaults();
Expand All @@ -234,7 +283,7 @@ public Configuration build() {
mappingProvider = defaults.mappingProvider();
}
}
return new Configuration(jsonProvider, mappingProvider, options, evaluationListener);
return new Configuration(jsonProvider, mappingProvider, options, evaluationListener, jsonPathFunctionFactory);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.jayway.jsonpath.internal.function;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be moved to a package within com.jayway.jsonpath.spi, along with the implementation class.


import com.jayway.jsonpath.InvalidPathException;

import java.util.Map;

public interface JsonPathFunctionFactory {
/**
* Returns the function by name or throws InvalidPathException if function not found.
*
* @see #FUNCTIONS
* @see PathFunction
*
* @param name
* The name of the function
*
* @return
* The implementation of a function
*
* @throws InvalidPathException
*/
PathFunction newFunction(final String name) throws InvalidPathException;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.jayway.jsonpath.internal.function;

import com.jayway.jsonpath.InvalidPathException;

import java.util.Map;

public class MapJsonPathFunctionFactory implements JsonPathFunctionFactory {

private final Map<String, Class<? extends PathFunction>> pathFunctionMap;

public MapJsonPathFunctionFactory(
final Map<String, Class<? extends PathFunction>> pathFunctionMap) {

this.pathFunctionMap = pathFunctionMap;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you clone the list here so that modifications to the original map don't affect the factory.

}

@Override
public PathFunction newFunction(final String name) throws InvalidPathException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I question whether PathFunction as an interface is an appropriate expression for a user facing function feature - there's some nuance to know in what context the function is being invoked that is internal state of the JsonPath library. Breaking apart the types of PathFunctions (aggregations vs straight calculations) might be a good way to tease apart the interface into something more meaningful for the end-user.

Also, does the function need to take parameters -- can a sub-select be a parameter to the function - expressing that in a more general way such that the consumer doesn't need to grok the internals of JsonPath would be ideal.

Certainly keep this method as all interfaces/implementations are a PathFunction, but I think you might want to introduce additional factory methods for aggregation vs straight calculation and then abstract away the nuance of accepting parameters.

Class functionClazz = pathFunctionMap.get(name);
if(functionClazz == null){
throw new InvalidPathException("Function with name: " + name + " does not exists.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "does not exist."

} else {
try {
return (PathFunction)functionClazz.newInstance();
} catch (Exception e) {
throw new InvalidPathException("Function of name: " + name + " cannot be created", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
*/
public class PathFunctionFactory {

public static final Map<String, Class> FUNCTIONS;
public static final Map<String, Class<? extends PathFunction>> FUNCTIONS;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move the value/initialization to the Configuration class and just keep a reference here?


static {
// New functions should be added here and ensure the name is not overridden
Map<String, Class> map = new HashMap<String, Class>();
Map<String, Class<? extends PathFunction>> map = new HashMap<String, Class<? extends PathFunction>>();

// Math Functions
map.put("avg", Average.class);
Expand Down Expand Up @@ -64,16 +64,8 @@ public class PathFunctionFactory {
*
* @throws InvalidPathException
*/
@Deprecated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should deprecate the whole class

public static PathFunction newFunction(String name) throws InvalidPathException {
Class functionClazz = FUNCTIONS.get(name);
if(functionClazz == null){
throw new InvalidPathException("Function with name: " + name + " does not exists.");
} else {
try {
return (PathFunction)functionClazz.newInstance();
} catch (Exception e) {
throw new InvalidPathException("Function of name: " + name + " cannot be created", e);
}
}
return new MapJsonPathFunctionFactory(FUNCTIONS).newFunction(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public FunctionPathToken(String pathFragment, List<Parameter> parameters) {

@Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
PathFunction pathFunction = PathFunctionFactory.newFunction(functionName);
PathFunction pathFunction = ctx.configuration().pathFunctionFactory().newFunction(functionName);
evaluateParameters(currentPath, parent, model, ctx);
Object result = pathFunction.invoke(currentPath, parent, model, ctx, functionParams);
ctx.addResult(currentPath + "." + functionName, parent, result);
Expand Down