Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public interface ArgumentConstraint {
*/
int maxArity();

/**
* @param n the number of arguments to check against minArity & maxArity
* @return true if any of the arity constraint failed.
*/
boolean arityViolated(int n);

/**
* @return a string representation of the types accepted. Used to construct
* user friendly error messages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,38 @@ public final class ArgumentConstraints {
private static final String EXPRESSION_TYPE = "expression";

/**
* Describes a heterogenous list of arguments. Each argument is checked against
* Describes a heterogeneous list of arguments. Each argument is checked against
* the corresponding constraint. An {@link ArityException} will be thrown when
* the number of arguments does not exactly match the number of constraints.
* <p>
* May only be used as a top level constraint – and is already built in to
* {@link Function}, so direct usage of this method should not be needed.
*/
public static ArgumentConstraint listOf(ArgumentConstraint... constraints) {
return new HeterogenousListOf(constraints);
return new HeterogeneousListOf(constraints);
}

/**
* Descripes a homogenous list of arguments, of fixed or variable length.
* Describes a homogeneous list of arguments, of fixed or variable length.
* An {@link ArityException} will be thrown when there are fewer arguments
* than the specified minimum arity, or when there are more arguments than
* the specified maximum arity.
* <p>
* May only be used as a top level constraint.
*/
public static ArgumentConstraint listOf(int min, int max, ArgumentConstraint constraint) {
return new HomogenousListOf(min, max, constraint);
return new HomogeneousListOf(min, max, constraint);
}

/**
* Describes a homogeneous list of arguments without upper limit.
* An {@link ArityException} will be thrown when there are fewer arguments
* than the specified minimum arity.
* <p>
* May only be used as a top level constraint.
*/
public static ArgumentConstraint listOf(int min, ArgumentConstraint constraint) {
return new VariadicListOf(min, constraint);
}

/**
Expand Down Expand Up @@ -115,7 +126,7 @@ public BaseArgumentConstraint(int minArity, int maxArity, String expectedTypeDes
this.expectedTypeDescription = expectedTypeDescription;
}

protected <T> Iterator<ArgumentError> checkNoRemaingArguments(Iterator<FunctionArgument<T>> arguments, boolean expectNoRemainingArguments) {
protected <T> Iterator<ArgumentError> checkNoRemainingArguments(Iterator<FunctionArgument<T>> arguments, boolean expectNoRemainingArguments) {
if (expectNoRemainingArguments && arguments.hasNext()) {
return singletonIterator(ArgumentError.createArityError());
} else {
Expand All @@ -133,6 +144,9 @@ public int maxArity() {
return maxArity;
}

@Override
public boolean arityViolated(int n) { return (n < minArity || maxArity < n); }

@Override
public String expectedType() {
return expectedTypeDescription;
Expand All @@ -147,10 +161,10 @@ protected <U> Iterator<U> emptyIterator() {
}
}

private static class HomogenousListOf extends BaseArgumentConstraint {
private static class HomogeneousListOf extends BaseArgumentConstraint {
private final ArgumentConstraint subConstraint;

public HomogenousListOf(int minArity, int maxArity, ArgumentConstraint subConstraint) {
public HomogeneousListOf(int minArity, int maxArity, ArgumentConstraint subConstraint) {
super(minArity, maxArity, subConstraint.expectedType());
this.subConstraint = subConstraint;
}
Expand Down Expand Up @@ -178,14 +192,14 @@ public <T> Iterator<ArgumentError> check(Adapter<T> runtime, Iterator<FunctionAr
break;
}
}
return checkNoRemaingArguments(arguments, expectNoRemainingArguments);
return checkNoRemainingArguments(arguments, expectNoRemainingArguments);
}
}

private static class HeterogenousListOf extends BaseArgumentConstraint {
private static class HeterogeneousListOf extends BaseArgumentConstraint {
private final ArgumentConstraint[] subConstraints;

public HeterogenousListOf(ArgumentConstraint[] subConstraints) {
public HeterogeneousListOf(ArgumentConstraint[] subConstraints) {
super(calculateMinArity(subConstraints), calculateMaxArity(subConstraints), null);
this.subConstraints = subConstraints;
}
Expand Down Expand Up @@ -218,10 +232,39 @@ public <T> Iterator<ArgumentError> check(Adapter<T> runtime, Iterator<FunctionAr
return singletonIterator(ArgumentError.createArityError());
}
}
return checkNoRemaingArguments(arguments, expectNoRemainingArguments);
return checkNoRemainingArguments(arguments, expectNoRemainingArguments);
}
}

private static class VariadicListOf extends BaseArgumentConstraint {
private final ArgumentConstraint subConstraint;

public VariadicListOf(int minArity, ArgumentConstraint subConstraint) {
super(minArity, -1, subConstraint.expectedType());
this.subConstraint = subConstraint;
}

@Override
public <T> Iterator<ArgumentError> check(Adapter<T> runtime, Iterator<FunctionArgument<T>> arguments, boolean expectNoRemainingArguments) {
int i = 0;
for ( ;arguments.hasNext(); ++i) {
Iterator<ArgumentError> error = subConstraint.check(runtime, arguments, false);
if (error.hasNext()) {
return error;
}
}
if (i < minArity()) {
return singletonIterator(ArgumentError.createArityError());
} else {
return emptyIterator();
}
}

@Override
public boolean arityViolated(int n) { return n < minArity(); }
}


private static abstract class TypeCheck extends BaseArgumentConstraint {
public TypeCheck(String expectedType) {
super(1, 1, expectedType);
Expand All @@ -234,7 +277,7 @@ public <T> Iterator<ArgumentError> check(Adapter<T> runtime, Iterator<FunctionAr
if (error.hasNext()) {
return error;
} else {
return checkNoRemaingArguments(arguments, expectNoRemainingArguments);
return checkNoRemainingArguments(arguments, expectNoRemainingArguments);
}
} else {
return singletonIterator(ArgumentError.createArityError());
Expand Down Expand Up @@ -359,7 +402,7 @@ public <T> Iterator<ArgumentError> check(Adapter<T> runtime, Iterator<FunctionAr
return singletonIterator((ArgumentError) ArgumentError.createArgumentTypeError(expectedType(), type.toString()));
}
}
return checkNoRemaingArguments(arguments, expectNoRemainingArguments);
return checkNoRemainingArguments(arguments, expectNoRemainingArguments);
} else {
return singletonIterator(ArgumentError.createArityError());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.burt.jmespath.function;

import java.util.Iterator;
import java.util.List;

import io.burt.jmespath.Adapter;
import io.burt.jmespath.JmesPathType;

public class ConcatFunction extends BaseFunction {
public ConcatFunction() {
super(ArgumentConstraints.listOf(2, ArgumentConstraints.anyValue()));
}

@Override
protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
StringBuffer sb = new StringBuffer();
Iterator<FunctionArgument<T>> args = arguments.iterator();
while (args.hasNext()) {
T value = args.next().value();
if (runtime.typeOf(value) != JmesPathType.NULL) {
sb.append(runtime.toString(value));
}
}
return runtime.createString(sb.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ public class FunctionRegistry {
new ValuesFunction()
);

private static final FunctionRegistry stringManipulationRegistry = new FunctionRegistry(
defaultRegistry().functions,
new ConcatFunction(),
new MatchesFunction(),
new NormalizeSpaceFunction(),
new ReplaceFunction(),
new TokenizeFunction(),
new LowerCaseFunction(),
new SubstringAfterFunction(),
new SubstringBeforeFunction(),
new TranslateFunction(),
new UpperCaseFunction()
);

private final Map<String, Function> functions;

/**
Expand All @@ -47,6 +61,14 @@ public static FunctionRegistry defaultRegistry() {
return defaultRegistry;
}

/**
* Returns a registry with some common string manipulating functions known
* from XPath function specification.
*/
public static FunctionRegistry stringManipulationRegistry() {
return stringManipulationRegistry;
}

/**
* Creates a new function registry containing the specified functions.
* When there are multiple functions with the same name the last one is used.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.burt.jmespath.function;

import java.util.List;

import io.burt.jmespath.Adapter;
import io.burt.jmespath.JmesPathType;

public class LowerCaseFunction extends BaseFunction {

public LowerCaseFunction() {
super(ArgumentConstraints.typeOf(JmesPathType.STRING));
}

@Override
protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
T arg = arguments.get(0).value();
return runtime.createString(runtime.toString(arg).toLowerCase());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.burt.jmespath.function;

import java.util.List;

import io.burt.jmespath.Adapter;
import io.burt.jmespath.JmesPathType;

public class MatchesFunction extends RegularExpressionFunction {

public MatchesFunction() {
super(ArgumentConstraints.listOf(2, 3, ArgumentConstraints.typeOf(JmesPathType.STRING)));
}

@Override
protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
return runtime.createBoolean(getPattern(runtime, arguments).matcher(getInputString(runtime, arguments)).find());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.burt.jmespath.function;

import java.util.List;

import io.burt.jmespath.Adapter;
import io.burt.jmespath.JmesPathType;

public class NormalizeSpaceFunction extends BaseFunction {
/**
* The zero-argument form of this function is not supported
* since it is just a shorthand of using the current context
* ('.' in XPath and '@' in JmesPath)
*/
public NormalizeSpaceFunction() {
super(ArgumentConstraints.typeOf(JmesPathType.STRING));
}

@Override
protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
T arg = arguments.get(0).value();
return runtime.createString(runtime.toString(arg).replaceAll("\\s+", " ").trim());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.burt.jmespath.function;

import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import io.burt.jmespath.Adapter;

public abstract class RegularExpressionFunction extends SubstringMatchingFunction {
public RegularExpressionFunction(ArgumentConstraint argumentConstraints) {
super(argumentConstraints);
}

protected <T> String getInputString(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
return getStringParam(runtime, arguments, inputArgumentPosition());
}

protected <T> Pattern getPattern(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
String regex = getStringParam(runtime, arguments, patternArgumentPosition());
Pattern pattern = Pattern.compile(regex, getFlags(runtime, arguments));
if (pattern.matcher("").matches()) {
throw new PatternSyntaxException("pattern matches zero-length string", pattern.pattern(), -1);
}
return pattern;
}

protected <T> int getFlags(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
if (arguments.size() <= flagArgumentPosition())
return 0;
return convertPatternFlags(getStringParam(runtime, arguments, flagArgumentPosition()));
}

protected <T> String getStringParam(Adapter<T> runtime, List<FunctionArgument<T>> arguments, int i) {
return runtime.toString(arguments.get(i).value());
}

/**
* Subclasses may override these methods if parameter positions are different than usual.
*/
protected int inputArgumentPosition() {
return 0;
}

protected int patternArgumentPosition() {
return 1;
}

protected int flagArgumentPosition() {
return 2;
}

private int convertPatternFlags(String flagStr) {
int flags = 0;
for (int i = 0; i < flagStr.length(); ++i) {
final char c = flagStr.charAt(i);
switch (c) {
case 's':
flags |= Pattern.DOTALL;
break;
case 'm':
flags |= Pattern.MULTILINE;
break;
case 'i':
flags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
break;
case 'x':
flags |= Pattern.COMMENTS;
break;
case 'q':
flags |= Pattern.LITERAL;
break;
default:
throw new InvalidRegexFlagException(c, flagStr);
}
}
return flags;
}

private class InvalidRegexFlagException extends RuntimeException {
private final char unknownFlag;
private final String flagStr;

public InvalidRegexFlagException(char flag, String flagStr) {
this.unknownFlag = flag;
this.flagStr = flagStr;
}

public char getUnknownFlag() { return unknownFlag; }

public String getFlagStr() { return flagStr; }

public String toString() { return "Unknown regex flag: " + getUnknownFlag() + " in " + getFlagStr(); }
}
}
Loading