Skip to content

Commit

Permalink
Merge pull request #222 from bivasdas/function
Browse files Browse the repository at this point in the history
Imlemented Function
  • Loading branch information
Milo Simpson authored Jul 6, 2016
2 parents abac69c + c4c4942 commit a78b209
Show file tree
Hide file tree
Showing 37 changed files with 1,101 additions and 239 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
language: java
jdk:
- oraclejdk7
- oraclejdk8
50 changes: 43 additions & 7 deletions jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
import com.bazaarvoice.jolt.exception.SpecException;
import com.bazaarvoice.jolt.templatr.OpMode;
import com.bazaarvoice.jolt.templatr.TemplatrSpecBuilder;
import com.bazaarvoice.jolt.templatr.function.Function;
import com.bazaarvoice.jolt.templatr.function.Math;
import com.bazaarvoice.jolt.templatr.function.Strings;
import com.bazaarvoice.jolt.templatr.spec.TemplatrCompositeSpec;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -31,18 +35,38 @@
*/
public abstract class Templatr implements SpecDriven, ContextualTransform {

private static final Map<String, Function> STOCK_FUNCTIONS = new HashMap<>( );

static {
STOCK_FUNCTIONS.put( "toLower", new Strings.toLowerCase() );
STOCK_FUNCTIONS.put( "toUpper", new Strings.toUpperCase() );
STOCK_FUNCTIONS.put( "concat", new Strings.concat() );

STOCK_FUNCTIONS.put( "minOf", new com.bazaarvoice.jolt.templatr.function.Math.MinOf() );
STOCK_FUNCTIONS.put( "maxOf", new Math.MaxOf() );
STOCK_FUNCTIONS.put( "abs", new Math.Abs() );
STOCK_FUNCTIONS.put( "toInteger", new Math.toInteger() );
STOCK_FUNCTIONS.put( "toDouble", new Math.toDouble() );
STOCK_FUNCTIONS.put( "toLong", new Math.toLong() );
}

private final TemplatrCompositeSpec rootSpec;

@SuppressWarnings( "unchecked" )
private Templatr(Object spec, OpMode opMode) {
private Templatr(Object spec, OpMode opMode, Map<String, Function> functionsMap) {
if ( spec == null ){
throw new SpecException( opMode.name() + " expected a spec of Map type, got 'null'." );
}
if ( ! ( spec instanceof Map ) ) {
throw new SpecException( opMode.name() + " expected a spec of Map type, got " + spec.getClass().getSimpleName() );
}

TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder( opMode );
if(functionsMap == null || functionsMap.isEmpty()) {
throw new SpecException( opMode.name() + " expected a populated functions' map type, got " + (functionsMap == null?"null":"empty") );
}

functionsMap = Collections.unmodifiableMap( functionsMap );
TemplatrSpecBuilder templatrSpecBuilder = new TemplatrSpecBuilder( opMode, functionsMap );
rootSpec = new TemplatrCompositeSpec( ROOT_KEY, (Map<String, Object>) spec, opMode, templatrSpecBuilder );
}

Expand All @@ -60,14 +84,18 @@ public Object transform( final Object input, final Map<String, Object> context )
return input;
}

/**
/**
* This variant of templatr creates the key/index is missing,
* and overwrites the value if present
*/
public static final class Overwritr extends Templatr {

public Overwritr( Object spec ) {
super(spec, OpMode.OVERWRITR );
this( spec, STOCK_FUNCTIONS );
}

public Overwritr( Object spec, Map<String, Function> functionsMap ) {
super( spec, OpMode.OVERWRITR, functionsMap );
}
}

Expand All @@ -77,17 +105,25 @@ public Overwritr( Object spec ) {
public static final class Definr extends Templatr {

public Definr( final Object spec ) {
super( spec, OpMode.DEFINER );
this( spec, STOCK_FUNCTIONS );
}

public Definr( Object spec, Map<String, Function> functionsMap ) {
super( spec, OpMode.DEFINER, functionsMap );
}
}

/**
* This variant of templatr only writes when the key/index is present and the value is null
* This variant of templatr only writes when the key/index is missing or the value is null
*/
public static class Defaultr extends Templatr {

public Defaultr( final Object spec ) {
super( spec, OpMode.DEFAULTR );
this( spec, STOCK_FUNCTIONS );
}

public Defaultr( Object spec, Map<String, Function> functionsMap ) {
super( spec, OpMode.DEFAULTR, functionsMap );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.bazaarvoice.jolt.exception.SpecException;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

Expand Down Expand Up @@ -301,4 +302,48 @@ public static String removeEscapeChars( String origKey ) {

return sb.toString();
}

public static List<String> parseFunctionArgs(String argString) {
List<String> argsList = new LinkedList<>( );
int firstBracket = argString.indexOf( '(' );

String className = argString.substring( 0, firstBracket );
argsList.add( className );

// drop the first and last ( )
argString = argString.substring( firstBracket + 1, argString.length() - 1 );

StringBuilder sb = new StringBuilder( );
boolean inBetweenBrackets = false;
boolean inBetweenQuotes = false;
for (int i = 0; i < argString.length(); i++){
char c = argString.charAt(i);
switch ( c ) {
case '(':
inBetweenBrackets = true;
sb.append( c );
break;
case ')':
inBetweenBrackets = false;
sb.append( c );
break;
case '\'':
inBetweenQuotes = !inBetweenQuotes;
sb.append( c );
break;
case ',':
if ( !inBetweenBrackets && !inBetweenQuotes ) {
argsList.add( sb.toString() );
sb = new StringBuilder();
break;
}
default:
sb.append( c );
break;
}
}

argsList.add( sb.toString() );
return argsList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,36 @@
package com.bazaarvoice.jolt.templatr;

import com.bazaarvoice.jolt.common.spec.SpecBuilder;
import com.bazaarvoice.jolt.templatr.function.Function;
import com.bazaarvoice.jolt.templatr.spec.TemplatrCompositeSpec;
import com.bazaarvoice.jolt.templatr.spec.TemplatrContextLookupLeafSpec;
import com.bazaarvoice.jolt.templatr.spec.TemplatrDefaultLeafSpec;
import com.bazaarvoice.jolt.templatr.spec.TemplatrSelfLookupLeafSpec;
import com.bazaarvoice.jolt.templatr.spec.TemplatrLeafSpec;
import com.bazaarvoice.jolt.templatr.spec.TemplatrSpec;

import java.util.Map;

public class TemplatrSpecBuilder extends SpecBuilder<TemplatrSpec> {

private static final String CARET = "^";
private static final String AT = "@";
public static final String CARET = "^";
public static final String AT = "@";
public static final String FUNCTION = "=";

private final OpMode opMode;
private final Map<String, Function> functionsMap;

public TemplatrSpecBuilder(OpMode opMode) {

public TemplatrSpecBuilder( OpMode opMode, Map<String, Function> functionsMap ) {
this.opMode = opMode;
this.functionsMap = functionsMap;
}

@Override
@SuppressWarnings( "unchecked" )
public TemplatrSpec createSpec( final String lhs, final Object rhs ) {
if( rhs instanceof Map ) {
Map rhsMap = (Map)rhs;
if(rhsMap.isEmpty()) {
return new TemplatrDefaultLeafSpec( lhs, rhsMap, opMode );
}
else {
return new TemplatrCompositeSpec(lhs, rhsMap, opMode, this );
}

if( rhs instanceof Map && (!( (Map) rhs ).isEmpty())) {
return new TemplatrCompositeSpec(lhs, (Map)rhs, opMode, this );
}
else {
if ( rhs instanceof String ) {
String rhsValue = (String) rhs;
// leaf level starts with ^ , so spec is an dot notation read from context
if(rhsValue.startsWith( CARET )) {
return new TemplatrContextLookupLeafSpec( lhs, rhsValue, opMode );
}
// leaf level starts with @ , so spec is an dot notation read from self
else if(rhsValue.startsWith( AT )) {
return new TemplatrSelfLookupLeafSpec( lhs, rhsValue, opMode );
}
// leaf level is an actual string value, we need to set as default
else {
return new TemplatrDefaultLeafSpec( lhs, rhsValue, opMode );
}
}
// leaf level is an actual non-string value or null or Map or List, we need to set as default
else {
return new TemplatrDefaultLeafSpec( lhs, rhs, opMode );
}
return new TemplatrLeafSpec( lhs, rhs, opMode, functionsMap );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2013 Bazaarvoice, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bazaarvoice.jolt.templatr.function;

import com.bazaarvoice.jolt.common.Optional;

/**
* Templatr supports a Function on RHS that accepts jolt path expressions as arguments and evaluates
* them at runtime before calling it. Function always returns an Optional, and the value is written
* only if the optional is not empty.
*
* function spec is defined by "key": "=functionName(args...)"
*
*
* input:
* { "num": -1.0 }
* spec:
* { "num": "=abs(@(1,&0))" }
* will call the stock function Math.abs() and will pass the matching value at "num"
*
* spec:
* { "num": "=abs" }
* an alternative shortcut will do the same thing
*
* output:
* { "num": 1.0 }
*
*
*
* input:
* { "value": -1.0 }
*
* spec:
* { "absValue": "=abs(@(1,value))" }
* will evaluate the jolt path expression @(1,value) and pass the output to stock function Math.abs()
*
* output:
* { "value": -1.0, "absValue": 1.0 }
*
*
*
* Currently defined stock functions are:
*
* toLower - returns toLower value of toString() value of first arg, rest is ignored
* toUpper - returns toUpper value of toString() value of first arg, rest is ignored
* concat - concatenate all given arguments' toString() values
*
* minOf - returns the min of all numbers provided in the arguments, non-numbers are ignored
* maxOf - returns the max of all numbers provided in the arguments, non-numbers are ignored
* abs - returns the absolute value of first argument, rest is ignored
* toInteger - returns the intValue() value of first argument if its numeric, rest is ignored
* toDouble - returns the doubleValue() value of first argument if its numeric, rest is ignored
* toLong - returns the longValue() value of first argument if its numeric, rest is ignored
*
* All of these functions returns Optional.EMPTY if unsuccessful, which results in a no-op when performing
* the actual write in the json doc.
*
* i.e.
* input:
* { "value": "1.0" } --- note: string, not number
*
* spec:
* { "absValue": "=abs" } --- fails silently
*
* output:
* { "value": "1.0" } --- note: "absValue": null is not inserted
*
*/
public interface Function {

public Optional<Object> apply(Object... args);

}
Loading

0 comments on commit a78b209

Please sign in to comment.