Skip to content

Commit

Permalink
Merge pull request #219 from bivasdas/templatr
Browse files Browse the repository at this point in the history
Implemented templating featur
  • Loading branch information
Milo Simpson authored Jun 27, 2016
2 parents d986293 + 3f45006 commit 3022676
Show file tree
Hide file tree
Showing 65 changed files with 3,516 additions and 862 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ public CardinalityTransform( Object spec ) {
@Override
public Object transform( Object input ) {

rootSpec.apply( ROOT_KEY, input, new WalkedPath(), null );
rootSpec.apply( ROOT_KEY, input, new WalkedPath(), null, null );

return input;
}
Expand Down
3 changes: 1 addition & 2 deletions jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,6 @@
*/
public class Shiftr implements SpecDriven, Transform {

public static final String ROOT_KEY = "root";
private final ShiftrCompositeSpec rootSpec;

/**
Expand Down Expand Up @@ -502,7 +501,7 @@ public Object transform( Object input ) {
WalkedPath walkedPath = new WalkedPath();
walkedPath.add( input, rootLpe );

rootSpec.apply( ROOT_KEY, input, walkedPath, output );
rootSpec.apply( ROOT_KEY, input, walkedPath, output, null );

return output.get( ROOT_KEY );
}
Expand Down
3 changes: 3 additions & 0 deletions jolt-core/src/main/java/com/bazaarvoice/jolt/SpecDriven.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@
* Ideally, calls to the transform method are expected to be stateless and multi-thread safe.
*/
public interface SpecDriven {

String ROOT_KEY = "root";

}
93 changes: 93 additions & 0 deletions jolt-core/src/main/java/com/bazaarvoice/jolt/Templatr.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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;

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.spec.TemplatrCompositeSpec;
import com.bazaarvoice.jolt.templatr.TemplatrSpecBuilder;

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

/**
* Base Templatr transform that to behave differently based on provided opMode
*/
public abstract class Templatr implements SpecDriven, ContextualTransform {

private final TemplatrCompositeSpec rootSpec;

@SuppressWarnings( "unchecked" )
private Templatr(Object spec, OpMode opMode) {
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 );
rootSpec = new TemplatrCompositeSpec( ROOT_KEY, (Map<String, Object>) spec, opMode, templatrSpecBuilder );
}

@Override
public Object transform( final Object input, final Map<String, Object> context ) {

Map<String, Object> contextWrapper = new HashMap<>( );
contextWrapper.put( ROOT_KEY, context );

MatchedElement rootLpe = new MatchedElement( ROOT_KEY );
WalkedPath walkedPath = new WalkedPath();
walkedPath.add( input, rootLpe );

rootSpec.apply( ROOT_KEY, input, walkedPath, null, contextWrapper );
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 variant of templatr only writes when the key/index is missing
*/
public static final class Definr extends Templatr {

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

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

public Defaultr( final Object spec ) {
super( spec, OpMode.DEFAULTR );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@
*/
package com.bazaarvoice.jolt.cardinality;

import com.bazaarvoice.jolt.common.ComputedKeysComparator;
import com.bazaarvoice.jolt.common.tree.WalkedPath;
import com.bazaarvoice.jolt.common.pathelement.AmpPathElement;
import com.bazaarvoice.jolt.common.pathelement.AtPathElement;
import com.bazaarvoice.jolt.common.tree.MatchedElement;
import com.bazaarvoice.jolt.common.pathelement.PathElement;
import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement;
import com.bazaarvoice.jolt.common.pathelement.StarPathElement;
import com.bazaarvoice.jolt.exception.SpecException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -38,8 +37,15 @@
*/
public class CardinalityCompositeSpec extends CardinalitySpec {

private static final ComputedKeysComparator computedKeysComparator = new ComputedKeysComparator();
private static final HashMap<Class, Integer> orderMap;
private static final ComputedKeysComparator computedKeysComparator;

static {
orderMap = new HashMap<>();
orderMap.put( AmpPathElement.class, 1 );
orderMap.put( StarPathElement.class, 2 );
computedKeysComparator = ComputedKeysComparator.fromOrder(orderMap);
}
// Three different buckets for the children of this CardinalityCompositeSpec
private CardinalityLeafSpec specialChild; // children that aren't actually triggered off the input data
private final Map<String, CardinalitySpec> literalChildren; // children that are simple exact matches against the input data
Expand Down Expand Up @@ -136,7 +142,7 @@ private static List<CardinalitySpec> createChildren( Map<String, Object> rawSpec
* @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, Object parentContainer ) {
public boolean applyCardinality( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer ) {
MatchedElement thisLevel = pathElement.match( inputKey, walkedPath );
if ( thisLevel == null ) {
return false;
Expand Down Expand Up @@ -195,56 +201,18 @@ private static void applyKeyToLiteralAndComputed( CardinalityCompositeSpec spec,

// 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, input );
literalChild.applyCardinality( subKeyStr, subInput, walkedPath, input );
} else {
// If no literal spec key matched, iterate through all the computedChildren

// Iterate through all the computedChildren until we find a match
// This relies upon the computedChildren having already been sorted in priority order
for ( CardinalitySpec computedChild : spec.computedChildren ) {
// if the computed key does not match it will quickly return false
if ( computedChild.apply( subKeyStr, subInput, walkedPath, input ) ) {
if ( computedChild.applyCardinality( subKeyStr, subInput, walkedPath, input ) ) {
break;
}
}
}
}

public static class ComputedKeysComparator implements Comparator<CardinalitySpec> {

private static final HashMap<Class, Integer> orderMap = new HashMap<>();

static {
orderMap.put( AmpPathElement.class, 1 );
orderMap.put( StarPathElement.class, 2 );
}

@Override
public int compare( CardinalitySpec a, CardinalitySpec b ) {

PathElement ape = a.pathElement;
PathElement bpe = b.pathElement;

int aa = orderMap.get( ape.getClass() );
int bb = orderMap.get( bpe.getClass() );

int elementsEqual = aa < bb ? -1 : aa == bb ? 0 : 1;

if ( elementsEqual != 0 ) {
return elementsEqual;
}

// At this point we have two PathElements of the same type.
String acf = ape.getCanonicalForm();
String bcf = bpe.getCanonicalForm();

int alen = acf.length();
int blen = bcf.length();

// Sort them by length, with the longest (most specific) being first
// aka "rating-range-*" needs to be evaluated before "rating-*", or else "rating-*" will catch too much
// If the lengths are equal, sort alphabetically as the last ditch deterministic behavior
return alen > blen ? -1 : alen == blen ? acf.compareTo( bcf ) : 1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public CardinalityLeafSpec( String rawKey, Object rhs ) {
* @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, Object parentContainer ) {
public boolean applyCardinality( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer ) {

MatchedElement thisLevel = getMatch( inputKey, walkedPath );
if ( thisLevel == null ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.bazaarvoice.jolt.cardinality;

import com.bazaarvoice.jolt.common.spec.BaseSpec;
import com.bazaarvoice.jolt.common.tree.WalkedPath;
import com.bazaarvoice.jolt.common.pathelement.AtPathElement;
import com.bazaarvoice.jolt.common.pathelement.MatchablePathElement;
Expand All @@ -28,6 +29,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
* A Spec Object represents a single line from the JSON Cardinality Spec.
Expand All @@ -46,7 +48,7 @@
* During the parallel tree walk, a Path<Literal PathElements> is maintained, and used when
* a tree walk encounters a leaf spec.
*/
public abstract class CardinalitySpec {
public abstract class CardinalitySpec implements BaseSpec {

private static final String STAR = "*";
private static final String AT = "@";
Expand Down Expand Up @@ -102,5 +104,15 @@ else if ( key.contains(STAR) ) {
*
* @return true if this this spec "handles" the inputkey such that no sibling specs need to see it
*/
public abstract boolean apply( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer );
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<String, Object> output, final Map<String, Object> context ) {
return applyCardinality( inputKey, input, walkedPath, output );
}

@Override
public MatchablePathElement getPathElement() {
return pathElement;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@

import com.bazaarvoice.jolt.CardinalityTransform;
import com.bazaarvoice.jolt.Chainr;
import com.bazaarvoice.jolt.Defaultr;
import com.bazaarvoice.jolt.JoltTransform;
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;

Expand All @@ -40,13 +40,21 @@ public class ChainrEntry {
* Map transform "operation" names to the classes that handle them
*/
public static final Map<String, String> STOCK_TRANSFORMS;

/**
* getName() returns fqdn$path compared to humanReadablePath from getCanonicalPath()
* to make internal classes available/loadable at runtime it is imperative that we use fqdn
*/
static {
HashMap<String, String> temp = new HashMap<>();
temp.put( "shift", Shiftr.class.getCanonicalName() );
temp.put( "default", Defaultr.class.getCanonicalName() );
temp.put( "remove", Removr.class.getCanonicalName() );
temp.put( "sort", Sortr.class.getCanonicalName() );
temp.put( "cardinality", CardinalityTransform.class.getCanonicalName() );
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( "remove", Removr.class.getName() );
temp.put( "sort", Sortr.class.getName() );
temp.put( "cardinality", CardinalityTransform.class.getName() );
STOCK_TRANSFORMS = Collections.unmodifiableMap( temp );
}

Expand Down Expand Up @@ -98,7 +106,7 @@ public ChainrEntry( int index, Object chainrEntryObj ) {

isSpecDriven = SpecDriven.class.isAssignableFrom( joltTransformClass );
if ( isSpecDriven && ! chainrEntryMap.containsKey( SPEC_KEY ) ) {
throw new SpecException( "JOLT Chainr - Transform className:" + joltTransformClass.getCanonicalName() + " requires a spec" + getErrorMessageIndexSuffix() );
throw new SpecException( "JOLT Chainr - Transform className:" + joltTransformClass.getName() + " requires a spec" + getErrorMessageIndexSuffix() );
}
}

Expand Down
Loading

0 comments on commit 3022676

Please sign in to comment.