From ccfd40ee1143142195bf729c5b10a5e17e188612 Mon Sep 17 00:00:00 2001 From: Bivas Das Date: Sat, 9 Jul 2016 22:00:55 -0500 Subject: [PATCH] Implemented following features: Marked Function interface as @Deprecated it is work in progress and implementation outside jolt is discouraged if and when we port this function feature into other transforms, it'll move and/or change Implemented node specific overrides "+key": "..." + means OVERRIDE "~key": "..." _ means DEFAULT "_key": "..." ~ means DEFINE "key?": "..." ? means only act if input contains that key/index "key": [ "=func1", "func2" ] try sequencially from specified list of function until one of them succeeds Implemented more functions: toList - returns args as list isPresent - returns if arg[0] is present notNull - returns if arg[0] is not null isNull - returns if arg[0] is null firstElement - returns first element lastElement - returns last element elementAt - returns element at # index Improved following functions: max(list) - now also supports list min(list) - now also supports list List abs(list) - now also returns abs of all element in list List toInteger(list) - now also returns toInt of all element in list List toLong(list) - now also returns toLong of all element in list List toDouble(list) - now also returns toDouble of all element in list Optimized "?" behavior Internal refactoring done to move "?" logic deeper in core changed baseSpec#apply(...) signature to supply availability of input via Optional, which is known at lower level Matched signature change into Shiftr and Cardinality, now it is possible to introduce "?" into them if needed Addressed comments final rename of internal methods, classes, function before merging repackaged templatr as modifier (templatr + function = modifier) Change-Id: I080f8609de63b6e1968f8270d413bc13d3424327 --- .../jolt/CardinalityTransform.java | 3 +- .../jolt/{Templatr.java => Modifier.java} | 52 ++- .../java/com/bazaarvoice/jolt/Shiftr.java | 3 +- .../jolt/cardinality/CardinalitySpec.java | 5 +- .../jolt/chainr/spec/ChainrEntry.java | 11 +- .../jolt/common/ExecutionStrategy.java | 74 ++-- .../com/bazaarvoice/jolt/common/Optional.java | 22 + .../jolt/common/spec/BaseSpec.java | 3 +- .../jolt/{templatr => modifier}/DataType.java | 2 +- .../jolt/{templatr => modifier}/OpMode.java | 53 ++- .../TemplatrSpecBuilder.java | 18 +- .../jolt/modifier/function/Function.java | 178 ++++++++ .../function/FunctionArg.java | 45 +-- .../function/FunctionEvaluator.java | 20 +- .../jolt/modifier/function/Lists.java | 78 ++++ .../jolt/modifier/function/Math.java | 379 ++++++++++++++++++ .../function/Strings.java | 4 +- .../spec/ModifierCompositeSpec.java} | 39 +- .../jolt/modifier/spec/ModifierLeafSpec.java | 146 +++++++ .../spec/ModifierSpec.java} | 44 +- .../jolt/shiftr/spec/ShiftrCompositeSpec.java | 14 +- .../jolt/shiftr/spec/ShiftrLeafSpec.java | 3 +- .../jolt/templatr/function/Function.java | 87 ---- .../jolt/templatr/function/Math.java | 169 -------- .../jolt/templatr/spec/TemplatrLeafSpec.java | 122 ------ .../{TemplatrTest.java => ModifierTest.java} | 88 ++-- .../modifier/function/AbstractTester.java | 47 +++ .../jolt/modifier/function/ListsTest.java | 71 ++++ .../jolt/modifier/function/MathTest.java | 254 ++++++++++++ .../{templatr => modifier}/arrayLiteral.json | 0 .../arrayLiteralWithEmptyInput.json | 0 .../arrayLiteralWithMissingInput.json | 0 .../arrayLiteralWithNullInput.json | 0 .../{templatr => modifier}/arrayObject.json | 4 +- .../complexArrayLookup.json | 0 .../{templatr => modifier}/complexLookup.json | 0 .../functions/arrayTests.json | 4 +- .../functions/computationTest.json | 0 .../json/modifier/functions/mathTests.json | 48 +++ .../functions/stringsTests.json | 0 .../json/modifier/functions/valueTests.json | 37 ++ .../{templatr => modifier}/mapLiteral.json | 0 .../mapLiteralWithEmptyInput.json | 0 .../mapLiteralWithMissingInput.json | 0 .../mapLiteralWithNullInput.json | 0 .../json/{templatr => modifier}/simple.json | 0 .../{templatr => modifier}/simpleArray.json | 0 .../simpleArrayLookup.json | 0 .../json/modifier/simpleArrayOpOverride.json | 97 +++++ .../{templatr => modifier}/simpleLookup.json | 0 .../simpleMapNullToArray.json | 0 .../json/modifier/simpleMapOpOverride.json | 100 +++++ .../simpleMapRuntimeNull.json | 0 .../json/modifier/testListOfFunction.json | 45 +++ .../validation/specThatShouldFail.json | 0 .../json/modifier/valueCheckSimpleArray.json | 31 ++ .../valueCheckSimpleArrayEmptyInput.json | 30 ++ .../valueCheckSimpleArrayNullInput.json | 29 ++ .../json/modifier/valueCheckSimpleMap.json | 40 ++ .../valueCheckSimpleMapEmptyInput.json | 34 ++ .../valueCheckSimpleMapNullInput.json | 32 ++ .../json/templatr/functions/mathTests.json | 45 --- 62 files changed, 2015 insertions(+), 595 deletions(-) rename jolt-core/src/main/java/com/bazaarvoice/jolt/{Templatr.java => Modifier.java} (66%) rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr => modifier}/DataType.java (99%) rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr => modifier}/OpMode.java (74%) rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr => modifier}/TemplatrSpecBuilder.java (70%) create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Function.java rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr => modifier}/function/FunctionArg.java (74%) rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr => modifier}/function/FunctionEvaluator.java (78%) create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Lists.java create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Math.java rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr => modifier}/function/Strings.java (96%) rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr/spec/TemplatrCompositeSpec.java => modifier/spec/ModifierCompositeSpec.java} (84%) create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierLeafSpec.java rename jolt-core/src/main/java/com/bazaarvoice/jolt/{templatr/spec/TemplatrSpec.java => modifier/spec/ModifierSpec.java} (71%) delete mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Function.java delete mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java delete mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java rename jolt-core/src/test/java/com/bazaarvoice/jolt/{TemplatrTest.java => ModifierTest.java} (71%) create mode 100644 jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/AbstractTester.java create mode 100644 jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/ListsTest.java create mode 100644 jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/MathTest.java rename jolt-core/src/test/resources/json/{templatr => modifier}/arrayLiteral.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/arrayLiteralWithEmptyInput.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/arrayLiteralWithMissingInput.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/arrayLiteralWithNullInput.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/arrayObject.json (94%) rename jolt-core/src/test/resources/json/{templatr => modifier}/complexArrayLookup.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/complexLookup.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/functions/arrayTests.json (87%) rename jolt-core/src/test/resources/json/{templatr => modifier}/functions/computationTest.json (100%) create mode 100644 jolt-core/src/test/resources/json/modifier/functions/mathTests.json rename jolt-core/src/test/resources/json/{templatr => modifier}/functions/stringsTests.json (100%) create mode 100644 jolt-core/src/test/resources/json/modifier/functions/valueTests.json rename jolt-core/src/test/resources/json/{templatr => modifier}/mapLiteral.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/mapLiteralWithEmptyInput.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/mapLiteralWithMissingInput.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/mapLiteralWithNullInput.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/simple.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/simpleArray.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/simpleArrayLookup.json (100%) create mode 100644 jolt-core/src/test/resources/json/modifier/simpleArrayOpOverride.json rename jolt-core/src/test/resources/json/{templatr => modifier}/simpleLookup.json (100%) rename jolt-core/src/test/resources/json/{templatr => modifier}/simpleMapNullToArray.json (100%) create mode 100644 jolt-core/src/test/resources/json/modifier/simpleMapOpOverride.json rename jolt-core/src/test/resources/json/{templatr => modifier}/simpleMapRuntimeNull.json (100%) create mode 100644 jolt-core/src/test/resources/json/modifier/testListOfFunction.json rename jolt-core/src/test/resources/json/{templatr => modifier}/validation/specThatShouldFail.json (100%) create mode 100644 jolt-core/src/test/resources/json/modifier/valueCheckSimpleArray.json create mode 100644 jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayEmptyInput.json create mode 100644 jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayNullInput.json create mode 100644 jolt-core/src/test/resources/json/modifier/valueCheckSimpleMap.json create mode 100644 jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapEmptyInput.json create mode 100644 jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapNullInput.json delete mode 100644 jolt-core/src/test/resources/json/templatr/functions/mathTests.json diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/CardinalityTransform.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/CardinalityTransform.java index 5eb60fd4..02945dce 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/CardinalityTransform.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/CardinalityTransform.java @@ -16,6 +16,7 @@ package com.bazaarvoice.jolt; import com.bazaarvoice.jolt.cardinality.CardinalityCompositeSpec; +import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.exception.SpecException; @@ -229,7 +230,7 @@ public CardinalityTransform( Object spec ) { @Override public Object transform( Object input ) { - rootSpec.apply( ROOT_KEY, input, new WalkedPath(), null, null ); + rootSpec.apply( ROOT_KEY, Optional.of( input ), new WalkedPath(), null, null ); return input; } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/Modifier.java similarity index 66% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/Modifier.java index ce9bfff9..24eac641 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/Modifier.java @@ -16,15 +16,17 @@ package com.bazaarvoice.jolt; +import com.bazaarvoice.jolt.common.Optional; 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.Math; -import com.bazaarvoice.jolt.templatr.function.Strings; -import com.bazaarvoice.jolt.templatr.spec.TemplatrCompositeSpec; +import com.bazaarvoice.jolt.modifier.OpMode; +import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder; +import com.bazaarvoice.jolt.modifier.function.Function; +import com.bazaarvoice.jolt.modifier.function.Lists; +import com.bazaarvoice.jolt.modifier.function.Math; +import com.bazaarvoice.jolt.modifier.function.Strings; +import com.bazaarvoice.jolt.modifier.spec.ModifierCompositeSpec; import java.util.Collections; import java.util.HashMap; @@ -33,7 +35,7 @@ /** * Base Templatr transform that to behave differently based on provided opMode */ -public abstract class Templatr implements SpecDriven, ContextualTransform { +public abstract class Modifier implements SpecDriven, ContextualTransform { private static final Map STOCK_FUNCTIONS = new HashMap<>( ); @@ -42,18 +44,28 @@ public abstract class Templatr implements SpecDriven, ContextualTransform { 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( "min", new Math.min() ); + STOCK_FUNCTIONS.put( "max", new Math.max() ); + 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() ); + + STOCK_FUNCTIONS.put( "noop", Function.noop ); + STOCK_FUNCTIONS.put( "isPresent", Function.isPresent ); + STOCK_FUNCTIONS.put( "notNull", Function.notNull ); + STOCK_FUNCTIONS.put( "isNull", Function.isNull ); + + STOCK_FUNCTIONS.put( "firstElement", new Lists.firstElement() ); + STOCK_FUNCTIONS.put( "lastElement", new Lists.lastElement() ); + STOCK_FUNCTIONS.put( "elementAt", new Lists.elementAt() ); + STOCK_FUNCTIONS.put( "toList", new Lists.toList() ); } - private final TemplatrCompositeSpec rootSpec; + private final ModifierCompositeSpec rootSpec; @SuppressWarnings( "unchecked" ) - private Templatr(Object spec, OpMode opMode, Map functionsMap) { + private Modifier( Object spec, OpMode opMode, Map functionsMap ) { if ( spec == null ){ throw new SpecException( opMode.name() + " expected a spec of Map type, got 'null'." ); } @@ -67,7 +79,7 @@ private Templatr(Object spec, OpMode opMode, Map functionsMap) functionsMap = Collections.unmodifiableMap( functionsMap ); TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder( opMode, functionsMap ); - rootSpec = new TemplatrCompositeSpec( ROOT_KEY, (Map) spec, opMode, templatrSpecBuilder ); + rootSpec = new ModifierCompositeSpec( ROOT_KEY, (Map) spec, opMode, templatrSpecBuilder ); } @Override @@ -80,15 +92,15 @@ public Object transform( final Object input, final Map context ) WalkedPath walkedPath = new WalkedPath(); walkedPath.add( input, rootLpe ); - rootSpec.apply( ROOT_KEY, input, walkedPath, null, contextWrapper ); + rootSpec.apply( ROOT_KEY, Optional.of( input), walkedPath, null, contextWrapper ); return input; } /** - * This variant of templatr creates the key/index is missing, + * This variant of modifier creates the key/index is missing, * and overwrites the value if present */ - public static final class Overwritr extends Templatr { + public static final class Overwritr extends Modifier { public Overwritr( Object spec ) { this( spec, STOCK_FUNCTIONS ); @@ -100,9 +112,9 @@ public Overwritr( Object spec, Map functionsMap ) { } /** - * This variant of templatr only writes when the key/index is missing + * This variant of modifier only writes when the key/index is missing */ - public static final class Definr extends Templatr { + public static final class Definr extends Modifier { public Definr( final Object spec ) { this( spec, STOCK_FUNCTIONS ); @@ -114,9 +126,9 @@ public Definr( Object spec, Map functionsMap ) { } /** - * This variant of templatr only writes when the key/index is missing or the value is null + * This variant of modifier only writes when the key/index is missing or the value is null */ - public static class Defaultr extends Templatr { + public static class Defaultr extends Modifier { public Defaultr( final Object spec ) { this( spec, STOCK_FUNCTIONS ); diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java index 3b476e06..aa80a6a6 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java @@ -15,6 +15,7 @@ */ package com.bazaarvoice.jolt; +import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.exception.SpecException; @@ -501,7 +502,7 @@ public Object transform( Object input ) { WalkedPath walkedPath = new WalkedPath(); walkedPath.add( input, rootLpe ); - rootSpec.apply( ROOT_KEY, input, walkedPath, output, null ); + rootSpec.apply( ROOT_KEY, Optional.of( input ), walkedPath, output, null ); return output.get( ROOT_KEY ); } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalitySpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalitySpec.java index 1c75f69f..de597501 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalitySpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalitySpec.java @@ -15,6 +15,7 @@ */ package com.bazaarvoice.jolt.cardinality; +import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.pathelement.AtPathElement; import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.common.pathelement.MatchablePathElement; @@ -107,8 +108,8 @@ else if ( key.contains(STAR) ) { public abstract boolean applyCardinality( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer ); @Override - public boolean apply( final String inputKey, final Object input, final WalkedPath walkedPath, final Map output, final Map context ) { - return applyCardinality( inputKey, input, walkedPath, output ); + public boolean apply( final String inputKey, final Optional inputOptional, final WalkedPath walkedPath, final Map output, final Map context ) { + return applyCardinality( inputKey, inputOptional.get(), walkedPath, output ); } @Override diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/chainr/spec/ChainrEntry.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/chainr/spec/ChainrEntry.java index ddf88605..4c12294f 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/chainr/spec/ChainrEntry.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/chainr/spec/ChainrEntry.java @@ -17,12 +17,13 @@ import com.bazaarvoice.jolt.CardinalityTransform; import com.bazaarvoice.jolt.Chainr; +import com.bazaarvoice.jolt.Defaultr; import com.bazaarvoice.jolt.JoltTransform; +import com.bazaarvoice.jolt.Modifier; import com.bazaarvoice.jolt.Removr; import com.bazaarvoice.jolt.Shiftr; import com.bazaarvoice.jolt.Sortr; import com.bazaarvoice.jolt.SpecDriven; -import com.bazaarvoice.jolt.Templatr; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.utils.StringTools; @@ -48,10 +49,10 @@ public class ChainrEntry { static { HashMap temp = new HashMap<>(); temp.put( "shift", Shiftr.class.getName() ); - temp.put( "default", com.bazaarvoice.jolt.Defaultr.class.getName() ); - temp.put( "overwrite-beta", Templatr.Overwritr.class.getName() ); - temp.put( "default-beta", Templatr.Defaultr.class.getName() ); - temp.put( "define-beta", Templatr.Definr.class.getName() ); + temp.put( "default", Defaultr.class.getName() ); + temp.put( "modify-overwrite-beta", Modifier.Overwritr.class.getName() ); + temp.put( "modify-default-beta", Modifier.Defaultr.class.getName() ); + temp.put( "modify-define-beta", Modifier.Definr.class.getName() ); temp.put( "remove", Removr.class.getName() ); temp.put( "sort", Sortr.class.getName() ); temp.put( "cardinality", CardinalityTransform.class.getName() ); diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/ExecutionStrategy.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/ExecutionStrategy.java index 6a45a1fb..29594336 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/ExecutionStrategy.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/ExecutionStrategy.java @@ -40,8 +40,8 @@ void processMap( OrderedCompositeSpec spec, Map inputMap, Walked // Do not work if the value is missing in the input map if ( inputMap.containsKey( key ) ) { - Object subInput = inputMap.get( key ); - spec.getLiteralChildren().get( key ).apply( key, subInput, walkedPath, output, context ); + Optional subInputOptional = Optional.of( inputMap.get( key ) ); + spec.getLiteralChildren().get( key ).apply( key, subInputOptional, walkedPath, output, context ); } } } @@ -49,6 +49,7 @@ void processMap( OrderedCompositeSpec spec, Map inputMap, Walked @Override void processList( OrderedCompositeSpec spec, List inputList, WalkedPath walkedPath, Map output, Map context ) { + Integer originalSize = walkedPath.lastElement().getOrigSize().get(); for( String key : spec.getLiteralChildren().keySet() ) { int keyInt = Integer.MAX_VALUE; @@ -66,9 +67,16 @@ void processList( OrderedCompositeSpec spec, List inputList, WalkedPath if ( keyInt < inputList.size() ) { Object subInput = inputList.get( keyInt ); + Optional subInputOptional; + if ( subInput == null && originalSize != null && keyInt >= originalSize ) { + subInputOptional = Optional.empty(); + } + else { + subInputOptional = Optional.of( subInput ); + } // we know the .get(key) will not return null, because we are iterating over its keys - spec.getLiteralChildren().get( key ).apply( key, subInput, walkedPath, output, context ); + spec.getLiteralChildren().get( key ).apply( key, subInputOptional, walkedPath, output, context ); } } } @@ -78,10 +86,9 @@ void processScalar( OrderedCompositeSpec spec, String scalarInput, WalkedPath wa BaseSpec literalChild = spec.getLiteralChildren().get( scalarInput ); if ( literalChild != null ) { - literalChild.apply( scalarInput, null, walkedPath, output, context ); + literalChild.apply( scalarInput, Optional.empty(), walkedPath, output, context ); } } - }, /** @@ -98,17 +105,18 @@ void processMap( OrderedCompositeSpec spec, Map inputMap, Walked // if the input in not available in the map us null or else get value, // then lookup and place a defined value from spec there - Object subInput = null; + Optional subInputOptional = Optional.empty(); if ( inputMap.containsKey( key ) ) { - subInput = inputMap.get( key ); + subInputOptional = Optional.of( inputMap.get( key )); } - spec.getLiteralChildren().get( key ).apply( key, subInput, walkedPath, output, context ); + spec.getLiteralChildren().get( key ).apply( key, subInputOptional, walkedPath, output, context ); } } @Override void processList( OrderedCompositeSpec spec, List inputList, WalkedPath walkedPath, Map output, Map context ) { + Integer originalSize = walkedPath.lastElement().getOrigSize().get(); for( String key : spec.getLiteralChildren().keySet() ) { int keyInt = Integer.MAX_VALUE; @@ -124,12 +132,15 @@ void processList( OrderedCompositeSpec spec, List inputList, WalkedPath // if the input in not available in the list use null or else get value, // then lookup and place a default value as defined in spec there - Object subInput = null; + Optional subInputOptional = Optional.empty(); if ( keyInt < inputList.size() ) { - subInput = inputList.get( keyInt ); + Object subInput = inputList.get( keyInt ); + if ( subInput != null || originalSize == null || keyInt < originalSize ) { + subInputOptional = Optional.of( subInput ); + } } // we know the .get(key) will not return null, because we are iterating over its keys - spec.getLiteralChildren().get( key ).apply( key, subInput, walkedPath, output, context ); + spec.getLiteralChildren().get( key ).apply( key, subInputOptional, walkedPath, output, context ); } } @@ -150,24 +161,32 @@ void processMap( OrderedCompositeSpec spec, Map inputMap, Walked // Iterate over the whole entrySet rather than the keyset with follow on gets of the values for( Map.Entry inputEntry : inputMap.entrySet() ) { - applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, inputEntry.getKey(), inputEntry.getValue(), context ); + applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, inputEntry.getKey(), Optional.of( inputEntry.getValue() ), context ); } } @Override void processList( OrderedCompositeSpec spec, List inputList, WalkedPath walkedPath, Map output, Map context ) { + Integer originalSize = walkedPath.lastElement().getOrigSize().get(); for (int index = 0; index < inputList.size(); index++) { Object subInput = inputList.get( index ); String subKeyStr = Integer.toString( index ); + Optional subInputOptional; + if ( subInput == null && originalSize != null && index >= originalSize ) { + subInputOptional = Optional.empty(); + } + else { + subInputOptional = Optional.of( subInput ); + } - applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, subKeyStr, subInput, context ); + applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, subKeyStr, subInputOptional, context ); } } @Override void processScalar( OrderedCompositeSpec spec, String scalarInput, WalkedPath walkedPath, Map output, Map context ) { - applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, scalarInput, null, context ); + applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, scalarInput, Optional.empty(), context ); } }, @@ -181,24 +200,32 @@ void processMap( OrderedCompositeSpec spec, Map inputMap, Walked // Iterate over the whole entrySet rather than the keyset with follow on gets of the values for( Map.Entry inputEntry : inputMap.entrySet() ) { - applyKeyToLiteralAndComputed( spec, inputEntry.getKey(), inputEntry.getValue(), walkedPath, output, context ); + applyKeyToLiteralAndComputed( spec, inputEntry.getKey(), Optional.of( inputEntry.getValue() ), walkedPath, output, context ); } } @Override void processList( OrderedCompositeSpec spec, List inputList, WalkedPath walkedPath, Map output, Map context ) { + Integer originalSize = walkedPath.lastElement().getOrigSize().get(); for (int index = 0; index < inputList.size(); index++) { Object subInput = inputList.get( index ); String subKeyStr = Integer.toString( index ); + Optional subInputOptional; + if ( subInput == null && originalSize != null && index >= originalSize ) { + subInputOptional = Optional.empty(); + } + else { + subInputOptional = Optional.of( subInput ); + } - applyKeyToLiteralAndComputed( spec, subKeyStr, subInput, walkedPath, output, context ); + applyKeyToLiteralAndComputed( spec, subKeyStr, subInputOptional, walkedPath, output, context ); } } @Override void processScalar( OrderedCompositeSpec spec, String scalarInput, WalkedPath walkedPath, Map output, Map context ) { - applyKeyToLiteralAndComputed( spec, scalarInput, null, walkedPath, output, context ); + applyKeyToLiteralAndComputed( spec, scalarInput, Optional.empty(), walkedPath, output, context ); } }, @@ -248,7 +275,8 @@ void processScalar( OrderedCompositeSpec spec, String scalarInput, WalkedPath wa }; @SuppressWarnings( "unchecked" ) - public void process( OrderedCompositeSpec spec, Object input, WalkedPath walkedPath, Map output, Map context ) { + public void process( OrderedCompositeSpec spec, Optional inputOptional, WalkedPath walkedPath, Map output, Map context ) { + Object input = inputOptional.get(); if ( input instanceof Map) { processMap( spec, (Map) input, walkedPath, output, context ); } @@ -276,27 +304,27 @@ else if ( input != null ) { * n is number of input keys * c is number of computed children */ - private static void applyKeyToLiteralAndComputed( T spec, String subKeyStr, Object subInput, WalkedPath walkedPath, Map output, Map context ) { + private static void applyKeyToLiteralAndComputed( T spec, String subKeyStr, Optional subInputOptional, WalkedPath walkedPath, Map output, Map context ) { BaseSpec literalChild = spec.getLiteralChildren().get( subKeyStr ); // if the subKeyStr found a literalChild, then we do not have to try to match any of the computed ones if ( literalChild != null ) { - literalChild.apply( subKeyStr, subInput, walkedPath, output, context ); + literalChild.apply( subKeyStr, subInputOptional, walkedPath, output, context ); } else { // If no literal spec key matched, iterate through all the getComputedChildren() - applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, subKeyStr, subInput, context ); + applyKeyToComputed( spec.getComputedChildren(), walkedPath, output, subKeyStr, subInputOptional, context ); } } - private static void applyKeyToComputed( List computedChildren, WalkedPath walkedPath, Map output, String subKeyStr, Object subInput, Map context ) { + private static void applyKeyToComputed( List computedChildren, WalkedPath walkedPath, Map output, String subKeyStr, Optional subInputOptional, Map context ) { // Iterate through all the getComputedChildren() until we find a match // This relies upon the getComputedChildren() having already been sorted in priority order for ( BaseSpec computedChild : computedChildren ) { // if the computed key does not match it will quickly return false - if ( computedChild.apply( subKeyStr, subInput, walkedPath, output, context ) ) { + if ( computedChild.apply( subKeyStr, subInputOptional, walkedPath, output, context ) ) { break; } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/Optional.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/Optional.java index 3006bb31..6a0f8995 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/Optional.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/Optional.java @@ -54,4 +54,26 @@ public T get() { public boolean isPresent() { return ! abs; } + + @Override + public boolean equals( final Object obj ) { + if(!(obj instanceof Optional)) { + return false; + } + Optional that = (Optional) obj; + return that == EMPTY || ( + this.abs == that.abs && ( + (this.obj == null && that.obj == null) || ( + this.obj != null && + that.obj != null && + this.obj.equals( that.obj ) + ) + ) + ); + } + + @Override + public String toString() { + return "Optional<" + (abs?"?":obj==null?"?":obj.getClass().getSimpleName()) + ">: present=" + !abs + ", value=(" + obj + ")"; + } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/spec/BaseSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/spec/BaseSpec.java index f1c292ca..e768ff21 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/spec/BaseSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/spec/BaseSpec.java @@ -16,6 +16,7 @@ package com.bazaarvoice.jolt.common.spec; +import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.pathelement.MatchablePathElement; import com.bazaarvoice.jolt.common.tree.WalkedPath; @@ -44,6 +45,6 @@ public interface BaseSpec { * * @return true if this this spec "handles" the inputkey such that no sibling specs need to see it */ - boolean apply( final String inputKey, final Object input, final WalkedPath walkedPath, final Map output, final Map context ); + boolean apply( final String inputKey, final Optional inputOptional, final WalkedPath walkedPath, final Map output, final Map context ); } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/DataType.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/DataType.java similarity index 99% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/DataType.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/DataType.java index e46f1522..9fbc021d 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/DataType.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/DataType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr; +package com.bazaarvoice.jolt.modifier; import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.tree.WalkedPath; diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/OpMode.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/OpMode.java similarity index 74% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/OpMode.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/OpMode.java index 1424e7b7..11bc7adf 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/OpMode.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/OpMode.java @@ -14,8 +14,11 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr; +package com.bazaarvoice.jolt.modifier; +import com.bazaarvoice.jolt.exception.SpecException; + +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,7 +38,8 @@ * */ public enum OpMode { - OVERWRITR { + + OVERWRITR("+") { @Override public boolean isApplicable( final Map source, final String key ) { return super.isApplicable(source, key); @@ -45,7 +49,7 @@ public boolean isApplicable( final List source, final int reqIndex , int origSi return super.isApplicable(source, reqIndex , origSize); } }, - DEFAULTR { + DEFAULTR("~") { @Override public boolean isApplicable( final Map source, final String key ) { return super.isApplicable( source, key ) && source.get( key ) == null; @@ -55,7 +59,7 @@ public boolean isApplicable( final List source, final int reqIndex, int origSize return super.isApplicable(source, reqIndex, origSize ) && source.get( reqIndex ) == null; } }, - DEFINER { + DEFINER("_") { @Override public boolean isApplicable( final Map source, final String key ) { return super.isApplicable(source, key) && !source.containsKey( key ); @@ -68,6 +72,24 @@ public boolean isApplicable( final List source, final int reqIndex, int origSize } }; + /** + * Identifier OP prefix that is defined in SPEC + */ + private String op; + + private OpMode( final String op ) { + this.op = op; + } + + public String getOp() { + return op; + } + + public String toString() { + return op + "modify"; + } + + /** * Given a source map and a input key returns true if it is ok to go ahead with * write operation given a specific opMode @@ -83,4 +105,27 @@ public boolean isApplicable(Map source, String key) { public boolean isApplicable(List source, int reqIndex, int origSize) { return source != null && reqIndex >= 0 && origSize >= 0; } + + /** + * Static validity checker and instance getter from given op String + */ + private static Map opModeMap; + + static { + opModeMap = new HashMap<>( ); + opModeMap.put( OVERWRITR.op, OVERWRITR ); + opModeMap.put( DEFAULTR.op, DEFAULTR ); + opModeMap.put( DEFINER.op, DEFINER ); + } + + public static boolean isValid(String op) { + return opModeMap.containsKey( op ); + } + + public static OpMode from(String op) { + if ( isValid( op ) ) { + return opModeMap.get( op ); + } + throw new SpecException( "OpMode " + op + " is not valid" ); + } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/TemplatrSpecBuilder.java similarity index 70% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/TemplatrSpecBuilder.java index 625906cf..42ae8a18 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/TemplatrSpecBuilder.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/TemplatrSpecBuilder.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr; +package com.bazaarvoice.jolt.modifier; 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.TemplatrLeafSpec; -import com.bazaarvoice.jolt.templatr.spec.TemplatrSpec; +import com.bazaarvoice.jolt.modifier.function.Function; +import com.bazaarvoice.jolt.modifier.spec.ModifierCompositeSpec; +import com.bazaarvoice.jolt.modifier.spec.ModifierLeafSpec; +import com.bazaarvoice.jolt.modifier.spec.ModifierSpec; import java.util.Map; -public class TemplatrSpecBuilder extends SpecBuilder { +public class TemplatrSpecBuilder extends SpecBuilder { public static final String CARET = "^"; public static final String AT = "@"; @@ -41,12 +41,12 @@ public TemplatrSpecBuilder( OpMode opMode, Map functionsMap ) @Override @SuppressWarnings( "unchecked" ) - public TemplatrSpec createSpec( final String lhs, final Object rhs ) { + public ModifierSpec createSpec( final String lhs, final Object rhs ) { if( rhs instanceof Map && (!( (Map) rhs ).isEmpty())) { - return new TemplatrCompositeSpec(lhs, (Map)rhs, opMode, this ); + return new ModifierCompositeSpec(lhs, (Map)rhs, opMode, this ); } else { - return new TemplatrLeafSpec( lhs, rhs, opMode, functionsMap ); + return new ModifierLeafSpec( lhs, rhs, opMode, functionsMap ); } } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Function.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Function.java new file mode 100644 index 00000000..69c331d9 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Function.java @@ -0,0 +1,178 @@ +/* + * 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.modifier.function; + +import com.bazaarvoice.jolt.common.Optional; + +/** + * Modifier 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 + * + * min - returns the min of all numbers provided in the arguments, non-numbers are ignored + * max - 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: + * { "value1": "xyz" } --- note: string, not number + * { "value1": "1.0" } --- note: string, not number + * + * spec: + * { "value1": "=abs" } --- fails silently + * { "value2": "=abs" } + * + * output: + * { "value1": "xyz", "value2": "1" } --- note: "absValue": null is not inserted + * + * + * This is work in progress, and probably will be changed in future releases. Hence it is marked for + * removal as it'll eventually be moved to a different package as the Function feature is baked into + * other transforms as well. In short this interface is not yet ready to be implemented outside jolt! + * + */ + +@Deprecated +public interface Function { + + public Optional apply(Object... args); + + /** + * Does nothing + * + * spec - "key": "=noop" + * + * will cause the key to remain unchanged + */ + public static final Function noop = new Function() { + @Override + public Optional apply( final Object... args ) { + return Optional.empty(); + } + }; + + /** + * Returns the first argument, null or otherwise + * + * spec - "key": [ "=isPresent", "otherValue" ] + * + * input - "key": null + * output - "key": null + * + * input - "key": "value" + * output - "key": "value" + * + * input - key is missing + * output - "key": "otherValue" + * + */ + public static final Function isPresent = new Function() { + @Override + public Optional apply( final Object... args ) { + if (args.length == 0) { + return Optional.empty(); + } + return Optional.of( args[0] ); + } + }; + + /** + * Returns the first argument if in not null + * + * spec - "key": ["=notNull", "otherValue" ] + * + * input - "key": null + * output - "key": "otherValue" + * + * input - "key": "value" + * output - "key": "value" + * + */ + public static final Function notNull = new Function() { + @Override + public Optional apply( final Object... args ) { + if (args.length == 0 || args[0] == null) { + return Optional.empty(); + } + return Optional.of( args[0] ); + } + }; + + /** + * Returns the first argument if it is null + * + * spec - "key": ["=inNull", "otherValue" ] + * + * input - "key": null + * output - "key": null + * + * input - "key": "value" + * output - "key": "otherValue" + * + */ + public static final Function isNull = new Function() { + @Override + public Optional apply( final Object... args ) { + if (args.length == 0 || args[0] != null) { + return Optional.empty(); + } + return Optional.of( args[0] ); + } + }; +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/FunctionArg.java similarity index 74% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/FunctionArg.java index 12261f46..bb04a5e2 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionArg.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/FunctionArg.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr.function; +package com.bazaarvoice.jolt.modifier.function; import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.PathEvaluatingTraversal; @@ -67,32 +67,29 @@ public Optional evaluateArg( final WalkedPath walkedPath, final Map optional = Math.toNumber( arg ); + if(optional.isPresent()) { + return new LiteralArg( optional.get() ); + } + return new LiteralArg( arg ); + } + } + else { + return new LiteralArg( obj ); } } else { diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/FunctionEvaluator.java similarity index 78% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/FunctionEvaluator.java index 7e40de02..0f3e45ce 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/FunctionEvaluator.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/FunctionEvaluator.java @@ -14,34 +14,39 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr.function; +package com.bazaarvoice.jolt.modifier.function; import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.tree.WalkedPath; import java.util.Map; +@SuppressWarnings( "deprecated" ) public class FunctionEvaluator { - public static FunctionEvaluator of(Function function, FunctionArg[] functionArgs) { + public static FunctionEvaluator forFunctionEvaluation( Function function, FunctionArg... functionArgs ) { return new FunctionEvaluator( function, functionArgs ); } + public static FunctionEvaluator forArgEvaluation( FunctionArg functionArgs ) { + return new FunctionEvaluator( null, 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 ) { + 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(); + public Optional evaluate(Optional inputOptional, WalkedPath walkedPath, Map context) { + Optional valueOptional = Optional.empty(); try { Object[] evaluatedArgs; @@ -50,13 +55,14 @@ public Optional evaluate(Object input, WalkedPath walkedPath, Map 0 ) { evaluatedArgs = evaluateArgsValue( functionArgs, context, walkedPath ); valueOptional = function.apply( evaluatedArgs ); } // "key": "=abs" else { - evaluatedArgs = new Object[] {input}; // pass current value as arg + // pass current value as arg + evaluatedArgs = inputOptional.isPresent() ? new Object[] {inputOptional.get()} : new Object[0]; valueOptional = function.apply( evaluatedArgs ); } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Lists.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Lists.java new file mode 100644 index 00000000..f5d5a0e1 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Lists.java @@ -0,0 +1,78 @@ +/* + * 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.modifier.function; + +import com.bazaarvoice.jolt.common.Optional; + +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings( "deprecated" ) +public class Lists { + + public static final class firstElement implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 1 && args[0] instanceof List) { + List input = (List)args[0]; + if(input.size() > 0) { + return Optional.of( input.get( 0 ) ); + } + } + return Optional.empty(); + } + } + + public static final class lastElement implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 1 && args[0] instanceof List) { + List input = (List)args[0]; + if(input.size() > 0) { + return Optional.of( input.get( input.size() - 1 ) ); + } + } + return Optional.empty(); + } + } + + public static final class elementAt implements Function { + @Override + public Optional apply( final Object... args ) { + if ( args.length == 2 && args[0] instanceof List && args[1] instanceof Integer ) { + int index = (int) args[1]; + List input = (List) args[0]; + if ( input.size() > index ) { + return Optional.of( input.get( index ) ); + } + } + return Optional.empty(); + } + } + + public static final class toList implements Function { + @Override + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + else { + return Optional.of( Arrays.asList( args )); + } + } + } +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Math.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Math.java new file mode 100644 index 00000000..573b39bc --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Math.java @@ -0,0 +1,379 @@ +/* + * 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.modifier.function; + +import com.bazaarvoice.jolt.common.Optional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings( "deprecated" ) +public class Math { + + /** + * Given a list of objects, returns the max value in its appropriate type + * also, interprets String as Number and returns appropriately + * + * max(1,2l,3d) == Optional.of(3d) + * max(1,2l,"3.0") == Optional.of(3.0) + * max("a", "b", "c") == Optional.empty() + * max([]) == Optional.empty() + */ + public static Optional max( List args ) { + if(args.size() == 0) { + return Optional.empty(); + } + + Integer maxInt = Integer.MIN_VALUE; + Double maxDouble = -(Double.MAX_VALUE); + Long maxLong = Long.MIN_VALUE; + boolean found = false; + + for(Object arg: args) { + if(arg instanceof Integer) { + maxInt = java.lang.Math.max( maxInt, (Integer) arg ); + found = true; + } + else if(arg instanceof Double) { + maxDouble = java.lang.Math.max( maxDouble, (Double) arg ); + found = true; + } + else if(arg instanceof Long) { + maxLong = java.lang.Math.max(maxLong, (Long) arg); + found = true; + } + else if(arg instanceof String) { + Optional optional = toNumber( arg ); + if(optional.isPresent()) { + arg = optional.get(); + if(arg instanceof Integer) { + maxInt = java.lang.Math.max( maxInt, (Integer) arg ); + found = true; + } + else if(arg instanceof Double) { + maxDouble = java.lang.Math.max( maxDouble, (Double) arg ); + found = true; + } + else if(arg instanceof Long) { + maxLong = java.lang.Math.max(maxLong, (Long) arg); + found = true; + } + } + } + } + if(!found) { + return Optional.empty(); + } + + // explicit getter method calls to avoid runtime autoboxing + // autoBoxing will cause it to return the different type + // check MathTest#testAutoBoxingIssue for example + if(maxInt.longValue() >= maxDouble.longValue() && maxInt.longValue() >= maxLong) { + return Optional.of(maxInt); + } + else if(maxLong >= maxDouble.longValue()) { + return Optional.of(maxLong); + } + else { + return Optional.of(maxDouble); + } + } + + /** + * Given a list of objects, returns the min value in its appropriate type + * also, interprets String as Number and returns appropriately + * + * min(1d,2l,3) == Optional.of(1d) + * min("1.0",2l,d) == Optional.of(1.0) + * min("a", "b", "c") == Optional.empty() + * min([]) == Optional.empty() + */ + public static Optional min( List args ) { + if(args.size() == 0) { + return Optional.empty(); + } + Integer minInt = Integer.MAX_VALUE; + Double minDouble = Double.MAX_VALUE; + Long minLong = Long.MAX_VALUE; + boolean found = false; + + for(Object arg: args) { + if(arg instanceof Integer) { + minInt = java.lang.Math.min( minInt, (Integer) arg ); + found = true; + } + else if(arg instanceof Double) { + minDouble = java.lang.Math.min( minDouble, (Double) arg ); + found = true; + } + else if(arg instanceof Long) { + minLong = java.lang.Math.min( minLong, (Long) arg ); + found = true; + } + else if(arg instanceof String) { + Optional optional = toNumber( arg ); + if(optional.isPresent()) { + arg = optional.get(); + if(arg instanceof Integer) { + minInt = java.lang.Math.min( minInt, (Integer) arg ); + found = true; + } + else if(arg instanceof Double) { + minDouble = java.lang.Math.min( minDouble, (Double) arg ); + found = true; + } + else if(arg instanceof Long) { + minLong = java.lang.Math.min(minLong, (Long) arg); + found = true; + } + } + } + } + if(!found) { + return Optional.empty(); + } + // explicit getter method calls to avoid runtime autoboxing + if(minInt.longValue() <= minDouble.longValue() && minInt.longValue() <= minLong) { + return Optional.of(minInt); + } + else if(minLong <= minDouble.longValue()) { + return Optional.of(minLong); + } + else { + return Optional.of(minDouble); + } + } + + /** + * Given any object, returns, if possible. its absolute value wrapped in Optional + * Interprets String as Number + * + * abs("-123") == Optional.of(123) + * abs("123") == Optional.of(123) + * abs("12.3") == Optional.of(12.3) + * + * abs("abc") == Optional.empty() + * abs(null) == Optional.empty() + * + */ + public static Optional abs( Object arg ) { + 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 )); + } + else if(arg instanceof String) { + return abs( toNumber( arg ).get() ); + } + return Optional.empty(); + } + + /** + * Given any object, returns, if possible. its Java number equivalent wrapped in Optional + * Interprets String as Number + * + * toNumber("123") == Optional.of(123) + * toNumber("-123") == Optional.of(-123) + * toNumber("12.3") == Optional.of(12.3) + * + * toNumber("abc") == Optional.empty() + * toNumber(null) == Optional.empty() + * + * also, see: MathTest#testNitPicks + * + */ + public static Optional toNumber(Object arg) { + if ( arg instanceof Number ) { + return Optional.of( ( (Number) arg )); + } + else if(arg instanceof String) { + try { + return Optional.of( (Number) Integer.parseInt( (String) arg ) ); + } + catch(Exception ignored) {} + try { + return Optional.of( (Number) Long.parseLong( (String) arg ) ); + } + catch(Exception ignored) {} + try { + return Optional.of( (Number) Double.parseDouble( (String) arg ) ); + } + catch(Exception ignored) {} + return Optional.empty(); + } + else { + return Optional.empty(); + } + } + + /** + * Returns int value of argument, if possible, wrapped in Optional + * Interprets String as Number + */ + public static Optional toInteger(Object arg) { + if ( arg instanceof Number ) { + return Optional.of( ( (Number) arg ).intValue() ); + } + else if(arg instanceof String) { + Optional optional = toNumber( arg ); + if ( optional.isPresent() ) { + return Optional.of( optional.get().intValue() ); + } + else { + return Optional.empty(); + } + } + else { + return Optional.empty(); + } + } + + /** + * Returns long value of argument, if possible, wrapped in Optional + * Interprets String as Number + */ + public static Optional toLong(Object arg) { + if ( arg instanceof Number ) { + return Optional.of( ( (Number) arg ).longValue() ); + } + else if(arg instanceof String) { + Optional optional = toNumber( arg ); + if ( optional.isPresent() ) { + return Optional.of( optional.get().longValue() ); + } + else { + return Optional.empty(); + } + } + else { + return Optional.empty(); + } + } + + /** + * Returns double value of argument, if possible, wrapped in Optional + * Interprets String as Number + */ + public static Optional toDouble(Object arg) { + if ( arg instanceof Number ) { + return Optional.of( ( (Number) arg ).doubleValue() ); + } + else if(arg instanceof String) { + Optional optional = toNumber( arg ); + if ( optional.isPresent() ) { + return Optional.of( optional.get().doubleValue() ); + } + else { + return Optional.empty(); + } + } + else { + return Optional.empty(); + } + } + + @SuppressWarnings( "unchecked" ) + public static final class max implements Function { + + public Optional apply( final List args ) { + return (Optional) max( args ); + } + + @Override + public Optional apply( final Object... args ) { + return (Optional) max( Arrays.asList( args )); + } + + } + + @SuppressWarnings( "unchecked" ) + public static final class min implements Function { + + public Optional apply( final List args ) { + return (Optional) min( args ); + } + + @Override + public Optional apply( final Object... args ) { + return (Optional) min( Arrays.asList( args )); + } + } + + @SuppressWarnings( "unchecked" ) + public static final class abs extends genericConverter { + @Override + protected Optional convert( final Object o ) { + return (Optional) abs( o ); + } + } + + @SuppressWarnings( "unchecked" ) + public static final class toInteger extends genericConverter { + @Override + protected Optional convert( final Object o ) { + return (Optional) toInteger( o ); + } + + } + + @SuppressWarnings( "unchecked" ) + public static final class toLong extends genericConverter { + @Override + protected Optional convert( final Object o ) { + return (Optional) toLong( o ); + } + } + + @SuppressWarnings( "unchecked" ) + public static final class toDouble extends genericConverter { + @Override + protected Optional convert( final Object o ) { + return (Optional) toDouble( o ); + } + } + + @SuppressWarnings( "unchecked" ) + private static abstract class genericConverter implements Function { + + public Optional apply( final Object... args ) { + if(args.length == 0) { + return Optional.empty(); + } + else if(args.length == 1) { + return (Optional) convert( args[0] ); + } + return apply(Arrays.asList( args )); + } + + public Optional apply( final List input ) { + List ret = new ArrayList<>( input.size() ); + for(Object o: input) { + Optional optional = convert( o ); + ret.add(optional.isPresent()?optional.get():o); + } + return Optional.of( ret ); + } + + protected abstract Optional convert( final Object o ); + } + +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Strings.java similarity index 96% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Strings.java index 60090579..f1cad66a 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Strings.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/function/Strings.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr.function; +package com.bazaarvoice.jolt.modifier.function; import com.bazaarvoice.jolt.common.Optional; -@SuppressWarnings( "unused" ) +@SuppressWarnings( "deprecated" ) public class Strings { public static final class toLowerCase implements Function { diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierCompositeSpec.java similarity index 84% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierCompositeSpec.java index 7150938f..4ae7f120 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrCompositeSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierCompositeSpec.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.bazaarvoice.jolt.templatr.spec; +package com.bazaarvoice.jolt.modifier.spec; import com.bazaarvoice.jolt.common.ComputedKeysComparator; import com.bazaarvoice.jolt.common.ExecutionStrategy; +import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.pathelement.ArrayPathElement; import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.common.pathelement.PathElement; @@ -31,9 +32,9 @@ 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.DataType; -import com.bazaarvoice.jolt.templatr.OpMode; -import com.bazaarvoice.jolt.templatr.TemplatrSpecBuilder; +import com.bazaarvoice.jolt.modifier.DataType; +import com.bazaarvoice.jolt.modifier.OpMode; +import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder; import java.util.ArrayList; import java.util.Collections; @@ -46,7 +47,7 @@ * Composite spec is non-leaf level spec that contains one or many child specs and processes * them based on a pre-determined execution strategy */ -public class TemplatrCompositeSpec extends TemplatrSpec implements OrderedCompositeSpec { +public class ModifierCompositeSpec extends ModifierSpec implements OrderedCompositeSpec { private static final HashMap orderMap; private static final ComputedKeysComparator computedKeysComparator; @@ -60,25 +61,25 @@ public class TemplatrCompositeSpec extends TemplatrSpec implements OrderedCompos computedKeysComparator = ComputedKeysComparator.fromOrder(orderMap); } - private final Map literalChildren; - private final List computedChildren; + private final Map literalChildren; + private final List computedChildren; private final ExecutionStrategy executionStrategy; private final DataType specDataType; - public TemplatrCompositeSpec( final String key, final Map spec, final OpMode opMode, TemplatrSpecBuilder specBuilder ) { + public ModifierCompositeSpec( final String key, final Map spec, final OpMode opMode, TemplatrSpecBuilder specBuilder ) { super(key, opMode); - Map literals = new LinkedHashMap<>(); - ArrayList computed = new ArrayList<>(); + Map literals = new LinkedHashMap<>(); + ArrayList computed = new ArrayList<>(); - List children = specBuilder.createSpec( spec ); + List children = specBuilder.createSpec( spec ); // remember max explicit index from spec to expand input array at runtime // need to validate spec such that it does not specify both array and literal path element int maxExplicitIndexFromSpec = -1, confirmedMapAtIndex = -1, confirmedArrayAtIndex = -1; for(int i=0; i context ) { + public void applyElement( final String inputKey, Optional inputOptional, MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { + Object input = inputOptional.get(); // sanity checks, cannot work on a list spec with map input and vice versa, and runtime with null input if(!specDataType.isCompatible( input )) { return; @@ -146,6 +151,10 @@ public void applyElement( final String inputKey, Object input, MatchedElement th // create input if it is null if( input == null ) { input = specDataType.create( inputKey, walkedPath, opMode ); + // if input has changed, wrap + if ( input != null ) { + inputOptional = Optional.of( input ); + } } // if input is List, create special ArrayMatchedElement, which tracks the original size of the input array @@ -164,7 +173,7 @@ public void applyElement( final String inputKey, Object input, MatchedElement th // add self to walked path walkedPath.add( input, thisLevel ); // Handle the rest of the children - executionStrategy.process( this, input, walkedPath, null, context ); + executionStrategy.process( this, inputOptional, walkedPath, null, context ); // We are done, so remove ourselves from the walkedPath walkedPath.removeLast(); } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierLeafSpec.java new file mode 100644 index 00000000..e4740530 --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/modifier/spec/ModifierLeafSpec.java @@ -0,0 +1,146 @@ +/* + * 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.modifier.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.modifier.OpMode; +import com.bazaarvoice.jolt.modifier.TemplatrSpecBuilder; +import com.bazaarvoice.jolt.modifier.function.Function; +import com.bazaarvoice.jolt.modifier.function.FunctionArg; +import com.bazaarvoice.jolt.modifier.function.FunctionEvaluator; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +@SuppressWarnings( "deprecated" ) +public class ModifierLeafSpec extends ModifierSpec { + + private final List functionEvaluatorList; + + @SuppressWarnings( "unchecked" ) + public ModifierLeafSpec( final String rawJsonKey, Object rhsObj, final OpMode opMode, final Map functionsMap ) { + super(rawJsonKey, opMode); + functionEvaluatorList = new LinkedList<>( ); + + FunctionEvaluator functionEvaluator; + + // "key": "expression1" + if ( (rhsObj instanceof String) ) { + functionEvaluator = buildFunctionEvaluator( (String) rhsObj, functionsMap ); + functionEvaluatorList.add( functionEvaluator ); + } + // "key": ["expression1", "expression2", "expression3"] + else if(rhsObj instanceof List && ((List)rhsObj).size() > 0) { + List rhsList = (List) rhsObj; + for(Object rhs: rhsList) { + if(rhs instanceof String) { + functionEvaluator = buildFunctionEvaluator( rhs.toString(), functionsMap ); + functionEvaluatorList.add( functionEvaluator ); + } + else { + functionEvaluator = FunctionEvaluator.forArgEvaluation( FunctionArg.forLiteral( rhs, false ) ); + functionEvaluatorList.add( functionEvaluator ); + } + } + } + // "key": anyObjectOrLiteral --- just set as-is + else { + functionEvaluator = FunctionEvaluator.forArgEvaluation( FunctionArg.forLiteral( rhsObj, false ) ); + functionEvaluatorList.add( functionEvaluator ); + } + } + + @Override + public void applyElement( final String inputKey, final Optional inputOptional, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ) { + + Object parent = walkedPath.lastElement().getTreeRef(); + + walkedPath.add( inputOptional.get(), thisLevel ); + + Optional valueOptional = getFirstAvailable( functionEvaluatorList, inputOptional, walkedPath, context ); + + if(valueOptional.isPresent()) { + setData( parent, thisLevel, valueOptional.get(), opMode ); + } + + walkedPath.removeLast(); + } + + private static FunctionEvaluator buildFunctionEvaluator( final String rhs, final Map functionsMap ) { + final FunctionEvaluator functionEvaluator; + // "key": "@0" --- evaluate expression then set + if(!rhs.startsWith( TemplatrSpecBuilder.FUNCTION )) { + return FunctionEvaluator.forArgEvaluation( constructSingleArg( rhs, false ) ); + } + 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() ); + return FunctionEvaluator.forFunctionEvaluation( functionsMap.get( functionName ) ); + } + // "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 ); + functionEvaluator = FunctionEvaluator.forFunctionEvaluation( functionsMap.get( functionName ), constructArgs( fnArgs ) ); + } + } + return functionEvaluator; + } + + private static Optional getFirstAvailable(List functionEvaluatorList, Optional inputOptional, WalkedPath walkedPath, Map context) { + Optional valueOptional = Optional.empty(); + for(FunctionEvaluator functionEvaluator: functionEvaluatorList) { + try { + valueOptional = functionEvaluator.evaluate( inputOptional, walkedPath, context ); + if(valueOptional.isPresent()) { + return valueOptional; + } + } + catch(Exception ignored) {} + } + return valueOptional; + } + + private static FunctionArg[] constructArgs( List argsList ) { + FunctionArg[] argsArray = new FunctionArg[argsList.size()]; + for(int i=0; i T buildFromPath( final String path ) protected final OpMode opMode; protected final MatchablePathElement pathElement; + protected final boolean checkValue; /** * Builds LHS pathElement and validates to specification */ - protected TemplatrSpec(String rawJsonKey, OpMode opMode) { - this.pathElement = buildMatchablePathElement( rawJsonKey ); - if(pathElement instanceof StarPathElement || pathElement instanceof LiteralPathElement || pathElement instanceof ArrayPathElement) { + protected ModifierSpec( String rawJsonKey, OpMode opMode ) { + String prefix = rawJsonKey.substring( 0, 1 ); + String suffix = rawJsonKey.length() > 1 ? rawJsonKey.substring( rawJsonKey.length() - 1 ) : null; + + if(OpMode.isValid( prefix )) { + this.opMode = OpMode.from( prefix ); + rawJsonKey = rawJsonKey.substring( 1 ); + } + else { this.opMode = opMode; } + + if ( suffix != null && suffix.equals( "?" ) && !( rawJsonKey.endsWith( "\\?" ) ) ) { + checkValue = true; + rawJsonKey = rawJsonKey.substring( 0, rawJsonKey.length() - 1 ); + } else { + checkValue = false; + } + + this.pathElement = buildMatchablePathElement( rawJsonKey ); + if ( !( pathElement instanceof StarPathElement ) && !( pathElement instanceof LiteralPathElement ) && !( pathElement instanceof ArrayPathElement ) ) { throw new SpecException( opMode.name() + " cannot have " + pathElement.getClass().getSimpleName() + " RHS" ); } } @@ -72,8 +91,8 @@ public MatchablePathElement getPathElement() { } @Override - public boolean apply( final String inputKey, final Object input, final WalkedPath walkedPath, final Map output, final Map context ) { - if(output != null) { + public boolean apply( final String inputKey, final Optional inputOptional, final WalkedPath walkedPath, final Map output, final Map context ) { + if ( output != null ) { throw new TransformException( "Expected a null output" ); } @@ -82,7 +101,12 @@ public boolean apply( final String inputKey, final Object input, final WalkedPat return false; } - applyElement( inputKey, input, thisLevel, walkedPath, context ); + if ( !checkValue ) { // there was no trailing "?" so no check is necessary + applyElement( inputKey, inputOptional, thisLevel, walkedPath, context ); + } + else if ( inputOptional.isPresent() ) { + applyElement( inputKey, inputOptional, thisLevel, walkedPath, context ); + } return true; } @@ -90,7 +114,7 @@ public boolean apply( final String inputKey, final Object input, final WalkedPat * Templatr specific override that is used in BaseSpec#apply(...) * The name is changed for easy identification during debugging */ - protected abstract void applyElement( final String key, final Object input, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ); + protected abstract void applyElement( final String key, final Optional inputOptional, final MatchedElement thisLevel, final WalkedPath walkedPath, final Map context ); /** * Static utility method for facilitating writes on input object diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrCompositeSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrCompositeSpec.java index 5ebdfb69..2068082a 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrCompositeSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrCompositeSpec.java @@ -189,7 +189,7 @@ else if ( literalChildren.isEmpty() ) { * @return true if this this spec "handles" the inputKey such that no sibling specs need to see it */ @Override - public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map output, Map context ) + public boolean apply( String inputKey, Optional inputOptional, WalkedPath walkedPath, Map output, Map context ) { MatchedElement thisLevel = pathElement.match( inputKey, walkedPath ); if ( thisLevel == null ) { @@ -204,24 +204,22 @@ public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map< // Note the data found may not be a String, thus we have to call the special objectEvaluate // Optional, because the input data could have been a valid null. Optional optional = tpe.objectEvaluate( walkedPath ); - if ( optional.isPresent() ) { - input = optional.get(); - } - else { + if ( !optional.isPresent() ) { return false; } + inputOptional = optional; } // add ourselves to the path, so that our children can reference us - walkedPath.add( input, thisLevel ); + walkedPath.add( inputOptional.get(), thisLevel ); // Handle any special / key based children first, but don't have them block anything for( ShiftrSpec subSpec : specialChildren ) { - subSpec.apply( inputKey, input, walkedPath, output, context ); + subSpec.apply( inputKey, inputOptional, walkedPath, output, context ); } // Handle the rest of the children - executionStrategy.process( this, input, walkedPath, output, context ); + executionStrategy.process( this, inputOptional, walkedPath, output, context ); // We are done, so remove ourselves from the walkedPath walkedPath.removeLast(); diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrLeafSpec.java index 027a11b5..b882bf51 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrLeafSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrLeafSpec.java @@ -88,8 +88,9 @@ else if ( rhs == null ) { * @return true if this this spec "handles" the inputkey such that no sibling specs need to see it */ @Override - public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map output, Map context){ + public boolean apply( String inputKey, Optional inputOptional, WalkedPath walkedPath, Map output, Map context){ + Object input = inputOptional.get(); MatchedElement thisLevel = pathElement.match( inputKey, walkedPath ); if ( thisLevel == null ) { return false; 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 deleted file mode 100644 index def102b8..00000000 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Function.java +++ /dev/null @@ -1,87 +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.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/Math.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java deleted file mode 100644 index acf5515d..00000000 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/function/Math.java +++ /dev/null @@ -1,169 +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.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/spec/TemplatrLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java deleted file mode 100644 index 62df95e3..00000000 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/templatr/spec/TemplatrLeafSpec.java +++ /dev/null @@ -1,122 +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.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 BUILT_INS = (Map) f.get( null ); BUILT_INS.put( "minLabelComputation", new MinLabelComputation() ); @@ -72,23 +73,41 @@ public void setup() throws Exception { public Iterator getTestCases() { List testCases = Lists.newLinkedList(); - testCases.add( new Object[]{"/json/templatr/mapLiteral.json"} ); - testCases.add( new Object[]{"/json/templatr/mapLiteralWithNullInput.json"} ); - testCases.add( new Object[]{"/json/templatr/mapLiteralWithMissingInput.json"} ); - testCases.add( new Object[]{"/json/templatr/mapLiteralWithEmptyInput.json"} ); - testCases.add( new Object[]{"/json/templatr/arrayLiteralWithNullInput.json"} ); - testCases.add( new Object[]{"/json/templatr/arrayLiteral.json"} ); - testCases.add( new Object[]{"/json/templatr/arrayLiteralWithEmptyInput.json"} ); - testCases.add( new Object[]{"/json/templatr/arrayLiteralWithMissingInput.json"} ); - testCases.add( new Object[]{"/json/templatr/arrayObject.json"} ); - testCases.add( new Object[]{"/json/templatr/simple.json"} ); - testCases.add( new Object[]{"/json/templatr/simpleMapNullToArray.json"} ); - testCases.add( new Object[]{"/json/templatr/simpleMapRuntimeNull.json"} ); - testCases.add( new Object[]{"/json/templatr/simpleLookup.json"} ); - testCases.add( new Object[]{"/json/templatr/complexLookup.json"} ); - testCases.add( new Object[]{"/json/templatr/simpleArray.json"} ); - testCases.add( new Object[]{"/json/templatr/simpleArrayLookup.json"} ); - testCases.add( new Object[]{"/json/templatr/complexArrayLookup.json"} ); + testCases.add( new Object[]{"/json/modifier/mapLiteral.json"} ); + testCases.add( new Object[]{"/json/modifier/mapLiteralWithNullInput.json"} ); + testCases.add( new Object[]{"/json/modifier/mapLiteralWithMissingInput.json"} ); + testCases.add( new Object[]{"/json/modifier/mapLiteralWithEmptyInput.json"} ); + + testCases.add( new Object[]{"/json/modifier/arrayLiteral.json"} ); + testCases.add( new Object[]{"/json/modifier/arrayLiteralWithNullInput.json"} ); + testCases.add( new Object[]{"/json/modifier/arrayLiteralWithEmptyInput.json"} ); + testCases.add( new Object[]{"/json/modifier/arrayLiteralWithMissingInput.json"} ); + + testCases.add( new Object[]{"/json/modifier/simple.json"} ); + testCases.add( new Object[]{"/json/modifier/simpleArray.json"} ); + testCases.add( new Object[]{"/json/modifier/arrayObject.json"} ); + + testCases.add( new Object[]{"/json/modifier/simpleMapNullToArray.json"} ); + testCases.add( new Object[]{"/json/modifier/simpleMapRuntimeNull.json"} ); + + testCases.add( new Object[]{"/json/modifier/simpleLookup.json"} ); + testCases.add( new Object[]{"/json/modifier/complexLookup.json"} ); + + testCases.add( new Object[]{"/json/modifier/simpleArrayLookup.json"} ); + testCases.add( new Object[]{"/json/modifier/complexArrayLookup.json"} ); + + testCases.add( new Object[]{"/json/modifier/valueCheckSimpleArray.json"} ); + testCases.add( new Object[]{"/json/modifier/valueCheckSimpleArrayNullInput.json"} ); + testCases.add( new Object[]{"/json/modifier/valueCheckSimpleArrayEmptyInput.json"} ); + + testCases.add( new Object[]{"/json/modifier/valueCheckSimpleMap.json"} ); + testCases.add( new Object[]{"/json/modifier/valueCheckSimpleMapNullInput.json"} ); + testCases.add( new Object[]{"/json/modifier/valueCheckSimpleMapEmptyInput.json"} ); + + testCases.add( new Object[]{"/json/modifier/simpleMapOpOverride.json"} ); + testCases.add( new Object[]{"/json/modifier/simpleArrayOpOverride.json"} ); + + testCases.add( new Object[]{"/json/modifier/testListOfFunction.json"} ); return testCases.iterator(); } @@ -115,8 +134,8 @@ public void doTest(String testFile, TemplatrTestCase testCase) throws Exception Object context = testUnit.get( "context" ); Object expected = testUnit.get( testCase.name() ); if(expected != null) { - Templatr templatr = testCase.getTemplatr( spec ); - Object actual = templatr.transform( input, (Map) context ); + Modifier modifier = testCase.getTemplatr( spec ); + Object actual = modifier.transform( input, (Map) context ); JoltTestUtil.runArrayOrderObliviousDiffy( testCase.name() + " failed case " + testFile, expected, actual ); } } @@ -124,7 +143,7 @@ public void doTest(String testFile, TemplatrTestCase testCase) throws Exception @DataProvider public Iterator getSpecValidationTestCases() { List testCases = Lists.newLinkedList(); - List testObjects = JsonUtils.classpathToList( "/json/templatr/validation/specThatShouldFail.json" ); + List testObjects = JsonUtils.classpathToList( "/json/modifier/validation/specThatShouldFail.json" ); for(TemplatrTestCase testCase: TemplatrTestCase.values()) { for(Object specObj: testObjects) { @@ -144,10 +163,11 @@ public void testInvalidSpecs(TemplatrTestCase testCase, Object spec) { 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} ); + testCases.add( new Object[]{"/json/modifier/functions/stringsTests.json", TemplatrTestCase.OVERWRITR}); + testCases.add( new Object[]{"/json/modifier/functions/mathTests.json", TemplatrTestCase.OVERWRITR} ); + testCases.add( new Object[]{"/json/modifier/functions/arrayTests.json", TemplatrTestCase.OVERWRITR} ); + testCases.add( new Object[]{"/json/modifier/functions/computationTest.json", TemplatrTestCase.DEFAULTR} ); + testCases.add( new Object[]{"/json/modifier/functions/valueTests.json", TemplatrTestCase.OVERWRITR } ); return testCases.iterator(); } diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/AbstractTester.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/AbstractTester.java new file mode 100644 index 00000000..110f44c6 --- /dev/null +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/AbstractTester.java @@ -0,0 +1,47 @@ +/* + * 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.modifier.function; + +import com.bazaarvoice.jolt.common.Optional; +import org.testng.annotations.Test; + +import java.util.Iterator; +import java.util.List; + +import static org.testng.Assert.assertEquals; + +@SuppressWarnings( "deprecated" ) +public abstract class AbstractTester { + + @SuppressWarnings( "unused" ) + public abstract Iterator getTestCases(); + + @Test(dataProvider = "getTestCases") + public void testFunctions(String name, Function function, Object args, Optional expected) { + Optional actual; + if(args instanceof List) { + actual = function.apply( (List) args ); + } + else if (args instanceof Object[]){ + actual = function.apply( (Object[]) args ); + } + else { + actual = function.apply( args ); + } + assertEquals( actual, expected, name + " failed"); + } +} diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/ListsTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/ListsTest.java new file mode 100644 index 00000000..2bbb9fb8 --- /dev/null +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/ListsTest.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.modifier.function; + +import com.bazaarvoice.jolt.common.Optional; +import org.testng.annotations.DataProvider; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +@SuppressWarnings( "deprecated" ) +public class ListsTest extends AbstractTester { + + @DataProvider(parallel = true) + public Iterator getTestCases() { + List testCases = new LinkedList<>( ); + + Function FIRST_ELEMENT = new Lists.firstElement(); + Function LAST_ELEMENT = new Lists.lastElement(); + Function ELEMENT_AT = new Lists.elementAt(); + + + + testCases.add( new Object[] {"first-empty-array", FIRST_ELEMENT, new Object[0], Optional.empty() } ); + testCases.add( new Object[] {"first-empty-list", FIRST_ELEMENT, Arrays.asList( ), Optional.empty() } ); + + testCases.add( new Object[] {"first-null", FIRST_ELEMENT, null, Optional.of( null ) } ); + testCases.add( new Object[] {"first-array", FIRST_ELEMENT, new Object[]{ 1, 2, 3 }, Optional.of( 1 ) } ); + testCases.add( new Object[] {"first-list", FIRST_ELEMENT, Arrays.asList( 1, 2, 3 ), Optional.of( 1 ) } ); + + + + testCases.add( new Object[] {"last-empty-array", LAST_ELEMENT, new Object[0], Optional.empty() } ); + testCases.add( new Object[] {"last-empty-list", LAST_ELEMENT, Arrays.asList( ), Optional.empty() } ); + + testCases.add( new Object[] {"last-null", LAST_ELEMENT, null, Optional.of( null ) } ); + testCases.add( new Object[] {"last-array", LAST_ELEMENT, new Object[]{ 1, 2, 3 }, Optional.of( 3 ) } ); + testCases.add( new Object[] {"last-list", LAST_ELEMENT, Arrays.asList( 1, 2, 3 ), Optional.of( 3 ) } ); + + + + testCases.add( new Object[] {"at-empty-array", ELEMENT_AT, new Object[] {5}, Optional.empty() } ); + testCases.add( new Object[] {"at-empty-list", ELEMENT_AT, Arrays.asList( 5 ), Optional.empty() } ); + testCases.add( new Object[] {"at-empty-null", ELEMENT_AT, new Object[] {null, 1}, Optional.empty() } ); + testCases.add( new Object[] {"at-empty-invalid", ELEMENT_AT, new Object(), Optional.empty() } ); + + testCases.add( new Object[] {"at-array", ELEMENT_AT, new Object[]{ 1, 2, 3, 1 }, Optional.of( 2 ) } ); + testCases.add( new Object[] {"at-list", ELEMENT_AT, Arrays.asList( 1, 2, 3, 1 ), Optional.of( 2 ) } ); + + testCases.add( new Object[] {"at-array-missing", ELEMENT_AT, new Object[]{ 1, 2, 3, 5 }, Optional.empty() } ); + testCases.add( new Object[] {"at-list-missing", ELEMENT_AT, Arrays.asList( 1, 2, 3, 5 ), Optional.empty() } ); + + return testCases.iterator(); + } +} diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/MathTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/MathTest.java new file mode 100644 index 00000000..82098b5e --- /dev/null +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/modifier/function/MathTest.java @@ -0,0 +1,254 @@ +/* + * 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.modifier.function; + +import com.bazaarvoice.jolt.common.Optional; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import static com.bazaarvoice.jolt.modifier.function.Math.abs; +import static com.bazaarvoice.jolt.modifier.function.Math.toNumber; + +@SuppressWarnings( "deprecated" ) +public class MathTest extends AbstractTester { + + @DataProvider(parallel = true) + public Iterator getTestCases() { + List testCases = new LinkedList<>( ); + + Function MAX_OF = new Math.max(); + Function MIN_OF = new Math.min(); + Function ABS_OF = new Math.abs(); + Function TO_INTEGER = new Math.toInteger(); + Function TO_DOUBLE = new Math.toDouble(); + Function TO_LONG = new Math.toLong(); + + + testCases.add( new Object[] { "max-empty-array", MAX_OF, new Object[] {}, Optional.empty() } ); + testCases.add( new Object[] { "max-empty-list", MAX_OF, new ArrayList( ), Optional.empty() } ); + testCases.add( new Object[] { "max-null", MAX_OF, null, Optional.empty() } ); + testCases.add( new Object[] { "max-object", MAX_OF, new Object(), Optional.empty() } ); + + testCases.add( new Object[] { "max-single-int-array", MAX_OF, new Object[] {1}, Optional.of( 1 ) } ); + testCases.add( new Object[] { "max-single-long-array", MAX_OF, new Object[] {1L}, Optional.of( 1L ) } ); + testCases.add( new Object[] { "max-single-double-array", MAX_OF, new Object[] {1.0}, Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "max-single-int-list", MAX_OF, Arrays.asList( 1 ), Optional.of( 1 ) } ); + testCases.add( new Object[] { "max-single-long-list", MAX_OF, Arrays.asList( 1L ), Optional.of( 1L ) } ); + testCases.add( new Object[] { "max-single-double-list", MAX_OF, Arrays.asList( 1.0 ), Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "max-single-int-array-extra-arg", MAX_OF, new Object[] {1, "a"}, Optional.of( 1 ) } ); + testCases.add( new Object[] { "max-single-long-array-extra-arg", MAX_OF, new Object[] {1L, "a"}, Optional.of( 1L ) } ); + testCases.add( new Object[] { "max-single-double-array-extra-arg", MAX_OF, new Object[] {1.0, "a"}, Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "max-single-int-list-extra-arg", MAX_OF, Arrays.asList( 1, "a" ), Optional.of( 1 ) } ); + testCases.add( new Object[] { "max-single-long-list-extra-arg", MAX_OF, Arrays.asList( 1L, "a" ), Optional.of( 1L ) } ); + testCases.add( new Object[] { "max-single-double-list-extra-arg", MAX_OF, Arrays.asList( 1.0, "a" ), Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "max-multi-int-array", MAX_OF, new Object[] {1, 3, 2, 5}, Optional.of( 5 ) } ); + testCases.add( new Object[] { "max-multi-long-array", MAX_OF, new Object[] {1L, 3L, 2L, 5L}, Optional.of( 5L ) } ); + testCases.add( new Object[] { "max-multi-double-array", MAX_OF, new Object[] {1.0, 3.0, 2.0, 5.0}, Optional.of( 5.0 ) } ); + + testCases.add( new Object[] { "max-multi-int-list", MAX_OF, Arrays.asList( 1, 3, 2, 5 ), Optional.of( 5 ) } ); + testCases.add( new Object[] { "max-multi-long-list", MAX_OF, Arrays.asList( 1L, 3L, 2L, 5L ), Optional.of( 5L ) } ); + testCases.add( new Object[] { "max-multi-double-list", MAX_OF, Arrays.asList( 1.0, 3.0, 2.0, 5.0 ), Optional.of( 5.0 ) } ); + + testCases.add( new Object[] { "max-combo-int-array", MAX_OF, new Object[] {1.0, 3L, null, 5}, Optional.of( 5 ) } ); + testCases.add( new Object[] { "max-combo-long-array", MAX_OF, new Object[] {1.0, 3L, null, 5L}, Optional.of( 5L ) } ); + testCases.add( new Object[] { "max-combo-double-array", MAX_OF, new Object[] {1.0, 3L, null, 5.0}, Optional.of( 5.0 ) } ); + + testCases.add( new Object[] { "max-combo-int-list", MAX_OF, Arrays.asList( 1.0, 3L, null, 5 ), Optional.of( 5 ) } ); + testCases.add( new Object[] { "max-combo-long-list", MAX_OF, Arrays.asList( 1.0, 3L, null, 5L ), Optional.of( 5L ) } ); + testCases.add( new Object[] { "max-combo-double-list", MAX_OF, Arrays.asList( 1.0, 3L, null, 5.0 ), Optional.of( 5.0 ) } ); + + testCases.add( new Object[] { "max-NaN", MAX_OF, Arrays.asList( 1.0, Double.NaN ), Optional.of( Double.NaN ) } ); + testCases.add( new Object[] { "max-positive-infinity", MAX_OF, Arrays.asList( 1.0, Double.POSITIVE_INFINITY ), Optional.of( Double.POSITIVE_INFINITY ) } ); + testCases.add( new Object[] { "max-NaN-positive-infinity", MAX_OF, Arrays.asList( 1.0, Double.NaN, Double.POSITIVE_INFINITY ), Optional.of( Double.NaN ) } ); + + + + testCases.add( new Object[] { "min-empty-array", MIN_OF, new Object[] {}, Optional.empty() } ); + testCases.add( new Object[] { "min-empty-list", MIN_OF, new ArrayList( ), Optional.empty() } ); + testCases.add( new Object[] { "min-null", MIN_OF, null, Optional.empty() } ); + testCases.add( new Object[] { "min-object", MIN_OF, new Object(), Optional.empty() } ); + + testCases.add( new Object[] { "min-single-int-array", MIN_OF, new Object[] {1}, Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-single-long-array", MIN_OF, new Object[] {1L}, Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-single-double-array", MIN_OF, new Object[] {1.0}, Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-single-int-list", MIN_OF, Arrays.asList( 1 ), Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-single-long-list", MIN_OF, Arrays.asList( 1L ), Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-single-double-list", MIN_OF, Arrays.asList( 1.0 ), Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-single-int-array-extra-arg", MIN_OF, new Object[] {1, "a"}, Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-single-long-array-extra-arg", MIN_OF, new Object[] {1L, "a"}, Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-single-double-array-extra-arg", MIN_OF, new Object[] {1.0, "a"}, Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-single-int-list-extra-arg", MIN_OF, Arrays.asList( 1, "a" ), Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-single-long-list-extra-arg", MIN_OF, Arrays.asList( 1L, "a" ), Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-single-double-list-extra-arg", MIN_OF, Arrays.asList( 1.0, "a" ), Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-multi-int-array", MIN_OF, new Object[] {1, 3, 2, 5}, Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-multi-long-array", MIN_OF, new Object[] {1L, 3L, 2L, 5L}, Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-multi-double-array", MIN_OF, new Object[] {1.0, 3.0, 2.0, 5.0}, Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-multi-int-list", MIN_OF, Arrays.asList( 1, 3, 2, 5 ), Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-multi-long-list", MIN_OF, Arrays.asList( 1L, 3L, 2L, 5L ), Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-multi-double-list", MIN_OF, Arrays.asList( 1.0, 3.0, 2.0, 5.0 ), Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-combo-int-array", MIN_OF, new Object[] {1, 3L, null, 5.0}, Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-combo-long-array", MIN_OF, new Object[] {1L, 3, null, 5.0}, Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-combo-double-array", MIN_OF, new Object[] {1.0, 3L, null, 5}, Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-combo-int-list", MIN_OF, Arrays.asList( 1, 3L, null, 5.0 ), Optional.of( 1 ) } ); + testCases.add( new Object[] { "min-combo-long-list", MIN_OF, Arrays.asList( 1L, 3, null, 5.0 ), Optional.of( 1L ) } ); + testCases.add( new Object[] { "min-combo-double-list", MIN_OF, Arrays.asList( 1.0, 3L, null, 5 ), Optional.of( 1.0 ) } ); + + testCases.add( new Object[] { "min-NaN", MIN_OF, Arrays.asList( -1.0, Double.NaN ), Optional.of( Double.NaN ) } ); + testCases.add( new Object[] { "min-negative-Infinity", MIN_OF, Arrays.asList( -1.0, Double.NEGATIVE_INFINITY ), Optional.of( Double.NEGATIVE_INFINITY ) } ); + testCases.add( new Object[] { "min-NaN-positive-infinity", MIN_OF, Arrays.asList( -1.0, Double.NaN, Double.NEGATIVE_INFINITY ), Optional.of( Double.NaN ) } ); + + + + testCases.add( new Object[] { "abs-null", ABS_OF, null, Optional.empty() } ); + testCases.add( new Object[] { "abs-invalid", ABS_OF, new Object(), Optional.empty() } ); + testCases.add( new Object[] { "abs-empty-list", ABS_OF, new Object[] {}, Optional.empty() } ); + testCases.add( new Object[] { "abs-empty-array", ABS_OF, Arrays.asList( ), Optional.of( Arrays.asList( ) ) } ); + + testCases.add( new Object[] { "abs-single-negative-int", ABS_OF, -1, Optional.of( 1 ) } ); + testCases.add( new Object[] { "abs-single-negative-long", ABS_OF, -1L, Optional.of(1L) } ); + testCases.add( new Object[] { "abs-single-negative-double", ABS_OF, -1.0, Optional.of(1.0) } ); + testCases.add( new Object[] { "abs-single-positive-int", ABS_OF, 1, Optional.of( 1 ) } ); + testCases.add( new Object[] { "abs-single-positive-long", ABS_OF, 1L, Optional.of(1L) } ); + testCases.add( new Object[] { "abs-single-positive-double", ABS_OF, 1.0, Optional.of(1.0) } ); + + testCases.add( new Object[] { "abs-list", ABS_OF, new Object[] { -1, -1L, -1.0 }, Optional.of( Arrays.asList( 1, 1L, 1.0 ) ) } ); + testCases.add( new Object[] { "abs-array", ABS_OF, Arrays.asList( -1, -1L, -1.0 ), Optional.of( Arrays.asList( 1, 1L, 1.0 ) ) } ); + + testCases.add( new Object[] { "abs-Nan", ABS_OF, Double.NaN, Optional.of(Double.NaN) } ); + testCases.add( new Object[] { "abs-PosInfinity", ABS_OF, Double.POSITIVE_INFINITY, Optional.of(Double.POSITIVE_INFINITY) } ); + testCases.add( new Object[] { "abs-NefInfinity", ABS_OF, Double.NEGATIVE_INFINITY, Optional.of(Double.POSITIVE_INFINITY) } ); + + + + testCases.add( new Object[] { "toInt-null", TO_INTEGER, null, Optional.empty() } ); + testCases.add( new Object[] { "toInt-invalid", TO_INTEGER, new Object(), Optional.empty() } ); + testCases.add( new Object[] { "toInt-empty-array", TO_INTEGER, new Object[] {}, Optional.of(Arrays.asList( )) } ); + testCases.add( new Object[] { "toInt-empty-list", TO_INTEGER, Arrays.asList( ), Optional.of(Arrays.asList( )) } ); + + testCases.add( new Object[] { "toInt-single-positive-string", TO_INTEGER, "1", Optional.of( 1 ) } ); + testCases.add( new Object[] { "toInt-single-negative-string", TO_INTEGER, "-1", Optional.of( -1 ) } ); + testCases.add( new Object[] { "toInt-single-positive-int", TO_INTEGER, 1, Optional.of( 1 ) } ); + testCases.add( new Object[] { "toInt-single-negative-int", TO_INTEGER, -1, Optional.of( -1 ) } ); + testCases.add( new Object[] { "toInt-single-positive-long", TO_INTEGER, 1L, Optional.of( 1 ) } ); + testCases.add( new Object[] { "toInt-single-negative-long", TO_INTEGER, -1L, Optional.of( -1 ) } ); + testCases.add( new Object[] { "toInt-single-positive-double", TO_INTEGER, 1.0, Optional.of( 1 ) } ); + testCases.add( new Object[] { "toInt-single-negative-double", TO_INTEGER, -1.0, Optional.of( -1 ) } ); + + testCases.add( new Object[] { "toInt-single-positive-string-list", TO_INTEGER, new Object[] {"1", "2"}, Optional.of( Arrays.asList( 1, 2 ) ) } ); + testCases.add( new Object[] { "toInt-single-negative-string-array", TO_INTEGER, Arrays.asList( "-1", "-2" ), Optional.of( Arrays.asList( -1, -2 ) ) } ); + testCases.add( new Object[] { "toInt-single-positive-int-list", TO_INTEGER, new Object[] { 1, 2 }, Optional.of( Arrays.asList( 1, 2 ) ) } ); + testCases.add( new Object[] { "toInt-single-negative-int-array", TO_INTEGER, Arrays.asList( -1, -2 ), Optional.of( Arrays.asList( -1, -2 ) ) } ); + testCases.add( new Object[] { "toInt-single-positive-long-list", TO_INTEGER, new Object[] {1L, 2L}, Optional.of( Arrays.asList( 1, 2 ) ) } ); + testCases.add( new Object[] { "toInt-single-negative-long-array", TO_INTEGER, Arrays.asList( -1L, -2L ), Optional.of( Arrays.asList( -1, -2 ) ) } ); + testCases.add( new Object[] { "toInt-single-positive-double-list", TO_INTEGER, new Object[] {1.0, 2.0}, Optional.of( Arrays.asList( 1, 2 ) ) } ); + testCases.add( new Object[] { "toInt-single-negative-double-array", TO_INTEGER, Arrays.asList( -1.0, -2.0 ), Optional.of( Arrays.asList( -1, -2 ) ) } ); + + + + testCases.add( new Object[] { "toDouble-null", TO_DOUBLE, null, Optional.empty() } ); + testCases.add( new Object[] { "toDouble-invalid", TO_DOUBLE, new Object(), Optional.empty() } ); + testCases.add( new Object[] { "toDouble-empty-array", TO_DOUBLE, new Object[] {}, Optional.of(Arrays.asList( )) } ); + testCases.add( new Object[] { "toDouble-empty-list", TO_DOUBLE, Arrays.asList( ), Optional.of(Arrays.asList( )) } ); + + testCases.add( new Object[] { "toDouble-single-positive-string", TO_DOUBLE, "1", Optional.of( 1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-negative-string", TO_DOUBLE, "-1", Optional.of( -1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-positive-int", TO_DOUBLE, 1, Optional.of( 1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-negative-int", TO_DOUBLE, -1, Optional.of( -1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-positive-long", TO_DOUBLE, 1L, Optional.of( 1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-negative-long", TO_DOUBLE, -1L, Optional.of( -1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-positive-double", TO_DOUBLE, 1.0, Optional.of( 1.0 ) } ); + testCases.add( new Object[] { "toDouble-single-negative-double", TO_DOUBLE, -1.0, Optional.of( -1.0 ) } ); + + testCases.add( new Object[] { "toDouble-single-positive-string-list", TO_DOUBLE, new Object[] {"1", "2"}, Optional.of( Arrays.asList( 1.0, 2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-negative-string-array", TO_DOUBLE, Arrays.asList( "-1", "-2" ), Optional.of( Arrays.asList( -1.0, -2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-positive-int-list", TO_DOUBLE, new Object[] { 1, 2 }, Optional.of( Arrays.asList( 1.0, 2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-negative-int-array", TO_DOUBLE, Arrays.asList( -1, -2 ), Optional.of( Arrays.asList( -1.0, -2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-positive-long-list", TO_DOUBLE, new Object[] {1L, 2L}, Optional.of( Arrays.asList( 1.0, 2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-negative-long-array", TO_DOUBLE, Arrays.asList( -1L, -2L ), Optional.of( Arrays.asList( -1.0, -2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-positive-double-list", TO_DOUBLE, new Object[] {1.0, 2.0}, Optional.of( Arrays.asList( 1.0, 2.0 ) ) } ); + testCases.add( new Object[] { "toDouble-single-negative-double-array", TO_DOUBLE, Arrays.asList( -1.0, -2.0 ), Optional.of( Arrays.asList( -1.0, -2.0 ) ) } ); + + + + testCases.add( new Object[] { "toLong-null", TO_LONG, null, Optional.empty() } ); + testCases.add( new Object[] { "toLong-invalid", TO_LONG, new Object(), Optional.empty() } ); + testCases.add( new Object[] { "toLong-empty-array", TO_LONG, new Object[] {}, Optional.of(Arrays.asList( )) } ); + testCases.add( new Object[] { "toLong-empty-list", TO_LONG, Arrays.asList( ), Optional.of(Arrays.asList( )) } ); + + testCases.add( new Object[] { "toLong-single-positive-string", TO_LONG, "1", Optional.of( 1L ) } ); + testCases.add( new Object[] { "toLong-single-negative-string", TO_LONG, "-1", Optional.of( -1L ) } ); + testCases.add( new Object[] { "toLong-single-positive-int", TO_LONG, 1, Optional.of( 1L ) } ); + testCases.add( new Object[] { "toLong-single-negative-int", TO_LONG, -1, Optional.of( -1L ) } ); + testCases.add( new Object[] { "toLong-single-positive-long", TO_LONG, 1L, Optional.of( 1L ) } ); + testCases.add( new Object[] { "toLong-single-negative-long", TO_LONG, -1L, Optional.of( -1L ) } ); + testCases.add( new Object[] { "toLong-single-positive-double", TO_LONG, 1L, Optional.of( 1L ) } ); + testCases.add( new Object[] { "toLong-single-negative-double", TO_LONG, -1L, Optional.of( -1L ) } ); + + testCases.add( new Object[] { "toLong-single-positive-string-list", TO_LONG, new Object[] {"1", "2"}, Optional.of( Arrays.asList( 1L, 2L ) ) } ); + testCases.add( new Object[] { "toLong-single-negative-string-array", TO_LONG, Arrays.asList( "-1", "-2" ), Optional.of( Arrays.asList( -1L, -2L ) ) } ); + testCases.add( new Object[] { "toLong-single-positive-int-list", TO_LONG, new Object[] { 1, 2 }, Optional.of( Arrays.asList( 1L, 2L ) ) } ); + testCases.add( new Object[] { "toLong-single-negative-int-array", TO_LONG, Arrays.asList( -1, -2 ), Optional.of( Arrays.asList( -1L, -2L ) ) } ); + testCases.add( new Object[] { "toLong-single-positive-long-list", TO_LONG, new Object[] {1L, 2L}, Optional.of( Arrays.asList( 1L, 2L ) ) } ); + testCases.add( new Object[] { "toLong-single-negative-long-array", TO_LONG, Arrays.asList( -1L, -2L ), Optional.of( Arrays.asList( -1L, -2L ) ) } ); + testCases.add( new Object[] { "toLong-single-positive-double-list", TO_LONG, new Object[] {1L, 2L}, Optional.of( Arrays.asList( 1L, 2L ) ) } ); + testCases.add( new Object[] { "toLong-single-negative-double-array", TO_LONG, Arrays.asList( -1L, -2L ), Optional.of( Arrays.asList( -1L, -2L ) ) } ); + + + + testCases.add( new Object[] { "toInteger-combo-string-array", TO_INTEGER, Arrays.asList( "-1", 2, -3L, 4.0 ), Optional.of( Arrays.asList( -1, 2, -3, 4 ) ) } ); + testCases.add( new Object[] { "toLong-combo-int-array", TO_LONG, Arrays.asList( "-1", 2, -3L, 4.0 ), Optional.of( Arrays.asList( -1L, 2L, -3L, 4L ) ) } ); + testCases.add( new Object[] { "toDouble-combo-long-array", TO_DOUBLE, Arrays.asList( "-1", 2, -3L, 4.0 ), Optional.of( Arrays.asList( -1.0, 2.0, -3.0, 4.0 ) ) } ); + + return testCases.iterator(); + } + + @Test + @SuppressWarnings( "all" ) + public void testNitPicks() { + // we want to be able to return the min/max element of input type, not + // autoboxed type -- wanted to return int (2), returned double (2.0) + Object c = (1.0 > 2 ? 1.0 : 2); + assert c.getClass() == Double.class && c.equals( 2.0 ); + + // toNumber parsing preference ordering (int-then-long-then-double) demo + assert toNumber("123").equals( Optional.of( 123 ) ); + assert toNumber("123123123123123123").equals( Optional.of( 123123123123123123l ) ); + assert toNumber("123123123123123123123123123123123123").equals( Optional.of( 123123123123123123123123123123123123d ) ); + + // abs returns numbers in their appropriate type, not given type (string in this case) + assert abs( "-123" ).equals( Optional.of( 123 )); + assert abs("-123123123123123123").equals( Optional.of( 123123123123123123l ) ); + assert abs("-123123123123123123123123123123123123").equals( Optional.of( 123123123123123123123123123123123123d ) ); + } +} diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteral.json b/jolt-core/src/test/resources/json/modifier/arrayLiteral.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/arrayLiteral.json rename to jolt-core/src/test/resources/json/modifier/arrayLiteral.json diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithEmptyInput.json b/jolt-core/src/test/resources/json/modifier/arrayLiteralWithEmptyInput.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/arrayLiteralWithEmptyInput.json rename to jolt-core/src/test/resources/json/modifier/arrayLiteralWithEmptyInput.json diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithMissingInput.json b/jolt-core/src/test/resources/json/modifier/arrayLiteralWithMissingInput.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/arrayLiteralWithMissingInput.json rename to jolt-core/src/test/resources/json/modifier/arrayLiteralWithMissingInput.json diff --git a/jolt-core/src/test/resources/json/templatr/arrayLiteralWithNullInput.json b/jolt-core/src/test/resources/json/modifier/arrayLiteralWithNullInput.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/arrayLiteralWithNullInput.json rename to jolt-core/src/test/resources/json/modifier/arrayLiteralWithNullInput.json diff --git a/jolt-core/src/test/resources/json/templatr/arrayObject.json b/jolt-core/src/test/resources/json/modifier/arrayObject.json similarity index 94% rename from jolt-core/src/test/resources/json/templatr/arrayObject.json rename to jolt-core/src/test/resources/json/modifier/arrayObject.json index ebabcd6d..5c03ae09 100644 --- a/jolt-core/src/test/resources/json/templatr/arrayObject.json +++ b/jolt-core/src/test/resources/json/modifier/arrayObject.json @@ -15,8 +15,8 @@ }, "objectArrayFill": { - "*": [0], - "[4]": [4] + "*": "=toList(0)", + "[4]": "=toList(4)" }, "emptyMap": {}, "emptyList": [] diff --git a/jolt-core/src/test/resources/json/templatr/complexArrayLookup.json b/jolt-core/src/test/resources/json/modifier/complexArrayLookup.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/complexArrayLookup.json rename to jolt-core/src/test/resources/json/modifier/complexArrayLookup.json diff --git a/jolt-core/src/test/resources/json/templatr/complexLookup.json b/jolt-core/src/test/resources/json/modifier/complexLookup.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/complexLookup.json rename to jolt-core/src/test/resources/json/modifier/complexLookup.json diff --git a/jolt-core/src/test/resources/json/templatr/functions/arrayTests.json b/jolt-core/src/test/resources/json/modifier/functions/arrayTests.json similarity index 87% rename from jolt-core/src/test/resources/json/templatr/functions/arrayTests.json rename to jolt-core/src/test/resources/json/modifier/functions/arrayTests.json index edd8ad1d..5dcc3f64 100644 --- a/jolt-core/src/test/resources/json/templatr/functions/arrayTests.json +++ b/jolt-core/src/test/resources/json/modifier/functions/arrayTests.json @@ -10,8 +10,8 @@ "spec": { "*": { - "max": "=maxOf(@(2,[&1].value),^value,0)", - "min": "=minOf(@(2,[&1].value),^value,0.0)", + "max": "=max(@(2,[&1].value),^value,0)", + "min": "=min(@(2,[&1].value),^value,0.0)", "abs": "=abs(^value)", "double": "=toDouble(@(2,[&1].value))", "integer": "=toInteger(@(2,[&1].value))", diff --git a/jolt-core/src/test/resources/json/templatr/functions/computationTest.json b/jolt-core/src/test/resources/json/modifier/functions/computationTest.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/functions/computationTest.json rename to jolt-core/src/test/resources/json/modifier/functions/computationTest.json diff --git a/jolt-core/src/test/resources/json/modifier/functions/mathTests.json b/jolt-core/src/test/resources/json/modifier/functions/mathTests.json new file mode 100644 index 00000000..1fc24ec5 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/functions/mathTests.json @@ -0,0 +1,48 @@ +{ + "input": { + "data1": { + "value": -2 + }, + "data2": { + "value": -2.0 + } + }, + + "spec": { + "data1": { + "max": "=max(@(1,value),^value,0)", + "min": "=min(@(1,value),^value,0.0)", + "double": "=toDouble(@(1,value))", + "value": "=abs" + + }, + "data2": { + "max": "=max(@(1,value),^value,0.0)", + "min": "=min(@(1,value),^value,0)", + "integer": "=toInteger(@(1,value))", + "value": "=abs" + } + }, + + "context": { + "value" : 1.0 + }, + + "OVERWRITR": { + + "data1" : { + "max" : 1.0, + "min" : -2, + "double" : -2.0, + "value" : 2 + }, + "data2" : { + "max" : 1.0, + "min" : -2.0, + "integer" : -2, + "value" : 2.0 + } + + + } +} diff --git a/jolt-core/src/test/resources/json/templatr/functions/stringsTests.json b/jolt-core/src/test/resources/json/modifier/functions/stringsTests.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/functions/stringsTests.json rename to jolt-core/src/test/resources/json/modifier/functions/stringsTests.json diff --git a/jolt-core/src/test/resources/json/modifier/functions/valueTests.json b/jolt-core/src/test/resources/json/modifier/functions/valueTests.json new file mode 100644 index 00000000..87023c95 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/functions/valueTests.json @@ -0,0 +1,37 @@ +{ + "input": { + + "p": "", + "q": "", + "r": "", + + "x": null, + "y": null, + "z": null + }, + + "spec": { + "a": "=isPresent", + "b": "=notNull", + "c": "=isNull", + + "p": "=isPresent", + "q": "=notNull", + "r": "=isNull", + + "x": "=isPresent", + "y": [ "=notNull", 3 ], + "z": "=isNull" + }, + + "context": {}, + + "OVERWRITR": { + "p" : "", + "q" : "", + "r" : "", + "x" : null, + "y" : 3, + "z" : null + } +} diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteral.json b/jolt-core/src/test/resources/json/modifier/mapLiteral.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/mapLiteral.json rename to jolt-core/src/test/resources/json/modifier/mapLiteral.json diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteralWithEmptyInput.json b/jolt-core/src/test/resources/json/modifier/mapLiteralWithEmptyInput.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/mapLiteralWithEmptyInput.json rename to jolt-core/src/test/resources/json/modifier/mapLiteralWithEmptyInput.json diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteralWithMissingInput.json b/jolt-core/src/test/resources/json/modifier/mapLiteralWithMissingInput.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/mapLiteralWithMissingInput.json rename to jolt-core/src/test/resources/json/modifier/mapLiteralWithMissingInput.json diff --git a/jolt-core/src/test/resources/json/templatr/mapLiteralWithNullInput.json b/jolt-core/src/test/resources/json/modifier/mapLiteralWithNullInput.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/mapLiteralWithNullInput.json rename to jolt-core/src/test/resources/json/modifier/mapLiteralWithNullInput.json diff --git a/jolt-core/src/test/resources/json/templatr/simple.json b/jolt-core/src/test/resources/json/modifier/simple.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/simple.json rename to jolt-core/src/test/resources/json/modifier/simple.json diff --git a/jolt-core/src/test/resources/json/templatr/simpleArray.json b/jolt-core/src/test/resources/json/modifier/simpleArray.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/simpleArray.json rename to jolt-core/src/test/resources/json/modifier/simpleArray.json diff --git a/jolt-core/src/test/resources/json/templatr/simpleArrayLookup.json b/jolt-core/src/test/resources/json/modifier/simpleArrayLookup.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/simpleArrayLookup.json rename to jolt-core/src/test/resources/json/modifier/simpleArrayLookup.json diff --git a/jolt-core/src/test/resources/json/modifier/simpleArrayOpOverride.json b/jolt-core/src/test/resources/json/modifier/simpleArrayOpOverride.json new file mode 100644 index 00000000..1be966db --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/simpleArrayOpOverride.json @@ -0,0 +1,97 @@ +{ + "input": { + "data": [ + "a", + "b", + "c", + + null, + null, + null + ], + "data2": [ + "a", + null + ] + }, + + "spec": { + "data": { + "+[0]": "aa", + "~[1]": "bb", + "_[2]": "cc", + + "+[3]": "pp", + "~[4]": "qq", + "_[5]": "rr", + + "+[6]": "xx", + "~[7]": "yy", + "_[8]": "zz" + }, + "data2": { + "[0]": "aa", + "[1]": "bb", + "[2]": "cc" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": [ + "aa", + "b", + "c", + "pp", + "qq", + null, + "xx", + "yy", + "zz" + ], + "data2" : [ + "aa", + "bb", + "cc" + ] + }, + + "DEFAULTR": { + "data": [ + "aa", + "b", + "c", + "pp", + "qq", + null, + "xx", + "yy", + "zz" + ], + "data2" : [ + "a", + "bb", + "cc" + ] + }, + + "DEFINR": { + "data": [ + "aa", + "b", + "c", + "pp", + "qq", + null, + "xx", + "yy", + "zz" + ], + "data2" : [ + "a", + null, + "cc" + ] + } +} diff --git a/jolt-core/src/test/resources/json/templatr/simpleLookup.json b/jolt-core/src/test/resources/json/modifier/simpleLookup.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/simpleLookup.json rename to jolt-core/src/test/resources/json/modifier/simpleLookup.json diff --git a/jolt-core/src/test/resources/json/templatr/simpleMapNullToArray.json b/jolt-core/src/test/resources/json/modifier/simpleMapNullToArray.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/simpleMapNullToArray.json rename to jolt-core/src/test/resources/json/modifier/simpleMapNullToArray.json diff --git a/jolt-core/src/test/resources/json/modifier/simpleMapOpOverride.json b/jolt-core/src/test/resources/json/modifier/simpleMapOpOverride.json new file mode 100644 index 00000000..f7ac25f9 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/simpleMapOpOverride.json @@ -0,0 +1,100 @@ +{ + "input": { + "data": { + "a": "a", + "b": "b", + "c": "c", + + "p": null, + "q": null, + "r": null + + // x, y, z missing + }, + "data2": { + "a": "a", + "b": null + // c missing + } + }, + + "spec": { + "data": { + "+a": "aa", + "~b": "bb", + "_c": "cc", + + "+p": "pp", + "~q": "qq", + "_r": "rr", + + "+x": "xx", + "~y": "yy", + "_z": "zz" + }, + "data2": { + "a": "aa", + "b": "bb", + "c": "cc" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": { + "a" : "aa", + "b" : "b", + "c" : "c", + "p" : "pp", + "q" : "qq", + "r" : null, + "x" : "xx", + "y" : "yy", + "z" : "zz" + }, + "data2" : { + "a" : "aa", + "b" : "bb", + "c" : "cc" + } + }, + + "DEFAULTR": { + "data": { + "a" : "aa", + "b" : "b", + "c" : "c", + "p" : "pp", + "q" : "qq", + "r" : null, + "x" : "xx", + "y" : "yy", + "z" : "zz" + }, + "data2" : { + "a" : "a", + "b" : "bb", + "c" : "cc" + } + }, + + "DEFINR": { + "data": { + "a" : "aa", + "b" : "b", + "c" : "c", + "p" : "pp", + "q" : "qq", + "r" : null, + "x" : "xx", + "y" : "yy", + "z" : "zz" + }, + "data2" : { + "a" : "a", + "b" : null, + "c" : "cc" + } + } +} diff --git a/jolt-core/src/test/resources/json/templatr/simpleMapRuntimeNull.json b/jolt-core/src/test/resources/json/modifier/simpleMapRuntimeNull.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/simpleMapRuntimeNull.json rename to jolt-core/src/test/resources/json/modifier/simpleMapRuntimeNull.json diff --git a/jolt-core/src/test/resources/json/modifier/testListOfFunction.json b/jolt-core/src/test/resources/json/modifier/testListOfFunction.json new file mode 100644 index 00000000..9b2fe328 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/testListOfFunction.json @@ -0,0 +1,45 @@ +{ + "input": { + "data": { + "a": null, + "b": null, + "c": null, + + "p" : 0, + "q" : 0, + "r" : 0 + + // xyz missing + } + }, + + "spec": { + "data": { + "+a": [ "=noop", "=min(1,2,3)" ], + "~b": [ "=notNull", "1" ], + "_c": [ "=min(1,2,3)" ], + + "_p": [ "=isNull", "=min(1,2,3,@(1,&0))" ], + "+q": [ "=max('a','b','c')", "@(1,&0)" ], + "~r": [ "=toDouble" ], + + "~x": [ "=notNull", "=max(1,2,3)" ], + "_y": [ "=notNull", "=toDouble" ], + "+z": [ "=nonnull", "=min('a','b','c')", "=toList" ] + } + }, + + "context": { }, + + "OVERWRITR": { + "data": { + "a" : 1, + "b" : "1", + "c" : null, + "p" : 0, + "q" : 0, + "r" : 0, + "x" : 3 + } + } +} diff --git a/jolt-core/src/test/resources/json/templatr/validation/specThatShouldFail.json b/jolt-core/src/test/resources/json/modifier/validation/specThatShouldFail.json similarity index 100% rename from jolt-core/src/test/resources/json/templatr/validation/specThatShouldFail.json rename to jolt-core/src/test/resources/json/modifier/validation/specThatShouldFail.json diff --git a/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArray.json b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArray.json new file mode 100644 index 00000000..b467f6c8 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArray.json @@ -0,0 +1,31 @@ +{ + "input": { + "data": [ null,"b","c" ] + }, + + "spec": { + "data": { + "[0]": "x", + "[1]?": "y", + "[3]": "d", + "[4]?": "e" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": + [ "x", "y", "c", "d" ] + }, + + "DEFAULTR": { + "data": + [ "x", "b", "c", "d" ] + }, + + "DEFINR": { + "data": + [ null, "b", "c", "d" ] + } +} diff --git a/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayEmptyInput.json b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayEmptyInput.json new file mode 100644 index 00000000..918d01a9 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayEmptyInput.json @@ -0,0 +1,30 @@ +{ + "input": { + "data": [] + }, + + "spec": { + "data": { + "[0]": "x", + "[1]?": "y", + "[3]": "d", + "[4]?": "e" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": + [ "x", null, null, "d" ] + }, + + "DEFAULTR":{ + "data": + [ "x", null, null, "d" ] + }, + "DEFINR":{ + "data": + [ "x", null, null, "d" ] + } +} diff --git a/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayNullInput.json b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayNullInput.json new file mode 100644 index 00000000..9f094b50 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleArrayNullInput.json @@ -0,0 +1,29 @@ +{ + "input": { + "data": null + }, + + "spec": { + "data": { + "[0]": "x", + "[1]?": "y", + "[3]": "d", + "[4]?": "e" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": + [ "x", null, null, "d" ] + }, + + "DEFAULTR":{ + "data": + [ "x", null, null, "d" ] + }, + "DEFINR":{ + "data": null + } +} diff --git a/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMap.json b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMap.json new file mode 100644 index 00000000..6edf4a89 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMap.json @@ -0,0 +1,40 @@ +{ + "input": { + "data": { + "a": "a", + "c": null + } + }, + + "spec": { + "data": { + "a?": "x", + "b?": "y", + "c": "z" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": { + "a": "x", + "c": "z" + } + } + , + + "DEFAULTR": { + "data": { + "a": "a", + "c": "z" + } + }, + + "DEFINR": { + "data": { + "a": "a", + "c": null + } + } +} diff --git a/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapEmptyInput.json b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapEmptyInput.json new file mode 100644 index 00000000..d56e1f23 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapEmptyInput.json @@ -0,0 +1,34 @@ +{ + "input": { + "data": {} + }, + + "spec": { + "data": { + "a?": "x", + "b?": "y", + "c": "z" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": { + "c" : "z" + } + } + , + + "DEFAULTR": { + "data": { + "c" : "z" + } + }, + + "DEFINR": { + "data": { + "c" : "z" + } + } +} diff --git a/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapNullInput.json b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapNullInput.json new file mode 100644 index 00000000..8f5df416 --- /dev/null +++ b/jolt-core/src/test/resources/json/modifier/valueCheckSimpleMapNullInput.json @@ -0,0 +1,32 @@ +{ + "input": { + "data": null + }, + + "spec": { + "data": { + "a?": "x", + "b?": "y", + "c": "z" + } + }, + + "context": { }, + + "OVERWRITR": { + "data": { + "c" : "z" + } + } + , + + "DEFAULTR": { + "data": { + "c" : "z" + } + }, + + "DEFINR": { + "data": null + } +} diff --git a/jolt-core/src/test/resources/json/templatr/functions/mathTests.json b/jolt-core/src/test/resources/json/templatr/functions/mathTests.json deleted file mode 100644 index c0fd7ccd..00000000 --- a/jolt-core/src/test/resources/json/templatr/functions/mathTests.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "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 - } - } -}