From c4c4942580abcd81522ea97f8b7c2a927f2ffa86 Mon Sep 17 00:00:00 2001 From: Bivas Das Date: Tue, 5 Jul 2016 16:30:11 -0500 Subject: [PATCH] implemented function feature added function interface for injecting arbitary code for now only built in functions are suppolted this will be expanded to allow user registered functions later added tests for function added built in String and Math functions addressed comments and some minol optimizations/refactorings updated to java8 consolidated templater leafs and function --- .travis.yml | 2 +- .../java/com/bazaarvoice/jolt/Templatr.java | 50 +++++- .../jolt/common/SpecStringParser.java | 45 +++++ .../jolt/templatr/TemplatrSpecBuilder.java | 47 ++--- .../jolt/templatr/function/Function.java | 87 +++++++++ .../jolt/templatr/function/FunctionArg.java | 118 ++++++++++++ .../templatr/function/FunctionEvaluator.java | 79 ++++++++ .../jolt/templatr/function/Math.java | 169 ++++++++++++++++++ .../jolt/templatr/function/Strings.java | 71 ++++++++ .../templatr/spec/TemplatrCompositeSpec.java | 6 +- .../spec/TemplatrContextLookupLeafSpec.java | 53 ------ .../spec/TemplatrDefaultLeafSpec.java | 51 ------ .../jolt/templatr/spec/TemplatrLeafSpec.java | 122 +++++++++++++ .../spec/TemplatrSelfLookupLeafSpec.java | 65 ------- .../jolt/templatr/spec/TemplatrSpec.java | 5 +- .../com/bazaarvoice/jolt/TemplatrTest.java | 104 ++++++++++- .../resources/json/templatr/arrayLiteral.json | 2 +- .../templatr/arrayLiteralWithEmptyInput.json | 2 +- .../arrayLiteralWithMissingInput.json | 2 +- .../templatr/arrayLiteralWithNullInput.json | 2 +- .../resources/json/templatr/arrayObject.json | 4 +- .../json/templatr/complexArrayLookup.json | 2 +- .../json/templatr/complexLookup.json | 2 +- .../json/templatr/functions/arrayTests.json | 46 +++++ .../templatr/functions/computationTest.json | 94 ++++++++++ .../json/templatr/functions/mathTests.json | 45 +++++ .../json/templatr/functions/stringsTests.json | 42 +++++ .../resources/json/templatr/mapLiteral.json | 2 +- .../templatr/mapLiteralWithEmptyInput.json | 2 +- .../templatr/mapLiteralWithMissingInput.json | 5 +- .../templatr/mapLiteralWithNullInput.json | 2 +- .../test/resources/json/templatr/simple.json | 2 +- .../resources/json/templatr/simpleArray.json | 2 +- .../json/templatr/simpleArrayLookup.json | 2 +- .../resources/json/templatr/simpleLookup.json | 2 +- .../json/templatr/simpleMapNullToArray.json | 2 +- .../json/templatr/simpleMapRuntimeNull.json | 2 +- 37 files changed, 1101 insertions(+), 239 deletions(-) create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Function.java create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java delete mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrContextLookupLeafSpec.java delete mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrDefaultLeafSpec.java create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java delete mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrSelfLookupLeafSpec.java create mode 100644 jolt-core/src/test/resources/json/templatr/functions/arrayTests.json create mode 100644 jolt-core/src/test/resources/json/templatr/functions/computationTest.json create mode 100644 jolt-core/src/test/resources/json/templatr/functions/mathTests.json create mode 100644 jolt-core/src/test/resources/json/templatr/functions/stringsTests.json diff --git a/.travis.yml b/.travis.yml index e358d257..9bcf9994 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: java jdk: - - oraclejdk7 + - oraclejdk8 diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java index 109a87f4..ce9bfff9 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java @@ -21,8 +21,12 @@ import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.templatr.OpMode; import com.bazaarvoice.jolt.templatr.TemplatrSpecBuilder; +import com.bazaarvoice.jolt.templatr.function.Function; +import com.bazaarvoice.jolt.templatr.function.Math; +import com.bazaarvoice.jolt.templatr.function.Strings; import com.bazaarvoice.jolt.templatr.spec.TemplatrCompositeSpec; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -31,10 +35,25 @@ */ public abstract class Templatr implements SpecDriven, ContextualTransform { + private static final Map STOCK_FUNCTIONS = new HashMap<>( ); + + static { + STOCK_FUNCTIONS.put( "toLower", new Strings.toLowerCase() ); + STOCK_FUNCTIONS.put( "toUpper", new Strings.toUpperCase() ); + STOCK_FUNCTIONS.put( "concat", new Strings.concat() ); + + STOCK_FUNCTIONS.put( "minOf", new com.bazaarvoice.jolt.templatr.function.Math.MinOf() ); + STOCK_FUNCTIONS.put( "maxOf", new Math.MaxOf() ); + STOCK_FUNCTIONS.put( "abs", new Math.Abs() ); + STOCK_FUNCTIONS.put( "toInteger", new Math.toInteger() ); + STOCK_FUNCTIONS.put( "toDouble", new Math.toDouble() ); + STOCK_FUNCTIONS.put( "toLong", new Math.toLong() ); + } + private final TemplatrCompositeSpec rootSpec; @SuppressWarnings( "unchecked" ) - private Templatr(Object spec, OpMode opMode) { + private Templatr(Object spec, OpMode opMode, Map functionsMap) { if ( spec == null ){ throw new SpecException( opMode.name() + " expected a spec of Map type, got 'null'." ); } @@ -42,7 +61,12 @@ private Templatr(Object spec, OpMode opMode) { throw new SpecException( opMode.name() + " expected a spec of Map type, got " + spec.getClass().getSimpleName() ); } - TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder( opMode ); + if(functionsMap == null || functionsMap.isEmpty()) { + throw new SpecException( opMode.name() + " expected a populated functions' map type, got " + (functionsMap == null?"null":"empty") ); + } + + functionsMap = Collections.unmodifiableMap( functionsMap ); + TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder( opMode, functionsMap ); rootSpec = new TemplatrCompositeSpec( ROOT_KEY, (Map) spec, opMode, templatrSpecBuilder ); } @@ -60,14 +84,18 @@ public Object transform( final Object input, final Map context ) return input; } - /** +/** * This variant of templatr creates the key/index is missing, * and overwrites the value if present */ public static final class Overwritr extends Templatr { public Overwritr( Object spec ) { - super(spec, OpMode.OVERWRITR ); + this( spec, STOCK_FUNCTIONS ); + } + + public Overwritr( Object spec, Map functionsMap ) { + super( spec, OpMode.OVERWRITR, functionsMap ); } } @@ -77,17 +105,25 @@ public Overwritr( Object spec ) { public static final class Definr extends Templatr { public Definr( final Object spec ) { - super( spec, OpMode.DEFINER ); + this( spec, STOCK_FUNCTIONS ); + } + + public Definr( Object spec, Map functionsMap ) { + super( spec, OpMode.DEFINER, functionsMap ); } } /** - * This variant of templatr only writes when the key/index is present and the value is null + * This variant of templatr only writes when the key/index is missing or the value is null */ public static class Defaultr extends Templatr { public Defaultr( final Object spec ) { - super( spec, OpMode.DEFAULTR ); + this( spec, STOCK_FUNCTIONS ); + } + + public Defaultr( Object spec, Map functionsMap ) { + super( spec, OpMode.DEFAULTR, functionsMap ); } } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/SpecStringParser.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/SpecStringParser.java index 720872fa..5ee88f13 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/SpecStringParser.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/SpecStringParser.java @@ -19,6 +19,7 @@ import com.bazaarvoice.jolt.exception.SpecException; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; @@ -301,4 +302,48 @@ public static String removeEscapeChars( String origKey ) { return sb.toString(); } + + public static List parseFunctionArgs(String argString) { + List argsList = new LinkedList<>( ); + int firstBracket = argString.indexOf( '(' ); + + String className = argString.substring( 0, firstBracket ); + argsList.add( className ); + + // drop the first and last ( ) + argString = argString.substring( firstBracket + 1, argString.length() - 1 ); + + StringBuilder sb = new StringBuilder( ); + boolean inBetweenBrackets = false; + boolean inBetweenQuotes = false; + for (int i = 0; i < argString.length(); i++){ + char c = argString.charAt(i); + switch ( c ) { + case '(': + inBetweenBrackets = true; + sb.append( c ); + break; + case ')': + inBetweenBrackets = false; + sb.append( c ); + break; + case '\'': + inBetweenQuotes = !inBetweenQuotes; + sb.append( c ); + break; + case ',': + if ( !inBetweenBrackets && !inBetweenQuotes ) { + argsList.add( sb.toString() ); + sb = new StringBuilder(); + break; + } + default: + sb.append( c ); + break; + } + } + + argsList.add( sb.toString() ); + return argsList; + } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java index 84a3ee54..625906cf 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java @@ -17,57 +17,36 @@ package com.bazaarvoice.jolt.templatr; import com.bazaarvoice.jolt.common.spec.SpecBuilder; +import com.bazaarvoice.jolt.templatr.function.Function; import com.bazaarvoice.jolt.templatr.spec.TemplatrCompositeSpec; -import com.bazaarvoice.jolt.templatr.spec.TemplatrContextLookupLeafSpec; -import com.bazaarvoice.jolt.templatr.spec.TemplatrDefaultLeafSpec; -import com.bazaarvoice.jolt.templatr.spec.TemplatrSelfLookupLeafSpec; +import com.bazaarvoice.jolt.templatr.spec.TemplatrLeafSpec; import com.bazaarvoice.jolt.templatr.spec.TemplatrSpec; import java.util.Map; public class TemplatrSpecBuilder extends SpecBuilder { - private static final String CARET = "^"; - private static final String AT = "@"; + public static final String CARET = "^"; + public static final String AT = "@"; + public static final String FUNCTION = "="; private final OpMode opMode; + private final Map functionsMap; - public TemplatrSpecBuilder(OpMode opMode) { + + public TemplatrSpecBuilder( OpMode opMode, Map functionsMap ) { this.opMode = opMode; + this.functionsMap = functionsMap; } @Override + @SuppressWarnings( "unchecked" ) public TemplatrSpec createSpec( final String lhs, final Object rhs ) { - if( rhs instanceof Map ) { - Map rhsMap = (Map)rhs; - if(rhsMap.isEmpty()) { - return new TemplatrDefaultLeafSpec( lhs, rhsMap, opMode ); - } - else { - return new TemplatrCompositeSpec(lhs, rhsMap, opMode, this ); - } - + if( rhs instanceof Map && (!( (Map) rhs ).isEmpty())) { + return new TemplatrCompositeSpec(lhs, (Map)rhs, opMode, this ); } else { - if ( rhs instanceof String ) { - String rhsValue = (String) rhs; - // leaf level starts with ^ , so spec is an dot notation read from context - if(rhsValue.startsWith( CARET )) { - return new TemplatrContextLookupLeafSpec( lhs, rhsValue, opMode ); - } - // leaf level starts with @ , so spec is an dot notation read from self - else if(rhsValue.startsWith( AT )) { - return new TemplatrSelfLookupLeafSpec( lhs, rhsValue, opMode ); - } - // leaf level is an actual string value, we need to set as default - else { - return new TemplatrDefaultLeafSpec( lhs, rhsValue, opMode ); - } - } - // leaf level is an actual non-string value or null or Map or List, we need to set as default - else { - return new TemplatrDefaultLeafSpec( lhs, rhs, opMode ); - } + return new TemplatrLeafSpec( lhs, rhs, opMode, functionsMap ); } } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Function.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Function.java new file mode 100644 index 00000000..def102b8 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Function.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013 Bazaarvoice, Inc. + * + * 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.bazaarvoice.jolt.templatr.function; + +import com.bazaarvoice.jolt.common.Optional; + +/** + * Templatr supports a Function on RHS that accepts jolt path expressions as arguments and evaluates + * them at runtime before calling it. Function always returns an Optional, and the value is written + * only if the optional is not empty. + * + * function spec is defined by "key": "=functionName(args...)" + * + * + * input: + * { "num": -1.0 } + * spec: + * { "num": "=abs(@(1,&0))" } + * will call the stock function Math.abs() and will pass the matching value at "num" + * + * spec: + * { "num": "=abs" } + * an alternative shortcut will do the same thing + * + * output: + * { "num": 1.0 } + * + * + * + * input: + * { "value": -1.0 } + * + * spec: + * { "absValue": "=abs(@(1,value))" } + * will evaluate the jolt path expression @(1,value) and pass the output to stock function Math.abs() + * + * output: + * { "value": -1.0, "absValue": 1.0 } + * + * + * + * Currently defined stock functions are: + * + * toLower - returns toLower value of toString() value of first arg, rest is ignored + * toUpper - returns toUpper value of toString() value of first arg, rest is ignored + * concat - concatenate all given arguments' toString() values + * + * minOf - returns the min of all numbers provided in the arguments, non-numbers are ignored + * maxOf - returns the max of all numbers provided in the arguments, non-numbers are ignored + * abs - returns the absolute value of first argument, rest is ignored + * toInteger - returns the intValue() value of first argument if its numeric, rest is ignored + * toDouble - returns the doubleValue() value of first argument if its numeric, rest is ignored + * toLong - returns the longValue() value of first argument if its numeric, rest is ignored + * + * All of these functions returns Optional.EMPTY if unsuccessful, which results in a no-op when performing + * the actual write in the json doc. + * + * i.e. + * input: + * { "value": "1.0" } --- note: string, not number + * + * spec: + * { "absValue": "=abs" } --- fails silently + * + * output: + * { "value": "1.0" } --- note: "absValue": null is not inserted + * + */ +public interface Function { + + public Optional apply(Object... args); + +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java new file mode 100644 index 00000000..12261f46 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java @@ -0,0 +1,118 @@ +/* + * Copyright 2013 Bazaarvoice, Inc. + * + * 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.bazaarvoice.jolt.templatr.function; + +import com.bazaarvoice.jolt.common.Optional; +import com.bazaarvoice.jolt.common.PathEvaluatingTraversal; +import com.bazaarvoice.jolt.common.pathelement.PathElement; +import com.bazaarvoice.jolt.common.pathelement.TransposePathElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; +import com.bazaarvoice.jolt.exception.SpecException; + +import java.util.Map; + +public abstract class FunctionArg { + + public static FunctionArg forSelf(PathEvaluatingTraversal traversal) { + return new SelfLookupArg( traversal ); + } + + private static final class SelfLookupArg extends FunctionArg { + private final TransposePathElement pathElement; + + private SelfLookupArg( PathEvaluatingTraversal traversal ) { + PathElement pathElement = traversal.get( traversal.size() - 1 ); + if(pathElement instanceof TransposePathElement ) { + this.pathElement = (TransposePathElement) pathElement; + } + else { + throw new SpecException( "Expected @ path element here" ); + } + } + + @Override + public Optional evaluateArg( final WalkedPath walkedPath, final Map context ) { + return pathElement.objectEvaluate( walkedPath ); + } + } + + public static FunctionArg forContext(PathEvaluatingTraversal traversal) { + return new ContextLookupArg( traversal ); + } + + private static final class ContextLookupArg extends FunctionArg { + private final PathEvaluatingTraversal traversal; + + private ContextLookupArg( PathEvaluatingTraversal traversal ) { + this.traversal = traversal; + } + + @Override + public Optional evaluateArg( final WalkedPath walkedPath, final Map context ) { + return traversal.read( context, walkedPath ); + } + } + + public static FunctionArg forLiteral( Object obj ) { + if(obj instanceof String) { + String arg = (String) obj; + if ( arg.length() == 0 ) { + return new LiteralArg( null ); + } + else if ( arg.startsWith( "'" ) && arg.endsWith( "'" ) ) { + return new LiteralArg( arg.substring( 1, arg.length() - 1 ) ); + } + else if ( arg.equalsIgnoreCase( "true" ) || arg.equalsIgnoreCase( "false" ) ) { + return new LiteralArg( Boolean.parseBoolean( arg ) ); + } + else { + try { + return new LiteralArg( Integer.parseInt( arg ) ); + } + catch ( Exception ignored ) {} + try { + return new LiteralArg( Long.parseLong( arg ) ); + } + catch ( Exception ignored ) {} + try { + return new LiteralArg( Double.parseDouble( arg ) ); + } + catch ( Exception ignored ) {} + return new LiteralArg( arg ); + } + } + else { + return new LiteralArg( obj ); + } + } + + private static final class LiteralArg extends FunctionArg { + + private final Optional returnValue; + + private LiteralArg( final Object object ) { + this.returnValue = Optional.of( object ); + } + + @Override + public Optional evaluateArg( final WalkedPath walkedPath, final Map context ) { + return returnValue; + } + } + + public abstract Optional evaluateArg(WalkedPath walkedPath, Map context); +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java new file mode 100644 index 00000000..7e40de02 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Bazaarvoice, Inc. + * + * 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.bazaarvoice.jolt.templatr.function; + +import com.bazaarvoice.jolt.common.Optional; +import com.bazaarvoice.jolt.common.tree.WalkedPath; + +import java.util.Map; + +public class FunctionEvaluator { + + public static FunctionEvaluator of(Function function, FunctionArg[] functionArgs) { + return new FunctionEvaluator( function, functionArgs ); + } + + // function that is evaluated and applied as output + private final Function function; + // arguments of the function, not evaluated and can be a jolt path expression that + // either point to a context or self, or a value present at the matching level + private final FunctionArg[] functionArgs; + + private FunctionEvaluator( final Function function, final FunctionArg[] functionArgs ) { + this.function = function; + this.functionArgs = functionArgs; + } + + + public Optional evaluate(Object input, WalkedPath walkedPath, Map context) { + Optional valueOptional = Optional.empty(); + + try { + Object[] evaluatedArgs; + + // "key": "@0", "key": literal + if(function == null) { + valueOptional = functionArgs[0].evaluateArg( walkedPath, context ); + } + // "key": "=abs(@(1,&0))" + else if( functionArgs != null ) { + evaluatedArgs = evaluateArgsValue( functionArgs, context, walkedPath ); + valueOptional = function.apply( evaluatedArgs ); + } + // "key": "=abs" + else { + evaluatedArgs = new Object[] {input}; // pass current value as arg + valueOptional = function.apply( evaluatedArgs ); + } + } + catch(Exception ignored) {} + + return valueOptional; + + } + + private static Object[] evaluateArgsValue( final FunctionArg[] functionArgs, final Map context, final WalkedPath walkedPath ) { + + Object[] evaluatedArgs = new Object[functionArgs.length]; + for(int i=0; i evaluatedValue = arg.evaluateArg( walkedPath, context ); + evaluatedArgs[i] = evaluatedValue.isPresent() ? evaluatedValue.get() : null; + } + return evaluatedArgs; + } +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java new file mode 100644 index 00000000..acf5515d --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java @@ -0,0 +1,169 @@ +/* + * Copyright 2013 Bazaarvoice, Inc. + * + * 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.bazaarvoice.jolt.templatr.function; + +import com.bazaarvoice.jolt.common.Optional; + +@SuppressWarnings( "unused" ) +public class Math { + + public static final class MaxOf implements Function { + @Override + public Optional apply( final Object... args ) { + Integer maxInt = Integer.MIN_VALUE; + Double maxDouble = -(Double.MIN_VALUE); + Long maxLong = Long.MIN_VALUE; + + if(args.length == 0) { + return Optional.empty(); + } + + for(Object arg: args) { + if(arg instanceof Integer) { + maxInt = java.lang.Math.max( maxInt, (Integer) arg ); + } + else if(arg instanceof Double) { + maxDouble = java.lang.Math.max( maxDouble, (Double) arg ); + } + else if(arg instanceof Long) { + maxLong = java.lang.Math.max(maxLong, (Long) arg); + } + } + if(maxInt == Integer.MIN_VALUE && maxDouble == Double.MIN_VALUE && maxLong == Long.MIN_VALUE) { + return Optional.empty(); + } + // explicit if else to avoid autoboxing + if(maxInt.longValue() >= maxDouble.longValue() && maxInt.longValue() >= maxLong) { + return Optional.of(maxInt); + } + else if(maxLong >= maxDouble.longValue() && maxLong >= maxInt.longValue()) { + return Optional.of(maxLong); + } + else { + return Optional.of(maxDouble); + } + } + } + + public static final class MinOf implements Function { + @Override + public Optional apply( final Object... args ) { + Integer minInt = Integer.MAX_VALUE; + Double minDouble = Double.MAX_VALUE; + Long minLong = Long.MAX_VALUE; + + if(args.length == 0) { + return Optional.empty(); + } + + for(Object arg: args) { + if(arg instanceof Integer) { + minInt = java.lang.Math.min( minInt, (Integer) arg ); + } + else if(arg instanceof Double) { + minDouble = java.lang.Math.min( minDouble, (Double) arg ); + } + else if(arg instanceof Long) { + minLong = java.lang.Math.min( minLong, (Long) arg ); + } + } + if(minInt == Integer.MAX_VALUE && minDouble == Double.MAX_VALUE) { + return Optional.empty(); + } + // explicit if else to avoid autoboxing + if(minInt.longValue() <= minDouble.longValue() && minInt.longValue() <= minLong) { + return Optional.of(minInt); + } + else if(minLong <= minDouble.longValue() && minLong <= minInt.longValue()) { + return Optional.of(minLong); + } + else { + return Optional.of(minDouble); + } + } + } + + public static final class Abs implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + Object arg = args[0]; + if(arg != null) { + if(arg instanceof Integer) { + return Optional.of( java.lang.Math.abs( (Integer) arg )); + } + else if(arg instanceof Double) { + return Optional.of( java.lang.Math.abs( (Double) arg )); + } + else if(arg instanceof Long) { + return Optional.of( java.lang.Math.abs( (Long) arg )); + } + } + return Optional.empty(); + } + } + + public static final class toInteger implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + Object arg = args[0]; + if(arg != null && arg instanceof Number) { + return Optional.of( ( (Number) arg ).intValue() ); + } + else { + return Optional.empty(); + } + } + } + + public static final class toLong implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + Object arg = args[0]; + if(arg != null && arg instanceof Number) { + return Optional.of( ( (Number) arg ).longValue() ); + } + else { + return Optional.empty(); + } + } + } + + public static final class toDouble implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + Object arg = args[0]; + if(arg != null && arg instanceof Number) { + return Optional.of( ( (Number) arg ).doubleValue() ); + } + else { + return Optional.empty(); + } + } + } +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java new file mode 100644 index 00000000..60090579 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013 Bazaarvoice, Inc. + * + * 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.bazaarvoice.jolt.templatr.function; + +import com.bazaarvoice.jolt.common.Optional; + +@SuppressWarnings( "unused" ) +public class Strings { + + public static final class toLowerCase implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + Object arg = args[0]; + if(arg != null) { + return Optional.of( args[0].toString().toLowerCase()); + } + else { + return Optional.of( null ); + } + } + } + + public static final class toUpperCase implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + Object arg = args[0]; + if(arg != null) { + return Optional.of(args[0].toString().toUpperCase()); + } + else { + return Optional.of( null ); + } + } + } + + public static final class concat implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + StringBuilder sb = new StringBuilder( ); + for(Object arg: args) { + if ( arg != null ) { + sb.append(arg.toString() ); + } + } + return Optional.of(sb.toString()); + } + } +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java index 16e43136..7150938f 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java @@ -136,11 +136,11 @@ else if(childPathElement instanceof ArrayPathElement) { @Override @SuppressWarnings( "unchecked" ) - public boolean applyElement( final String inputKey, Object input, MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { + public void applyElement( final String inputKey, Object input, MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { // sanity checks, cannot work on a list spec with map input and vice versa, and runtime with null input if(!specDataType.isCompatible( input )) { - return true; + return; } // create input if it is null @@ -167,8 +167,6 @@ public boolean applyElement( final String inputKey, Object input, MatchedElement executionStrategy.process( this, input, walkedPath, null, context ); // We are done, so remove ourselves from the walkedPath walkedPath.removeLast(); - - return true; } @Override diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrContextLookupLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrContextLookupLeafSpec.java deleted file mode 100644 index c699b059..00000000 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrContextLookupLeafSpec.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013 Bazaarvoice, Inc. - * - * 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.bazaarvoice.jolt.templatr.spec; - -import com.bazaarvoice.jolt.common.Optional; -import com.bazaarvoice.jolt.common.PathEvaluatingTraversal; -import com.bazaarvoice.jolt.common.tree.MatchedElement; -import com.bazaarvoice.jolt.common.tree.WalkedPath; -import com.bazaarvoice.jolt.templatr.OpMode; - -import java.util.Map; - -/** - * This spec helps lookup a specific leaf value from a given context object and copies that to a leaf node in input - */ -public class TemplatrContextLookupLeafSpec extends TemplatrSpec { - - private final PathEvaluatingTraversal templatrReader; - - public TemplatrContextLookupLeafSpec( final String rawJsonKey, final String rhsValue, final OpMode opMode ) { - super( rawJsonKey, opMode ); - templatrReader = TRAVERSAL_BUILDER.build( rhsValue.substring( 1 ) ); - } - - @Override - public boolean applyElement( final String inputKey, final Object input, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { - - Object parent = walkedPath.lastElement().getTreeRef(); - walkedPath.add( input, thisLevel ); - - Optional dataOptional = templatrReader.read( context, walkedPath ); - if(dataOptional.isPresent()) { - setData( parent, thisLevel, dataOptional.get(), opMode ); - } - walkedPath.removeLast(); - - return true; - } -} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrDefaultLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrDefaultLeafSpec.java deleted file mode 100644 index 2d20e030..00000000 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrDefaultLeafSpec.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013 Bazaarvoice, Inc. - * - * 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.bazaarvoice.jolt.templatr.spec; - -import com.bazaarvoice.jolt.common.tree.MatchedElement; -import com.bazaarvoice.jolt.common.tree.WalkedPath; -import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.templatr.OpMode; - -import java.util.List; -import java.util.Map; - -/** - * This spec copies default/hardcoded values from spec onto input object - */ -public class TemplatrDefaultLeafSpec extends TemplatrSpec { - - private final Object defaultValue; - - public TemplatrDefaultLeafSpec( final String rawJsonKey, Object rhs, final OpMode opMode ) { - super(rawJsonKey, opMode); - // leaf level is an actual string, non-string, null, List or Map value, we need to set as default - if(rhs instanceof String || rhs instanceof Number || rhs instanceof Map || rhs instanceof List || rhs == null) { - defaultValue = rhs; - } - else { - throw new SpecException( "Invalid Templatr spec RHS. Should be literal value or string path leading with ^ to look up value from context or List or Map value to set as default. Spec in question " + rawJsonKey + " : " + rhs ); - } - } - - @Override - public boolean applyElement( final String inputKey, final Object input, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { - Object parent = walkedPath.lastElement().getTreeRef(); - setData( parent, thisLevel, defaultValue, opMode); - return true; - } -} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java new file mode 100644 index 00000000..62df95e3 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java @@ -0,0 +1,122 @@ +/* + * Copyright 2013 Bazaarvoice, Inc. + * + * 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.bazaarvoice.jolt.templatr.spec; + +import com.bazaarvoice.jolt.common.Optional; +import com.bazaarvoice.jolt.common.SpecStringParser; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; +import com.bazaarvoice.jolt.exception.SpecException; +import com.bazaarvoice.jolt.templatr.OpMode; +import com.bazaarvoice.jolt.templatr.TemplatrSpecBuilder; +import com.bazaarvoice.jolt.templatr.function.Function; +import com.bazaarvoice.jolt.templatr.function.FunctionArg; +import com.bazaarvoice.jolt.templatr.function.FunctionEvaluator; + +import java.util.List; +import java.util.Map; + +public class TemplatrLeafSpec extends TemplatrSpec { + + private final FunctionEvaluator functionEvaluator; + + @SuppressWarnings( "unchecked" ) + public TemplatrLeafSpec( final String rawJsonKey, Object rhsObj, final OpMode opMode, final Map functionsMap ) { + super(rawJsonKey, opMode); + + final Function function; + final FunctionArg[] functionArgs; + + // "key": anyObjectOrLiteral --- just set as-is + if ( !(rhsObj instanceof String) ) { + function = null; + functionArgs = new FunctionArg[]{ FunctionArg.forLiteral( rhsObj ) }; + } + else { + String rhs = (String) rhsObj; + // "key": "@0" --- evaluate expression then set + if(!rhs.startsWith( TemplatrSpecBuilder.FUNCTION )) { + function = null; + functionArgs = new FunctionArg[]{constructSingleArg( rhs )}; + } + else { + String functionName; + // "key": "=abs" --- call function with current value then set output if present + if ( !rhs.contains( "(" ) && !rhs.endsWith( ")" ) ) { + functionName = rhs.substring( TemplatrSpecBuilder.FUNCTION.length() ); + functionArgs = null; + } + // "key": "=abs(@(1,&0))" --- evaluate expression then call function with + // expression-output, then set output if present + else { + String fnString = rhs.substring( TemplatrSpecBuilder.FUNCTION.length() ); + List fnArgs = SpecStringParser.parseFunctionArgs( fnString ); + functionName = fnArgs.remove( 0 ); + functionArgs = constructArgs( fnArgs ); + } + // sanity check, should happen only if provided function name is different than what is provided + if ( (function = functionsMap.get( functionName )) == null ) { + throw new SpecException( "Invalid " + opMode.name() + " spec LHS. No function named:" + functionName + " available. Spec in question " + rawJsonKey + " : " + rhs ); + } + } + } + // sanity check --- this should not happen though! + if( function == null && ( functionArgs.length == 0 ) ) { + throw new SpecException( "Invalid " + opMode.name() + " spec LHS. Spec in question " + rawJsonKey + " : " + rhsObj ); + } + functionEvaluator = FunctionEvaluator.of( function, functionArgs ); + } + + @Override + public void applyElement( final String inputKey, final Object input, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { + + Object parent = walkedPath.lastElement().getTreeRef(); + Optional valueOptional = Optional.empty(); + walkedPath.add( input, thisLevel ); + + try { + valueOptional = functionEvaluator.evaluate( input, walkedPath, context ); + } + catch(Exception ignored) {} + + if(valueOptional.isPresent()) { + setData( parent, thisLevel, valueOptional.get(), opMode ); + } + walkedPath.removeLast(); + } + + private static FunctionArg[] constructArgs( List argsList ) { + FunctionArg[] argsArray = new FunctionArg[argsList.size()]; + for(int i=0; i context ) { - - Object parent = walkedPath.lastElement().getTreeRef(); - - walkedPath.add( input, thisLevel ); - Optional objectOptional = rhsPathElement.objectEvaluate( walkedPath ); - walkedPath.removeLast(); - - if(objectOptional.isPresent()) { - setData( parent, thisLevel, objectOptional.get(), opMode ); - } - - return true; - } -} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrSpec.java index a8467cdd..5f1662af 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrSpec.java @@ -82,14 +82,15 @@ public boolean apply( final String inputKey, final Object input, final WalkedPat return false; } - return applyElement( inputKey, input, thisLevel, walkedPath, context ); + applyElement( inputKey, input, thisLevel, walkedPath, context ); + return true; } /** * Templatr specific override that is used in BaseSpec#apply(...) * The name is changed for easy identification during debugging */ - protected abstract boolean applyElement( final String key, final Object input, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ); + protected abstract void applyElement( final String key, final Object input, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ); /** * Static utility method for facilitating writes on input object diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/TemplatrTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/TemplatrTest.java index 27c6b5e7..1935e813 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/TemplatrTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/TemplatrTest.java @@ -16,19 +16,25 @@ package com.bazaarvoice.jolt; +import com.bazaarvoice.jolt.common.Optional; +import com.bazaarvoice.jolt.common.SpecStringParser; import com.bazaarvoice.jolt.exception.SpecException; +import com.bazaarvoice.jolt.templatr.function.Function; import com.google.common.collect.Lists; +import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.lang.reflect.Field; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; public class TemplatrTest { enum TemplatrTestCase { - OVERWRITE { + OVERWRITR { @Override Templatr getTemplatr( final Object spec ) { return new Templatr.Overwritr( spec ); @@ -50,6 +56,18 @@ Templatr getTemplatr( final Object spec ) { abstract Templatr getTemplatr(Object spec); } + @BeforeClass + @SuppressWarnings( "unchecked" ) + public void setup() throws Exception { + // accessing built ins such that we can test a custom impl of function + // this is a special test case, and not a recommended approach of using function + Field f = Templatr.class.getDeclaredField("STOCK_FUNCTIONS"); + f.setAccessible( true ); + Map BUILT_INS = (Map) f.get( null ); + BUILT_INS.put( "minLabelComputation", new MinLabelComputation() ); + BUILT_INS.put( "maxLabelComputation", new MaxLabelComputation() ); + } + @DataProvider public Iterator getTestCases() { List testCases = Lists.newLinkedList(); @@ -77,7 +95,7 @@ public Iterator getTestCases() { @Test (dataProvider = "getTestCases") public void testOverwritrTransform(String testFile) throws Exception { - doTest( testFile, TemplatrTestCase.OVERWRITE ); + doTest( testFile, TemplatrTestCase.OVERWRITR ); } @Test (dataProvider = "getTestCases") @@ -121,4 +139,86 @@ public Iterator getSpecValidationTestCases() { public void testInvalidSpecs(TemplatrTestCase testCase, Object spec) { testCase.getTemplatr( spec ); } + + @DataProvider + public Iterator getFunctionTests() { + List testCases = Lists.newLinkedList(); + + testCases.add( new Object[]{"/json/templatr/functions/stringsTests.json", TemplatrTestCase.OVERWRITR}); + testCases.add( new Object[]{"/json/templatr/functions/mathTests.json", TemplatrTestCase.OVERWRITR} ); + testCases.add( new Object[]{"/json/templatr/functions/arrayTests.json", TemplatrTestCase.OVERWRITR} ); + testCases.add( new Object[]{"/json/templatr/functions/computationTest.json", TemplatrTestCase.DEFAULTR} ); + + return testCases.iterator(); + } + + + @Test (dataProvider = "getFunctionTests") + public void testFunctions(String testFile, TemplatrTestCase testCase) throws Exception { + doTest( testFile, testCase); + } + + @DataProvider + public Iterator fnArgParseTestCases(){ + List testCases = Lists.newLinkedList(); + + testCases.add( new Object[] {"fn(abc,efg,pqr)", new String[] {"fn", "abc", "efg", "pqr"} } ); + testCases.add( new Object[] {"fn(abc,@(1,2),pqr)", new String[] {"fn", "abc", "@(1,2)", "pqr"} } ); + testCases.add( new Object[] {"fn(abc,efg,pqr,)", new String[] {"fn", "abc", "efg", "pqr", ""} } ); + testCases.add( new Object[] {"fn(abc,,@(1,,2),,pqr,,)", new String[] {"fn", "abc", "","@(1,,2)","", "pqr", "", ""} } ); + testCases.add( new Object[] {"fn(abc,'e,f,g',pqr)", new String[] {"fn", "abc", "'e,f,g'", "pqr"} } ); + testCases.add( new Object[] {"fn(abc,'e(,f,)g',pqr)", new String[] {"fn", "abc", "'e(,f,)g'", "pqr"} } ); + + return testCases.iterator(); + } + + @Test( dataProvider = "fnArgParseTestCases") + public void testFunctionArgParse(String argString, String[] expected) throws Exception { + List actual = SpecStringParser.parseFunctionArgs( argString ); + JoltTestUtil.runArrayOrderObliviousDiffy(" failed case " + argString, expected, actual ); + } + + @SuppressWarnings( "unused" ) + public static final class MinLabelComputation implements Function { + @Override + @SuppressWarnings( "unchecked" ) + public Optional apply( final Object... args ) { + Map valueLabels = (Map) args[0]; + Integer min = Integer.MAX_VALUE; + Set valueLabelKeys = valueLabels.keySet(); + for (String labelKey: valueLabelKeys ) { + Integer val = null; + try { + val = Integer.parseInt( labelKey ); + } + catch(Exception ignored) {} + if(val != null) { + min = Math.min( val, min ); + } + } + return Optional.of( valueLabels.get( min.toString() ) ); + } + } + + @SuppressWarnings( "unused" ) + public static final class MaxLabelComputation implements Function { + @Override + @SuppressWarnings( "unchecked" ) + public Optional apply( final Object... args ) { + Map valueLabels = (Map) args[0]; + Integer max = Integer.MIN_VALUE; + Set valueLabelKeys = valueLabels.keySet(); + for (String labelKey: valueLabelKeys ) { + Integer val = null; + try { + val = Integer.parseInt( labelKey ); + } + catch(Exception ignored) {} + if(val != null) { + max = Math.max( val, max ); + } + } + return Optional.of( valueLabels.get( max.toString() ) ); + } + } } diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteral.json b/jolt-core/src/test/resources/json/templatr/arrayLiteral.json index 4a4f379a..87c8d713 100644 --- a/jolt-core/src/test/resources/json/templatr/arrayLiteral.json +++ b/jolt-core/src/test/resources/json/templatr/arrayLiteral.json @@ -19,7 +19,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleArray": [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithEmptyInput.json b/jolt-core/src/test/resources/json/templatr/arrayLiteralWithEmptyInput.json index 9806c040..dfa37133 100644 --- a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithEmptyInput.json +++ b/jolt-core/src/test/resources/json/templatr/arrayLiteralWithEmptyInput.json @@ -17,7 +17,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleArray": [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithMissingInput.json b/jolt-core/src/test/resources/json/templatr/arrayLiteralWithMissingInput.json index 5406b7c1..421cacb8 100644 --- a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithMissingInput.json +++ b/jolt-core/src/test/resources/json/templatr/arrayLiteralWithMissingInput.json @@ -14,7 +14,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleArray": [ 0,0,0,0,0 ] diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithNullInput.json b/jolt-core/src/test/resources/json/templatr/arrayLiteralWithNullInput.json index f4a82497..157d9805 100644 --- a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithNullInput.json +++ b/jolt-core/src/test/resources/json/templatr/arrayLiteralWithNullInput.json @@ -12,7 +12,7 @@ "context": {}, - "OVERWRITE": { + "OVERWRITR": { "simpleArray": [ 0,0,0,0,0 ] diff --git a/jolt-core/src/test/resources/json/templatr/arrayObject.json b/jolt-core/src/test/resources/json/templatr/arrayObject.json index 4d83205c..ebabcd6d 100644 --- a/jolt-core/src/test/resources/json/templatr/arrayObject.json +++ b/jolt-core/src/test/resources/json/templatr/arrayObject.json @@ -24,7 +24,7 @@ "context": {}, - "OVERWRITE": { + "OVERWRITR": { "objectMapFill": [ {"a": "b"},{"a": "b"},{"a": "b"},{"a": "b", "x": "y"} ], @@ -53,7 +53,7 @@ "objectArrayFill" : [ [],[],[],[0],[4] ], - "emptyMap" : { }, + "emptyMap" : {}, "emptyList" : [] } } diff --git a/jolt-core/src/test/resources/json/templatr/complexArrayLookup.json b/jolt-core/src/test/resources/json/templatr/complexArrayLookup.json index a9c9344f..71dc4147 100644 --- a/jolt-core/src/test/resources/json/templatr/complexArrayLookup.json +++ b/jolt-core/src/test/resources/json/templatr/complexArrayLookup.json @@ -58,7 +58,7 @@ } } }, - "OVERWRITE": { + "OVERWRITR": { "ContextDataValues": [ { "Age": [ diff --git a/jolt-core/src/test/resources/json/templatr/complexLookup.json b/jolt-core/src/test/resources/json/templatr/complexLookup.json index eab7174b..2e33ed0a 100644 --- a/jolt-core/src/test/resources/json/templatr/complexLookup.json +++ b/jolt-core/src/test/resources/json/templatr/complexLookup.json @@ -31,7 +31,7 @@ } } }, - "OVERWRITE": { + "OVERWRITR": { "ContextDataValues": { "Age": { "Value": "65orOver", diff --git a/jolt-core/src/test/resources/json/templatr/functions/arrayTests.json b/jolt-core/src/test/resources/json/templatr/functions/arrayTests.json new file mode 100644 index 00000000..edd8ad1d --- /dev/null +++ b/jolt-core/src/test/resources/json/templatr/functions/arrayTests.json @@ -0,0 +1,46 @@ +{ + "input": [ + { + "value": 2 + }, + { + "value": 2.0 + } + ], + + "spec": { + "*": { + "max": "=maxOf(@(2,[&1].value),^value,0)", + "min": "=minOf(@(2,[&1].value),^value,0.0)", + "abs": "=abs(^value)", + "double": "=toDouble(@(2,[&1].value))", + "integer": "=toInteger(@(2,[&1].value))", + "long": "=toLong(@(2,[&1].value))" + } + }, + + "context": { + "value" : -1.0 + }, + + "OVERWRITR": [ + { + "value": 2, + "min": -1.0, + "max": 2, + "abs": 1.0, + "double": 2.0, + "integer": 2, + "long": 2 + }, + { + "value": 2.0, + "min": -1.0, + "max": 2.0, + "abs": 1.0, + "integer": 2, + "double": 2.0, + "long": 2 + } + ] +} diff --git a/jolt-core/src/test/resources/json/templatr/functions/computationTest.json b/jolt-core/src/test/resources/json/templatr/functions/computationTest.json new file mode 100644 index 00000000..ef1485f9 --- /dev/null +++ b/jolt-core/src/test/resources/json/templatr/functions/computationTest.json @@ -0,0 +1,94 @@ +{ + "input": { + "ratings": { + "Leg4857": { + "minLabel": null, + "maxLabel": null + + }, + "EaseOfAssembly": {}, + "Quality": {}, + "Shrinkage69": { + "minLabel": null, + "maxLabel": null + }, + "Panels2564": { + "minLabel": null, + "maxLabel": null + }, + "Cleanliness": {} + } + } + , + "spec": { + "ratings": { + "*": { + "minLabel": "=minLabelComputation(^ratings.&1.valuesLabels)", + "maxLabel": "=maxLabelComputation(^ratings.&1.valuesLabels)" + } + } + }, + + "context": { + "cdvs": { }, + "tags": { }, + "additionalFields": { }, + "ratings": { + "Leg4857": { + "dimensionLabel": "Leg", + "valuesLabels": { + "0": "Too loose", + "1": "As expected", + "2": "Too tight" + } + }, + "EaseOfAssembly": { + "dimensionLabel": "do not use" + }, + "Quality": { + "dimensionLabel": "Quality" + }, + "Shrinkage69": { + "dimensionLabel": "Shrinkage", + "valuesLabels": { + "1": "More shrinkage", + "2": "As expected", + "3": "Less shrinkage" + } + }, + "Panels2564": { + "dimensionLabel": "Panels", + "valuesLabels": { + "1": "Highest", + "2": "Higher", + "3": "As expected", + "4": "Lower", + "5": "Lowest" + } + }, + "Cleanliness": { + "dimensionLabel": "do not use" + } + } + }, + + "DEFAULTR": { + "ratings" : { + "Leg4857" : { + "minLabel" : "Too loose", + "maxLabel" : "Too tight" + }, + "EaseOfAssembly" : { }, + "Quality" : { }, + "Shrinkage69" : { + "minLabel" : "More shrinkage", + "maxLabel" : "Less shrinkage" + }, + "Panels2564" : { + "minLabel" : "Highest", + "maxLabel" : "Lowest" + }, + "Cleanliness" : { } + } + } +} diff --git a/jolt-core/src/test/resources/json/templatr/functions/mathTests.json b/jolt-core/src/test/resources/json/templatr/functions/mathTests.json new file mode 100644 index 00000000..c0fd7ccd --- /dev/null +++ b/jolt-core/src/test/resources/json/templatr/functions/mathTests.json @@ -0,0 +1,45 @@ +{ + "input": { + "int": { + "value": -2 + }, + "double": { + "value": -2.0 + } + }, + + "spec": { + "int": { + "max": "=maxOf(@(1,value),^value,0)", + "min": "=minOf(@(1,value),^value,0.0)", + "double": "=toDouble(@(1,value))", + "value": "=abs" + + }, + "double": { + "max": "=maxOf(@(1,value),^value,0.0)", + "min": "=minOf(@(1,value),^value,0)", + "integer": "=toInteger(@(1,value))", + "value": "=abs" + } + }, + + "context": { + "value" : 1.0 + }, + + "OVERWRITR": { + "int": { + "value": 2, + "min": -2, + "max": 1.0, + "double": -2.0 + }, + "double": { + "value": 2.0, + "min": -2.0, + "max": 1.0, + "integer": -2 + } + } +} diff --git a/jolt-core/src/test/resources/json/templatr/functions/stringsTests.json b/jolt-core/src/test/resources/json/templatr/functions/stringsTests.json new file mode 100644 index 00000000..142dd474 --- /dev/null +++ b/jolt-core/src/test/resources/json/templatr/functions/stringsTests.json @@ -0,0 +1,42 @@ +{ + "input": { + "string": "the QuIcK brOwn fox" + }, + + "spec": { + "lower": { + "leading": "=toLower(@(2,string))", + "trailing": "=toLower(^value)", + "custom1": "=toLower(bazinga)", + "custom2": "=toLower('yabadabadoo')" + }, + "upper": { + "leading": "=toUpper(@(2,string))", + "trailing": "=toUpper(^value)", + "custom1": "=toUpper(bazinga)", + "custom2": "=toUpper('yabadabadoo')" + }, + "concat": "=concat(@(1,lower.leading),' ',@(1,lower.trailing))" + }, + + "context": { + "value" : "JumpeD OVeR THE laZy dog" + }, + + "OVERWRITR": { + "string" : "the QuIcK brOwn fox", + "lower": { + "leading": "the quick brown fox", + "trailing": "jumped over the lazy dog", + "custom1": "bazinga", + "custom2": "yabadabadoo" + }, + "upper": { + "leading": "THE QUICK BROWN FOX", + "trailing": "JUMPED OVER THE LAZY DOG", + "custom1": "BAZINGA", + "custom2": "YABADABADOO" + }, + "concat": "the quick brown fox jumped over the lazy dog" + } +} diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteral.json b/jolt-core/src/test/resources/json/templatr/mapLiteral.json index 98044a7e..2399c13e 100644 --- a/jolt-core/src/test/resources/json/templatr/mapLiteral.json +++ b/jolt-core/src/test/resources/json/templatr/mapLiteral.json @@ -24,7 +24,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleMap": { "0": 0, "1": 0, diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteralWithEmptyInput.json b/jolt-core/src/test/resources/json/templatr/mapLiteralWithEmptyInput.json index be41788a..93a12f15 100644 --- a/jolt-core/src/test/resources/json/templatr/mapLiteralWithEmptyInput.json +++ b/jolt-core/src/test/resources/json/templatr/mapLiteralWithEmptyInput.json @@ -17,7 +17,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleMap": { "6": 0, "7": 0, diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteralWithMissingInput.json b/jolt-core/src/test/resources/json/templatr/mapLiteralWithMissingInput.json index bfaff022..dce07c0b 100644 --- a/jolt-core/src/test/resources/json/templatr/mapLiteralWithMissingInput.json +++ b/jolt-core/src/test/resources/json/templatr/mapLiteralWithMissingInput.json @@ -1,6 +1,5 @@ { - "input": { - }, + "input": { }, "spec": { "simpleMap": { @@ -16,7 +15,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleMap": { "6": 0, "7": 0, diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteralWithNullInput.json b/jolt-core/src/test/resources/json/templatr/mapLiteralWithNullInput.json index ae07049b..d17940d5 100644 --- a/jolt-core/src/test/resources/json/templatr/mapLiteralWithNullInput.json +++ b/jolt-core/src/test/resources/json/templatr/mapLiteralWithNullInput.json @@ -17,7 +17,7 @@ "value": 5 }, - "OVERWRITE": { + "OVERWRITR": { "simpleMap": { "6": 0, "7": 0, diff --git a/jolt-core/src/test/resources/json/templatr/simple.json b/jolt-core/src/test/resources/json/templatr/simple.json index 035de887..c98650c0 100644 --- a/jolt-core/src/test/resources/json/templatr/simple.json +++ b/jolt-core/src/test/resources/json/templatr/simple.json @@ -30,7 +30,7 @@ } }, - "OVERWRITE": { + "OVERWRITR": { "thumbnail": { "Url": "http://test.com/0001/1234/photoThumb.jpg", "Id": "thumbnail", diff --git a/jolt-core/src/test/resources/json/templatr/simpleArray.json b/jolt-core/src/test/resources/json/templatr/simpleArray.json index 60c918ce..a51fff3f 100644 --- a/jolt-core/src/test/resources/json/templatr/simpleArray.json +++ b/jolt-core/src/test/resources/json/templatr/simpleArray.json @@ -30,7 +30,7 @@ } }, - "OVERWRITE": { + "OVERWRITR": { "thumbnail": [ { "Url": "http://test.com/0001/1234/photoThumb.jpg", diff --git a/jolt-core/src/test/resources/json/templatr/simpleArrayLookup.json b/jolt-core/src/test/resources/json/templatr/simpleArrayLookup.json index 4dc400e8..394edee5 100644 --- a/jolt-core/src/test/resources/json/templatr/simpleArrayLookup.json +++ b/jolt-core/src/test/resources/json/templatr/simpleArrayLookup.json @@ -63,7 +63,7 @@ } }, - "OVERWRITE": { + "OVERWRITR": { "photo": { "Sizes": { "thumbnail": [ diff --git a/jolt-core/src/test/resources/json/templatr/simpleLookup.json b/jolt-core/src/test/resources/json/templatr/simpleLookup.json index 303ac01e..6ec26200 100644 --- a/jolt-core/src/test/resources/json/templatr/simpleLookup.json +++ b/jolt-core/src/test/resources/json/templatr/simpleLookup.json @@ -41,7 +41,7 @@ } }, - "OVERWRITE": { + "OVERWRITR": { "photo": { "Sizes": { "thumbnail": { diff --git a/jolt-core/src/test/resources/json/templatr/simpleMapNullToArray.json b/jolt-core/src/test/resources/json/templatr/simpleMapNullToArray.json index 42f0ab7c..6805c50e 100644 --- a/jolt-core/src/test/resources/json/templatr/simpleMapNullToArray.json +++ b/jolt-core/src/test/resources/json/templatr/simpleMapNullToArray.json @@ -12,7 +12,7 @@ "context": {}, - "OVERWRITE": { + "OVERWRITR": { "simpleMap": [ 0,0 ] diff --git a/jolt-core/src/test/resources/json/templatr/simpleMapRuntimeNull.json b/jolt-core/src/test/resources/json/templatr/simpleMapRuntimeNull.json index c2b7d66b..dd35c03d 100644 --- a/jolt-core/src/test/resources/json/templatr/simpleMapRuntimeNull.json +++ b/jolt-core/src/test/resources/json/templatr/simpleMapRuntimeNull.json @@ -11,7 +11,7 @@ "context": {}, - "OVERWRITE": { + "OVERWRITR": { "simpleMap": null },