From c8e6ce62a2ea7551333d666d16ddba9f11feb7c4 Mon Sep 17 00:00:00 2001 From: Milo Simpson Date: Mon, 25 Apr 2016 10:03:28 -0500 Subject: [PATCH] Enabled all Jolt special chars to be escaped. Previously only '.' could be escaped. Now Escapeable : . @ $ & \ [ ] Specifically added test for Shifting Json-LD stuff, as that seems to be the primary driver of people wanting to be able to escape specs/content with the "@" character. Also, split out the mutable logic from LiteralPathElement into a new MatchedElement class. fixes #197 --- .../jolt/CardinalityTransform.java | 2 +- .../java/com/bazaarvoice/jolt/Shiftr.java | 6 +- .../cardinality/CardinalityCompositeSpec.java | 7 +- .../jolt/cardinality/CardinalityLeafSpec.java | 12 +- .../jolt/cardinality/CardinalitySpec.java | 4 +- .../common/pathelement/AmpPathElement.java | 11 +- .../common/pathelement/ArrayPathElement.java | 11 +- .../common/pathelement/AtPathElement.java | 7 +- .../common/pathelement/DollarPathElement.java | 9 +- .../pathelement/EvaluatablePathElement.java | 2 +- .../common/pathelement/HashPathElement.java | 7 +- .../pathelement/LiteralPathElement.java | 59 ++---- .../pathelement/MatchablePathElement.java | 5 +- .../pathelement/StarAllPathElement.java | 9 +- .../pathelement/StarDoublePathElement.java | 7 +- .../pathelement/StarRegexPathElement.java | 7 +- .../pathelement/StarSinglePathElement.java | 7 +- .../pathelement/TransposePathElement.java | 9 +- .../jolt/common/tree/MatchedElement.java | 93 ++++++++++ .../jolt/common/{ => tree}/PathStep.java | 14 +- .../jolt/common/{ => tree}/WalkedPath.java | 14 +- .../jolt/removr/spec/RemovrSpec.java | 2 +- .../jolt/shiftr/PathEvaluatingTraversal.java | 7 +- .../jolt/shiftr/spec/ShiftrCompositeSpec.java | 7 +- .../jolt/shiftr/spec/ShiftrLeafSpec.java | 8 +- .../jolt/shiftr/spec/ShiftrSpec.java | 175 +++++++++++++----- .../java/com/bazaarvoice/jolt/ShiftrTest.java | 4 + .../StarDoublePathElementTest.java | 11 +- .../pathelement/StarRegexPathElementTest.java | 3 +- .../StarSinglePathElementTest.java | 7 +- .../jolt/shiftr/PathElementTest.java | 13 +- .../jolt/shiftr/ShiftrUnitTest.java | 13 ++ ...SParsingTest.java => SpecParsingTest.java} | 53 +++++- .../json/shiftr/escapeAllTheThings.json | 36 ++++ .../json/shiftr/escapeAllTheThings2.json | 36 ++++ .../json/shiftr/json-ld-escaping.json | 113 +++++++++++ .../json/shiftr/simpleLHSEscape.json | 38 ++++ 37 files changed, 625 insertions(+), 203 deletions(-) create mode 100644 jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/MatchedElement.java rename jolt-core/src/main/java/com/bazaarvoice/jolt/common/{ => tree}/PathStep.java (71%) rename jolt-core/src/main/java/com/bazaarvoice/jolt/common/{ => tree}/WalkedPath.java (81%) rename jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/{RHSParsingTest.java => SpecParsingTest.java} (53%) create mode 100644 jolt-core/src/test/resources/json/shiftr/escapeAllTheThings.json create mode 100644 jolt-core/src/test/resources/json/shiftr/escapeAllTheThings2.json create mode 100644 jolt-core/src/test/resources/json/shiftr/json-ld-escaping.json create mode 100644 jolt-core/src/test/resources/json/shiftr/simpleLHSEscape.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 c4088b12..aa16c42f 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/CardinalityTransform.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/CardinalityTransform.java @@ -17,7 +17,7 @@ import com.bazaarvoice.jolt.cardinality.CardinalityCompositeSpec; import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import javax.inject.Inject; import java.util.Map; 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 2fbfe815..d7eb8a23 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/Shiftr.java @@ -16,8 +16,8 @@ package com.bazaarvoice.jolt; import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.common.WalkedPath; -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.shiftr.spec.ShiftrCompositeSpec; import javax.inject.Inject; @@ -498,7 +498,7 @@ public Object transform( Object input ) { Map output = new HashMap<>(); // Create a root LiteralPathElement so that # is useful at the root level - LiteralPathElement rootLpe = new LiteralPathElement( ROOT_KEY ); + MatchedElement rootLpe = new MatchedElement( ROOT_KEY ); WalkedPath walkedPath = new WalkedPath(); walkedPath.add( input, rootLpe ); diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityCompositeSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityCompositeSpec.java index eae2f4b6..30888e87 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityCompositeSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityCompositeSpec.java @@ -15,11 +15,12 @@ */ package com.bazaarvoice.jolt.cardinality; -import com.bazaarvoice.jolt.common.WalkedPath; +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.pathelement.LiteralPathElement; +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; @@ -136,7 +137,7 @@ private static List createChildren( Map rawSpec */ @Override public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer ) { - LiteralPathElement thisLevel = pathElement.match( inputKey, walkedPath ); + MatchedElement thisLevel = pathElement.match( inputKey, walkedPath ); if ( thisLevel == null ) { return false; } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityLeafSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityLeafSpec.java index c33dfcc5..b57dbe14 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityLeafSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/cardinality/CardinalityLeafSpec.java @@ -15,8 +15,8 @@ */ package com.bazaarvoice.jolt.cardinality; -import com.bazaarvoice.jolt.common.WalkedPath; -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.exception.SpecException; import java.util.ArrayList; @@ -58,7 +58,7 @@ public CardinalityLeafSpec( String rawKey, Object rhs ) { @Override public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer ) { - LiteralPathElement thisLevel = getMatch( inputKey, walkedPath ); + MatchedElement thisLevel = getMatch( inputKey, walkedPath ); if ( thisLevel == null ) { return false; } @@ -73,7 +73,7 @@ public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Obje */ public Object applyToParentContainer ( String inputKey, Object input, WalkedPath walkedPath, Object parentContainer ) { - LiteralPathElement thisLevel = getMatch( inputKey, walkedPath ); + MatchedElement thisLevel = getMatch( inputKey, walkedPath ); if ( thisLevel == null ) { return null; } @@ -84,7 +84,7 @@ public Object applyToParentContainer ( String inputKey, Object input, WalkedPath * * @return null if no work was done, otherwise returns the re-parented data */ - private Object performCardinalityAdjustment( String inputKey, Object input, WalkedPath walkedPath, Map parentContainer, LiteralPathElement thisLevel ) { + private Object performCardinalityAdjustment( String inputKey, Object input, WalkedPath walkedPath, Map parentContainer, MatchedElement thisLevel ) { // Add our the LiteralPathElement for this level, so that write path References can use it as &(0,0) walkedPath.add( input, thisLevel ); @@ -120,7 +120,7 @@ else if ( cardinalityRelationship == CardinalityRelationship.ONE ) { return returnValue; } - private LiteralPathElement getMatch( String inputKey, WalkedPath walkedPath ) { + private MatchedElement getMatch( String inputKey, WalkedPath walkedPath ) { return pathElement.match( inputKey, walkedPath ); } } 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 e8f7334e..b2e76703 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,11 +15,11 @@ */ package com.bazaarvoice.jolt.cardinality; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.common.pathelement.AtPathElement; -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.common.pathelement.MatchablePathElement; import com.bazaarvoice.jolt.common.pathelement.PathElement; +import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.common.pathelement.StarAllPathElement; import com.bazaarvoice.jolt.common.pathelement.StarRegexPathElement; import com.bazaarvoice.jolt.common.pathelement.StarSinglePathElement; diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AmpPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AmpPathElement.java index 46d0ca7a..c662c85c 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AmpPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AmpPathElement.java @@ -16,7 +16,8 @@ package com.bazaarvoice.jolt.common.pathelement; import com.bazaarvoice.jolt.common.reference.AmpReference; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import java.util.ArrayList; import java.util.Collections; @@ -114,8 +115,8 @@ public String evaluate( WalkedPath walkedPath ) { } else { AmpReference ref = (AmpReference) token; - LiteralPathElement literalPathElement = walkedPath.elementFromEnd( ref.getPathIndex() ).getLiteralPathElement(); - String value = literalPathElement.getSubKeyRef( ref.getKeyGroup() ); + MatchedElement matchedElement = walkedPath.elementFromEnd( ref.getPathIndex() ).getMatchedElement(); + String value = matchedElement.getSubKeyRef( ref.getKeyGroup() ); output.append( value ); } } @@ -124,10 +125,10 @@ public String evaluate( WalkedPath walkedPath ) { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { String evaled = evaluate( walkedPath ); if ( evaled.equals( dataKey ) ) { - return new LiteralPathElement( evaled ); + return new MatchedElement( evaled ); } return null; } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/ArrayPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/ArrayPathElement.java index 8fe817cc..875625c5 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/ArrayPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/ArrayPathElement.java @@ -15,9 +15,10 @@ */ package com.bazaarvoice.jolt.common.pathelement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.common.reference.AmpReference; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.common.reference.HashReference; import com.bazaarvoice.jolt.common.reference.PathAndGroupReference; import com.bazaarvoice.jolt.common.reference.PathReference; @@ -106,7 +107,7 @@ public String evaluate( WalkedPath walkedPath ) { return arrayIndex; case HASH: - LiteralPathElement element = walkedPath.elementFromEnd( ref.getPathIndex() ).getLiteralPathElement(); + MatchedElement element = walkedPath.elementFromEnd( ref.getPathIndex() ).getMatchedElement(); Integer index = element.getHashCount(); return index.toString(); @@ -115,7 +116,7 @@ public String evaluate( WalkedPath walkedPath ) { return verifyStringIsNonNegativeInteger( key ); case REFERENCE: - LiteralPathElement lpe = walkedPath.elementFromEnd( ref.getPathIndex() ).getLiteralPathElement(); + MatchedElement lpe = walkedPath.elementFromEnd( ref.getPathIndex() ).getMatchedElement(); String keyPart; if ( ref instanceof PathAndGroupReference ) { @@ -153,10 +154,10 @@ private static String verifyStringIsNonNegativeInteger( String key ) { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { String evaled = evaluate( walkedPath ); if ( evaled.equals( dataKey ) ) { - return new LiteralPathElement( evaled ); + return new MatchedElement( evaled ); } return null; } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AtPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AtPathElement.java index a7500c05..b72a32f3 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AtPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/AtPathElement.java @@ -15,8 +15,9 @@ */ package com.bazaarvoice.jolt.common.pathelement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; public class AtPathElement extends BasePathElement implements MatchablePathElement { public AtPathElement( String key ) { @@ -27,8 +28,8 @@ public AtPathElement( String key ) { } } - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { - return walkedPath.lastElement().getLiteralPathElement(); // copy what our parent was so that write keys of &0 and &1 both work. + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { + return walkedPath.lastElement().getMatchedElement(); // copy what our parent was so that write keys of &0 and &1 both work. } @Override diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/DollarPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/DollarPathElement.java index da054671..72fb4438 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/DollarPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/DollarPathElement.java @@ -16,7 +16,8 @@ package com.bazaarvoice.jolt.common.pathelement; import com.bazaarvoice.jolt.common.reference.DollarReference; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; public class DollarPathElement extends BasePathElement implements MatchablePathElement, EvaluatablePathElement { @@ -35,13 +36,13 @@ public String getCanonicalForm() { @Override public String evaluate( WalkedPath walkedPath ) { - LiteralPathElement pe = walkedPath.elementFromEnd( dRef.getPathIndex() ).getLiteralPathElement(); + MatchedElement pe = walkedPath.elementFromEnd( dRef.getPathIndex() ).getMatchedElement(); return pe.getSubKeyRef( dRef.getKeyGroup() ); } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { String evaled = evaluate( walkedPath ); - return new LiteralPathElement( evaled ); + return new MatchedElement( evaled ); } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/EvaluatablePathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/EvaluatablePathElement.java index 264d372c..62e36bb3 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/EvaluatablePathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/EvaluatablePathElement.java @@ -15,7 +15,7 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; public interface EvaluatablePathElement extends PathElement { diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/HashPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/HashPathElement.java index a87a2f8f..6cdc0fff 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/HashPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/HashPathElement.java @@ -15,7 +15,8 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.utils.StringTools; @@ -62,7 +63,7 @@ public String getCanonicalForm() { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { - return new LiteralPathElement( keyValue ); + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { + return new MatchedElement( keyValue ); } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/LiteralPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/LiteralPathElement.java index f396fe24..1db1570a 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/LiteralPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/LiteralPathElement.java @@ -15,39 +15,21 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; +/** + * Meant to be an immutable PathElement from a Spec, and therefore shareable across + * threads running multiple transforms using the same spec. + */ public class LiteralPathElement extends BasePathElement implements MatchablePathElement, EvaluatablePathElement { - private final List subKeys; - - private int hashCount = 0; + private final String canonicalForm; public LiteralPathElement( String key ) { super(key); - List keys = new ArrayList<>(1); - keys.add( key ); // always add the full key to index 0 - - this.subKeys = Collections.unmodifiableList( keys ); - } - - public LiteralPathElement( String key, List subKeys ) { - super(key); - - if ( subKeys == null ) { - throw new IllegalArgumentException( "LiteralPathElement for key:" + key + " got null list of subKeys" ); - } - - List keys = new ArrayList<>( 1 + subKeys.size() ); - keys.add( key ); // always add the full key to index 0 - keys.addAll( subKeys ); - - this.subKeys = Collections.unmodifiableList( keys ); + this.canonicalForm = key.replace( ".", "\\." ); } @Override @@ -56,34 +38,15 @@ public String evaluate( WalkedPath walkedPath ) { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { if ( getRawKey().equals( dataKey ) ) { - return new LiteralPathElement( getRawKey(), subKeys ); + return new MatchedElement( getRawKey() ); } return null; } @Override public String getCanonicalForm() { - return getRawKey(); - } - - public String getSubKeyRef( int index ) { - if ((index < 0) || (index >= this.subKeys.size())) { - throw new IndexOutOfBoundsException( "LiteralPathElement "+ this.subKeys +" cannot be indexed with index "+index ); - } - return subKeys.get( index ); - } - - public int getSubKeyCount(){ - return subKeys.size(); - } - - public int getHashCount() { - return hashCount; - } - - public void incrementHashCount() { - hashCount++; + return canonicalForm; } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/MatchablePathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/MatchablePathElement.java index d33d6ffd..469796f6 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/MatchablePathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/MatchablePathElement.java @@ -15,7 +15,8 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; public interface MatchablePathElement extends PathElement { @@ -28,5 +29,5 @@ public interface MatchablePathElement extends PathElement { * @param walkedPath "up the tree" list of LiteralPathElements, that may be used by this key as it is computing its match * @return null or a matched LiteralPathElement */ - LiteralPathElement match( String dataKey, WalkedPath walkedPath ); + MatchedElement match( String dataKey, WalkedPath walkedPath ); } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarAllPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarAllPathElement.java index 60759a6b..ec734db4 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarAllPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarAllPathElement.java @@ -15,9 +15,8 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; - -import java.util.Collections; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; /** * PathElement for the lone "*" wildcard. In this case we can avoid doing any @@ -41,8 +40,8 @@ public boolean stringMatch( String literal ) { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { - return new LiteralPathElement(dataKey, Collections.emptyList() ); + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { + return new MatchedElement( dataKey ); } @Override diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElement.java index 47d49dcd..cfaf6e7f 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElement.java @@ -15,7 +15,8 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.utils.StringTools; import java.util.ArrayList; @@ -113,7 +114,7 @@ private int finMidIndex(String literal){ @Override - public LiteralPathElement match(String dataKey, WalkedPath walkedPath) { + public MatchedElement match(String dataKey, WalkedPath walkedPath) { if ( stringMatch( dataKey ) ) { List subKeys = new ArrayList<>(2); @@ -126,7 +127,7 @@ public LiteralPathElement match(String dataKey, WalkedPath walkedPath) { String secondStarPart = dataKey.substring( midEnd, dataKey.length() - suffix.length() ); subKeys.add( secondStarPart ); - return new LiteralPathElement(dataKey, subKeys); + return new MatchedElement(dataKey, subKeys); } return null; } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElement.java index 7462fe77..0e50f400 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElement.java @@ -15,7 +15,8 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import java.util.*; import java.util.regex.Matcher; @@ -119,7 +120,7 @@ public boolean stringMatch( String literal ) { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { Matcher matcher = pattern.matcher( dataKey ); if ( ! matcher.find() ) { @@ -133,7 +134,7 @@ public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { subKeys.add( matcher.group( index ) ); } - return new LiteralPathElement(dataKey, subKeys); + return new MatchedElement(dataKey, subKeys); } @Override diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElement.java index 5bf9b95f..7cbe3bf5 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElement.java @@ -15,7 +15,8 @@ */ package com.bazaarvoice.jolt.common.pathelement; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.utils.StringTools; import java.util.ArrayList; @@ -66,7 +67,7 @@ public boolean stringMatch( String literal ) { } @Override - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { if ( stringMatch( dataKey ) ) { List subKeys = new ArrayList<>(1); @@ -74,7 +75,7 @@ public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { String starPart = dataKey.substring( prefix.length(), dataKey.length() - suffix.length() ); subKeys.add( starPart ); - return new LiteralPathElement(dataKey, subKeys); + return new MatchedElement(dataKey, subKeys); } return null; diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/TransposePathElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/TransposePathElement.java index 4a6eced7..077d0005 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/TransposePathElement.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/pathelement/TransposePathElement.java @@ -16,8 +16,9 @@ package com.bazaarvoice.jolt.common.pathelement; import com.bazaarvoice.jolt.common.Optional; -import com.bazaarvoice.jolt.common.PathStep; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.MatchedElement; +import com.bazaarvoice.jolt.common.tree.PathStep; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.shiftr.TransposeReader; import com.bazaarvoice.jolt.utils.StringTools; @@ -237,8 +238,8 @@ public String evaluate( WalkedPath walkedPath ) { } } - public LiteralPathElement match( String dataKey, WalkedPath walkedPath ) { - return walkedPath.lastElement().getLiteralPathElement(); // copy what our parent was so that write keys of &0 and &1 both work. + public MatchedElement match( String dataKey, WalkedPath walkedPath ) { + return walkedPath.lastElement().getMatchedElement(); // copy what our parent was so that write keys of &0 and &1 both work. } @Override diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/MatchedElement.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/MatchedElement.java new file mode 100644 index 00000000..9703700b --- /dev/null +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/MatchedElement.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016 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.common.tree; + +import com.bazaarvoice.jolt.common.pathelement.BasePathElement; +import com.bazaarvoice.jolt.common.pathelement.EvaluatablePathElement; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * MatchedElement is the result of a "match" between a spec PathElement and some input data. + * + * MatchedElements are not thread safe, and should instead be stack / single Thread/Transform specific. + * + * This mutability was specifically added for the the HashCount functionality, which allows Shiftr + * to transform data form maps to lists. + */ +public class MatchedElement extends BasePathElement implements EvaluatablePathElement { + + private final List subKeys; + + private int hashCount = 0; + + public MatchedElement( String key ) { + super(key); + + List keys = new ArrayList<>(1); + keys.add( key ); // always add the full key to index 0 + + this.subKeys = Collections.unmodifiableList( keys ); + } + + public MatchedElement( String key, List subKeys ) { + super(key); + + if ( subKeys == null ) { + throw new IllegalArgumentException( "MatchedElement for key:" + key + " got null list of subKeys" ); + } + + List keys = new ArrayList<>( 1 + subKeys.size() ); + keys.add( key ); // always add the full key to index 0 + keys.addAll( subKeys ); + + this.subKeys = Collections.unmodifiableList( keys ); + } + + @Override + public String evaluate( WalkedPath walkedPath ) { + return getRawKey(); + } + + @Override + public String getCanonicalForm() { + return getRawKey(); + } + + public String getSubKeyRef( int index ) { + if ((index < 0) || (index >= this.subKeys.size())) { + throw new IndexOutOfBoundsException( "MatchedElement "+ this.subKeys +" cannot be indexed with index "+index ); + } + return subKeys.get( index ); + } + + public int getSubKeyCount(){ + return subKeys.size(); + } + + public int getHashCount() { + return hashCount; + } + + /** + * Here be mutability... + */ + public void incrementHashCount() { + hashCount++; + } +} diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/PathStep.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/PathStep.java similarity index 71% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/common/PathStep.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/PathStep.java index 63307334..eb43c349 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/PathStep.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/PathStep.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.bazaarvoice.jolt.common; - -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; +package com.bazaarvoice.jolt.common.tree; /** * A tuple class that contains the data for one level of a @@ -24,18 +22,18 @@ */ public final class PathStep { private final Object treeRef; - private final LiteralPathElement literalPathElement; + private final MatchedElement matchedElement; - public PathStep(Object treeRef, LiteralPathElement literalPathElement) { + public PathStep(Object treeRef, MatchedElement matchedElement ) { this.treeRef = treeRef; - this.literalPathElement = literalPathElement; + this.matchedElement = matchedElement; } public Object getTreeRef() { return treeRef; } - public LiteralPathElement getLiteralPathElement() { - return literalPathElement; + public MatchedElement getMatchedElement() { + return matchedElement; } } diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/WalkedPath.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/WalkedPath.java similarity index 81% rename from jolt-core/src/main/java/com/bazaarvoice/jolt/common/WalkedPath.java rename to jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/WalkedPath.java index 085276e1..6a2971e6 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/common/WalkedPath.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/common/tree/WalkedPath.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.bazaarvoice.jolt.common; - -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; +package com.bazaarvoice.jolt.common.tree; import java.util.ArrayList; import java.util.Collection; @@ -31,7 +29,7 @@ * It is primarily used to by the ShiftrLeafSpec and CardinalityLeafSpec as a reference * to lookup real values for output "&(1,1)" references. * - * It is expected that as the SpecTransform navigates down the tree, LiteralElements will be added and then + * It is expected that as the SpecTransform navigates down the tree, MatchedElements will be added and then * removed when that subtree has been walked. */ public class WalkedPath extends ArrayList { @@ -44,16 +42,16 @@ public WalkedPath(Collection c) { super(c); } - public WalkedPath( Object treeRef, LiteralPathElement literalPathElement ) { + public WalkedPath( Object treeRef, MatchedElement matchedElement ) { super(); - this.add( new PathStep( treeRef, literalPathElement ) ); + this.add( new PathStep( treeRef, matchedElement ) ); } /** * Convenience method */ - public boolean add( Object treeRef, LiteralPathElement literalPathElement ) { - return super.add( new PathStep( treeRef, literalPathElement ) ); + public boolean add( Object treeRef, MatchedElement matchedElement ) { + return super.add( new PathStep( treeRef, matchedElement ) ); } public void removeLast() { diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/removr/spec/RemovrSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/removr/spec/RemovrSpec.java index 316b4a92..74926c69 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/removr/spec/RemovrSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/removr/spec/RemovrSpec.java @@ -17,11 +17,11 @@ import com.bazaarvoice.jolt.common.pathelement.MatchablePathElement; import com.bazaarvoice.jolt.common.pathelement.PathElement; +import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.common.pathelement.StarSinglePathElement; import com.bazaarvoice.jolt.common.pathelement.StarAllPathElement; import com.bazaarvoice.jolt.common.pathelement.StarDoublePathElement; import com.bazaarvoice.jolt.common.pathelement.StarRegexPathElement; -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.utils.StringTools; diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/PathEvaluatingTraversal.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/PathEvaluatingTraversal.java index 7184019a..1b942abe 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/PathEvaluatingTraversal.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/PathEvaluatingTraversal.java @@ -16,7 +16,7 @@ package com.bazaarvoice.jolt.shiftr; import com.bazaarvoice.jolt.common.Optional; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.common.pathelement.EvaluatablePathElement; import com.bazaarvoice.jolt.common.pathelement.PathElement; import com.bazaarvoice.jolt.exception.SpecException; @@ -42,8 +42,9 @@ public abstract class PathEvaluatingTraversal { public PathEvaluatingTraversal( String dotNotation ) { - if ( dotNotation.contains("*") || dotNotation.contains("$")) { - throw new SpecException("DotNotation (write key) can not contain '*' or '$' : write key: " + dotNotation ); + if ( ( dotNotation.contains("*") && ! dotNotation.contains( "\\*" ) ) || + ( dotNotation.contains("$") && ! dotNotation.contains( "\\$" ) ) ) { + throw new SpecException("DotNotation (write key) can not contain '*' or '$' : write key: " + dotNotation ); } List paths; 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 d2c0cb15..3baf2ad4 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 @@ -17,8 +17,9 @@ import com.bazaarvoice.jolt.common.Optional; import com.bazaarvoice.jolt.common.pathelement.*; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import java.util.*; @@ -164,7 +165,7 @@ List getComputedChildren() { @Override public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map output ) { - LiteralPathElement thisLevel = pathElement.match( inputKey, walkedPath ); + MatchedElement thisLevel = pathElement.match( inputKey, walkedPath ); if ( thisLevel == null ) { return false; } @@ -200,7 +201,7 @@ public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map< walkedPath.removeLast(); // we matched so increment the matchCount of our parent - walkedPath.lastElement().getLiteralPathElement().incrementHashCount(); + walkedPath.lastElement().getMatchedElement().incrementHashCount(); return true; } 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 4a2cf097..38382465 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 @@ -22,10 +22,10 @@ import com.bazaarvoice.jolt.shiftr.ShiftrWriter; import com.bazaarvoice.jolt.utils.StringTools; import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.common.pathelement.AtPathElement; import com.bazaarvoice.jolt.common.pathelement.DollarPathElement; -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import java.util.Arrays; import java.util.Collections; @@ -101,7 +101,7 @@ private static ShiftrWriter parseOutputDotNotation( Object rawObj ) { @Override public boolean apply( String inputKey, Object input, WalkedPath walkedPath, Map output ){ - LiteralPathElement thisLevel = pathElement.match( inputKey, walkedPath ); + MatchedElement thisLevel = pathElement.match( inputKey, walkedPath ); if ( thisLevel == null ) { return false; } @@ -153,7 +153,7 @@ else if ( this.pathElement instanceof TransposePathElement ) { if ( realChild ) { // we were a "real" child, so increment the matchCount of our parent - walkedPath.lastElement().getLiteralPathElement().incrementHashCount(); + walkedPath.lastElement().getMatchedElement().incrementHashCount(); } return realChild; diff --git a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrSpec.java b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrSpec.java index 91f0f406..8014256f 100644 --- a/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrSpec.java +++ b/jolt-core/src/main/java/com/bazaarvoice/jolt/shiftr/spec/ShiftrSpec.java @@ -17,7 +17,7 @@ import com.bazaarvoice.jolt.common.pathelement.*; import com.bazaarvoice.jolt.exception.SpecException; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.utils.StringTools; import java.util.ArrayList; @@ -77,72 +77,141 @@ public ShiftrSpec(String rawJsonKey) { * Inspects the key in a particular order to determine the correct sublass of * PathElement to create. * - * @param key String that should represent a single PathElement + * @param origKey String that should represent a single PathElement * @return a concrete implementation of PathElement */ - public static PathElement parseSingleKeyLHS( String key ) { + public static PathElement parseSingleKeyLHS( String origKey ) { + + String elementKey; // the String to use to actually make Elements + String keyToInspect; // the String to use to determine which kind of Element to create + + if ( origKey.contains( "\\" ) ) { + // only do the extra work of processing for escaped chars, if there is one. + keyToInspect = removeEscapedValues(origKey); + elementKey = removeEscapeChars( origKey ); + } + else { + keyToInspect = origKey; + elementKey = origKey; + } //// LHS single values - if ( "@".equals( key ) ) { - return new AtPathElement( key ); + if ( "@".equals( keyToInspect ) ) { + return new AtPathElement( elementKey ); } - else if ( "*".equals( key ) ) { - return new StarAllPathElement( key ); + else if ( "*".equals( keyToInspect ) ) { + return new StarAllPathElement( elementKey ); } - else if ( key.startsWith( "[" ) ) { + else if ( keyToInspect.startsWith( "[" ) ) { - if ( StringTools.countMatches(key, "[") != 1 || StringTools.countMatches(key, "]") != 1 ) { - throw new SpecException( "Invalid key:" + key + " has too many [] references."); + if ( StringTools.countMatches(keyToInspect, "[") != 1 || StringTools.countMatches(keyToInspect, "]") != 1 ) { + throw new SpecException( "Invalid key:" + origKey + " has too many [] references."); } - return new ArrayPathElement( key ); + return new ArrayPathElement( elementKey ); } //// LHS multiple values - else if ( key.startsWith("@") || key.contains( "@(" ) ) { - return TransposePathElement.parse( key ); + else if ( keyToInspect.startsWith("@") || keyToInspect.contains( "@(" ) ) { + // The traspose path element gets the origKey so that it has it's escapes. + return TransposePathElement.parse( origKey ); } - else if ( key.contains( "@" ) ) { - throw new SpecException( "Invalid key:" + key + " can not have an @ other than at the front." ); + else if ( keyToInspect.contains( "@" ) ) { + throw new SpecException( "Invalid key:" + origKey + " can not have an @ other than at the front." ); } - else if ( key.contains("$") ) { - return new DollarPathElement( key ); + else if ( keyToInspect.contains("$") ) { + return new DollarPathElement( elementKey ); } - else if ( key.contains("[") ) { + else if ( keyToInspect.contains("[") ) { - if ( StringTools.countMatches(key, "[") != 1 || StringTools.countMatches(key, "]") != 1 ) { - throw new SpecException( "Invalid key:" + key + " has too many [] references."); + if ( StringTools.countMatches(keyToInspect, "[") != 1 || StringTools.countMatches(keyToInspect, "]") != 1 ) { + throw new SpecException( "Invalid key:" + origKey + " has too many [] references."); } - return new ArrayPathElement( key ); + return new ArrayPathElement( elementKey ); } - else if ( key.contains( "&" ) ) { + else if ( keyToInspect.contains( "&" ) ) { - if ( key.contains("*") ) + if ( keyToInspect.contains("*") ) { - throw new SpecException("Can't mix * with & ) "); + throw new SpecException( "Invalid key:" + origKey + ", Can't mix * with & ) "); } - return new AmpPathElement( key ); + return new AmpPathElement( elementKey ); } - else if ( key.contains("*" ) ) { + else if ( keyToInspect.contains("*" ) ) { - int numOfStars = StringTools.countMatches(key, "*"); + int numOfStars = StringTools.countMatches(keyToInspect, "*"); if(numOfStars == 1){ - return new StarSinglePathElement( key ); + return new StarSinglePathElement( elementKey ); } else if(numOfStars == 2){ - return new StarDoublePathElement( key ); + return new StarDoublePathElement( elementKey ); } else { - return new StarRegexPathElement( key ); + return new StarRegexPathElement( elementKey ); } } - else if ( key.contains("#" ) ) { - return new HashPathElement( key ); + else if ( keyToInspect.contains("#" ) ) { + return new HashPathElement( elementKey ); } else { - return new LiteralPathElement( key ); + return new LiteralPathElement( elementKey ); + } + } + + // Visible for Testing + // given "\@pants" -> "pants" starts with escape + // given "rating-\&pants" -> "rating-pants" escape in the middle + // given "rating\\pants" -> "ratingpants" escape the escape char + static String removeEscapedValues(String origKey) { + StringBuilder sb = new StringBuilder(); + + boolean prevWasEscape = false; + for ( char c : origKey.toCharArray() ) { + if ( '\\' == c ) { + if ( prevWasEscape ) { + prevWasEscape = false; + } + else { + prevWasEscape = true; + } + } + else { + if ( ! prevWasEscape ) { + sb.append( c ); + } + prevWasEscape = false; + } } + + return sb.toString(); + } + + // Visible for Testing + // given "\@pants" -> "@pants" starts with escape + // given "rating-\&pants" -> "rating-&pants" escape in the middle + // given "rating\\pants" -> "rating\pants" escape the escape char + static String removeEscapeChars( String origKey ) { + StringBuilder sb = new StringBuilder(); + + boolean prevWasEscape = false; + for ( char c : origKey.toCharArray() ) { + if ( '\\' == c ) { + if ( prevWasEscape ) { + sb.append( c ); + prevWasEscape = false; + } + else { + prevWasEscape = true; + } + } + else { + sb.append( c ); + prevWasEscape = false; + } + } + + return sb.toString(); } @@ -200,7 +269,7 @@ private static String fixLeadingBracketSugar( String dotNotaton ) { for ( int index = 1; index < dotNotaton.length(); index++ ) { char curr = dotNotaton.charAt( index ); - if ( curr == '[' ) { + if ( curr == '[' && prev != '\\') { if ( prev == '@' || prev == '.' ) { // no need to add an extra '.' } @@ -297,13 +366,18 @@ else if ( c == '.' ) { * @param dotNotationRef the original dotNotation string used for error messages * @return */ - public static List parseDotNotation( List pathStrings, Iterator iter, String dotNotationRef ) { + public static List parseDotNotation( List pathStrings, Iterator iter, + String dotNotationRef ) { if ( ! iter.hasNext() ) { return pathStrings; } - boolean escapeActive = false; + // Leave the forward slashes, unless it precedes a "." + // The way this works is always supress the forward slashes, but add them back in if the next char is not a "." + + boolean prevIsEscape = false; + boolean currIsEscape = false; StringBuilder sb = new StringBuilder(); char c; @@ -311,12 +385,18 @@ public static List parseDotNotation( List pathStrings, Iterator< c = iter.next(); - if ( c == '\\' ) { - // two escapes lets one thru - escapeActive = ! escapeActive; + currIsEscape = false; + if ( c == '\\' && ! prevIsEscape ) { + // current is Escape only if the char is escape, or + // it is an Escape and the prior char was, then don't consider this one an escape + currIsEscape = true; } - if( c == '@' ) { + if ( prevIsEscape && c != '.' && c != '\\') { + sb.append( '\\' ); + sb.append( c ); + } + else if( c == '@' ) { sb.append( '@' ); sb.append( parseAtPathElement( iter, dotNotationRef ) ); @@ -328,8 +408,9 @@ public static List parseDotNotation( List pathStrings, Iterator< } } else if ( c == '.' ) { - if ( escapeActive ) { - sb.append( c ); + + if ( prevIsEscape ) { + sb.append( '.' ); } else { if ( sb.length() != 0 ) { @@ -338,15 +419,11 @@ else if ( c == '.' ) { return parseDotNotation( pathStrings, iter, dotNotationRef ); } } - else { - if ( ! escapeActive ) { - sb.append( c ); - } + else if ( ! currIsEscape ) { + sb.append( c ); } - if ( c != '\\' ) { - escapeActive = false; - } + prevIsEscape = currIsEscape; } if ( sb.length() != 0 ) { diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/ShiftrTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/ShiftrTest.java index 5fd46509..6191e3b6 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/ShiftrTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/ShiftrTest.java @@ -31,6 +31,8 @@ public Object[][] getTestCaseUnits() { {"arrayMismatch"}, {"bucketToPrefixSoup"}, {"declaredOutputArray"}, + {"escapeAllTheThings"}, + {"escapeAllTheThings2"}, {"explicitArrayKey"}, {"filterParallelArrays"}, {"filterParents1"}, @@ -40,6 +42,7 @@ public Object[][] getTestCaseUnits() { {"identity"}, {"inputArrayToPrefix"}, {"invertMap"}, + {"json-ld-escaping"}, {"keyref"}, {"lhsAmpMatch"}, {"listKeys"}, @@ -57,6 +60,7 @@ public Object[][] getTestCaseUnits() { {"prefixSoupToBuckets"}, {"queryMappingXform"}, {"shiftToTrash"}, + {"simpleLHSEscape"}, {"simpleRHSEscape"}, {"singlePlacement"}, {"specialKeys"}, diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElementTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElementTest.java index c88185b7..1ce8a6ca 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElementTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarDoublePathElementTest.java @@ -15,6 +15,7 @@ */ package com.bazaarvoice.jolt.common.pathelement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import org.testng.Assert; import org.testng.annotations.Test; @@ -29,7 +30,7 @@ public void testStarInFirstAndMiddle() { Assert.assertFalse( star.stringMatch( "abbbbbbbbcc" ) ); Assert.assertFalse( star.stringMatch( "bbba" ) ); - LiteralPathElement lpe = star.match( "bbbaccc", null ); + MatchedElement lpe = star.match( "bbbaccc", null ); // * -> bbb // a -> a // * -> ccc @@ -50,7 +51,7 @@ public void testStarAtFrontAndEnd() { Assert.assertFalse( star.stringMatch( "bac" ) ); Assert.assertFalse( star.stringMatch( "baa" ) ); - LiteralPathElement lpe = star.match( "abcadefc", null ); + MatchedElement lpe = star.match( "abcadefc", null ); // * -> abc // a -> a index 4 // * -> def @@ -73,7 +74,7 @@ public void testStarAtMiddleAndEnd() { Assert.assertFalse( star.stringMatch( "addb" ) ); Assert.assertFalse( star.stringMatch( "abc" ) ); - LiteralPathElement lpe = star.match( "abcbbac", null ); + MatchedElement lpe = star.match( "abcbbac", null ); // a -> a // * -> bc index 1 // b -> b index 3 @@ -95,7 +96,7 @@ public void testStarsInMiddle() { Assert.assertTrue( star.stringMatch( "a123b456c" ) ); Assert.assertTrue( star.stringMatch( "abccbcc" ) ); - LiteralPathElement lpe = star.match( "abccbcc", null ); + MatchedElement lpe = star.match( "abccbcc", null ); // a -> a // * -> bcc index 1 // b -> b @@ -114,7 +115,7 @@ public void testStarsInMiddleNonGreedy() { StarPathElement star = new StarDoublePathElement( "a*b*c" ); - LiteralPathElement lpe = star.match( "abbccbccc", null ); + MatchedElement lpe = star.match( "abbccbccc", null ); // a -> a // * -> b index 1 // b -> b diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElementTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElementTest.java index 08d64d9d..1a3bec0e 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElementTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarRegexPathElementTest.java @@ -15,6 +15,7 @@ */ package com.bazaarvoice.jolt.common.pathelement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -36,7 +37,7 @@ public void starPatternTest( String testName, String spec, String dataKey, Strin StarPathElement star = new StarRegexPathElement( spec ); - LiteralPathElement lpe = star.match( dataKey, null ); + MatchedElement lpe = star.match( dataKey, null ); Assert.assertEquals( 3, lpe.getSubKeyCount() ); Assert.assertEquals( dataKey, lpe.getSubKeyRef( 0 ) ); diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElementTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElementTest.java index a70c9d45..3822a655 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElementTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/common/pathelement/StarSinglePathElementTest.java @@ -15,6 +15,7 @@ */ package com.bazaarvoice.jolt.common.pathelement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import org.testng.Assert; import org.testng.annotations.Test; @@ -30,7 +31,7 @@ public void testStarAtFront() { Assert.assertFalse( star.stringMatch( "tuna" ) ); Assert.assertFalse( star.stringMatch( "tuna-bob" ) ); - LiteralPathElement lpe = star.match( "bob-tuna", null ); + MatchedElement lpe = star.match( "bob-tuna", null ); Assert.assertEquals( "bob-tuna", lpe.getSubKeyRef( 0 ) ); Assert.assertEquals( "bob", lpe.getSubKeyRef( 1 ) ); Assert.assertEquals( 2, lpe.getSubKeyCount() ); @@ -48,7 +49,7 @@ public void testStarAtEnd() { Assert.assertFalse( star.stringMatch( "tuna" ) ); Assert.assertFalse( star.stringMatch( "bob-tuna" ) ); - LiteralPathElement lpe = star.match( "tuna-bob", null ); + MatchedElement lpe = star.match( "tuna-bob", null ); Assert.assertEquals( "tuna-bob", lpe.getSubKeyRef( 0 ) ); Assert.assertEquals( "bob", lpe.getSubKeyRef( 1 ) ); Assert.assertEquals( 2, lpe.getSubKeyCount() ); @@ -66,7 +67,7 @@ public void testStarInMiddle() { Assert.assertFalse( star.stringMatch( "tunamarlin" ) ); Assert.assertFalse( star.stringMatch( "marlin-bob-tuna" ) ); - LiteralPathElement lpe = star.match( "tuna-bob-marlin", null ); + MatchedElement lpe = star.match( "tuna-bob-marlin", null ); Assert.assertEquals( "tuna-bob-marlin", lpe.getSubKeyRef( 0 ) ); Assert.assertEquals( "bob", lpe.getSubKeyRef( 1 ) ); Assert.assertEquals( 2, lpe.getSubKeyCount() ); diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/PathElementTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/PathElementTest.java index fd418951..8b57734a 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/PathElementTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/PathElementTest.java @@ -15,13 +15,14 @@ */ package com.bazaarvoice.jolt.shiftr; -import com.bazaarvoice.jolt.common.WalkedPath; +import com.bazaarvoice.jolt.common.tree.WalkedPath; import com.bazaarvoice.jolt.common.pathelement.AmpPathElement; import com.bazaarvoice.jolt.common.pathelement.ArrayPathElement; import com.bazaarvoice.jolt.common.pathelement.EvaluatablePathElement; -import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; +import com.bazaarvoice.jolt.common.tree.MatchedElement; import com.bazaarvoice.jolt.common.pathelement.MatchablePathElement; import com.bazaarvoice.jolt.common.pathelement.PathElement; +import com.bazaarvoice.jolt.common.pathelement.LiteralPathElement; import com.bazaarvoice.jolt.common.reference.AmpReference; import com.bazaarvoice.jolt.shiftr.spec.ShiftrSpec; import org.testng.Assert; @@ -107,7 +108,7 @@ public void calculateOutputTest_refsOnly() { MatchablePathElement pe1 = (MatchablePathElement) ShiftrSpec.parseSingleKeyLHS( "tuna-*-marlin-*" ); MatchablePathElement pe2 = (MatchablePathElement) ShiftrSpec.parseSingleKeyLHS( "rating-*" ); - LiteralPathElement lpe = pe1.match( "tuna-marlin", new WalkedPath() ); + MatchedElement lpe = pe1.match( "tuna-marlin", new WalkedPath() ); Assert.assertNull( lpe ); lpe = pe1.match( "tuna-A-marlin-AAA", new WalkedPath() ); @@ -117,7 +118,7 @@ public void calculateOutputTest_refsOnly() { Assert.assertEquals( "A" , lpe.getSubKeyRef( 1 ) ); Assert.assertEquals( "AAA" , lpe.getSubKeyRef( 2 ) ); - LiteralPathElement lpe2 = pe2.match( "rating-BBB", new WalkedPath( null, lpe ) ); + MatchedElement lpe2 = pe2.match( "rating-BBB", new WalkedPath( null, lpe ) ); Assert.assertEquals( "rating-BBB", lpe2.getRawKey() ); Assert.assertEquals( "rating-BBB", lpe2.getSubKeyRef( 0 ) ); Assert.assertEquals( 2, lpe2.getSubKeyCount() ); @@ -151,11 +152,11 @@ public void calculateOutputTest_arrayIndexes() { MatchablePathElement pe2 = (MatchablePathElement) ShiftrSpec.parseSingleKeyLHS( "rating-*" ); // match them against some data to get LiteralPathElements with captured values - LiteralPathElement lpe = pe1.match( "tuna-2-marlin-3", new WalkedPath() ); + MatchedElement lpe = pe1.match( "tuna-2-marlin-3", new WalkedPath() ); Assert.assertEquals( "2" , lpe.getSubKeyRef( 1 ) ); Assert.assertEquals( "3" , lpe.getSubKeyRef( 2 ) ); - LiteralPathElement lpe2 = pe2.match( "rating-BBB", new WalkedPath( null, lpe ) ); + MatchedElement lpe2 = pe2.match( "rating-BBB", new WalkedPath( null, lpe ) ); Assert.assertEquals( 2, lpe2.getSubKeyCount() ); Assert.assertEquals( "BBB" , lpe2.getSubKeyRef( 1 ) ); diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/ShiftrUnitTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/ShiftrUnitTest.java index 6b41dcd0..89c8c53b 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/ShiftrUnitTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/ShiftrUnitTest.java @@ -19,6 +19,7 @@ import com.bazaarvoice.jolt.JsonUtils; import com.bazaarvoice.jolt.Shiftr; import com.bazaarvoice.jolt.common.pathelement.PathElement; +import com.bazaarvoice.jolt.common.pathelement.TransposePathElement; import com.bazaarvoice.jolt.exception.SpecException; import com.bazaarvoice.jolt.shiftr.spec.ShiftrSpec; import com.google.common.base.Joiner; @@ -164,6 +165,8 @@ public Object[][] validRHS() throws IOException { { "@a", "@(0,a)" }, { "@abc", "@(0,abc)" }, { "@a.b.c", "@(0,a).b.c" }, + { "@(a.b\\.c)", "@(0,a.b\\.c)" }, + { "@a.b.c", "@(0,a).b.c" }, { "@a.b.@c", "@(0,a).b.@(0,c)" }, { "@(a[2].&).b.@c", "@(0,a.[2].&(0,0)).b.@(0,c)" }, { "a[&2].@b[1].c", "a.[&(2,0)].@(0,b).[1].c" } @@ -178,6 +181,16 @@ public void validRHSTests( String dotNotation, String expected ) { Assert.assertEquals( actualCanonicalForm, expected, "TestCase: " + dotNotation ); } + @Test + public void testTransposePathParsing() { + + List paths = ShiftrSpec.parseDotNotationRHS( "test.@(2,foo\\.bar)" ); + + Assert.assertEquals( paths.size(), 2 ); + TransposePathElement actualApe = (TransposePathElement) paths.get( 1 ); + + Assert.assertEquals( actualApe.getCanonicalForm(), "@(2,foo\\.bar)" ); + } @DataProvider public Object[][] badRHS() throws IOException { diff --git a/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/RHSParsingTest.java b/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/SpecParsingTest.java similarity index 53% rename from jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/RHSParsingTest.java rename to jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/SpecParsingTest.java index 9dc36bd2..8a9101a2 100644 --- a/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/RHSParsingTest.java +++ b/jolt-core/src/test/java/com/bazaarvoice/jolt/shiftr/spec/SpecParsingTest.java @@ -24,10 +24,10 @@ import java.util.Arrays; import java.util.List; -public class RHSParsingTest { +public class SpecParsingTest { @DataProvider - public Object[][] RHSParsingTests() throws IOException { + public Object[][] RHSParsingTestsRemoveEscapes() throws IOException { return new Object[][] { { "simple, no escape", @@ -55,9 +55,9 @@ public Object[][] RHSParsingTests() throws IOException { Arrays.asList( "a.b", "c" ) }, { - "double escape in front of a period, does not escape the period", - "a\\\\.b.c", - Arrays.asList( "a\\", "b", "c" ) + "escaping rhs", + "data.\\\\$rating-&1", + Arrays.asList( "data", "\\$rating-&1" ) }, { "@Class example", @@ -67,11 +67,48 @@ public Object[][] RHSParsingTests() throws IOException { }; } - @Test(dataProvider = "RHSParsingTests" ) - public void testRHSParsing( String testName, String unSweetendDotNotation, List expected ) { + @Test(dataProvider = "RHSParsingTestsRemoveEscapes") + public void testRHSParsingRemoveEscapes( String testName, String unSweetendDotNotation, List expected ) { - List actual = ShiftrSpec.parseDotNotation( Lists.newArrayList(), ShiftrSpec.stringIterator(unSweetendDotNotation), unSweetendDotNotation ); + List actual = ShiftrSpec.parseDotNotation( Lists.newArrayList(), ShiftrSpec.stringIterator(unSweetendDotNotation), + unSweetendDotNotation ); Assert.assertEquals( actual, expected, "Failed test name " + testName ); } + + @DataProvider + public Object[][] removeEscapeCharsTests() throws IOException { + + return new Object[][] { + { "starts with escape", "\\@pants", "@pants" }, + { "escape in the middle", "rating-\\&pants", "rating-&pants" }, + { "escape the escape char", "rating\\\\pants", "rating\\pants" }, + }; + } + + @Test(dataProvider = "removeEscapeCharsTests" ) + public void testRemoveEscapeChars( String testName, String input, String expected ) { + + String actual = ShiftrSpec.removeEscapeChars( input ); + Assert.assertEquals( actual, expected, "Failed test name " + testName ); + } + + + @DataProvider + public Object[][] removeEscapedValuesTest() throws IOException { + + return new Object[][] { + { "starts with escape", "\\@pants", "pants" }, + { "escape in the middle", "rating-\\&pants", "rating-pants" }, + { "escape the escape char", "rating\\\\pants", "ratingpants" }, + { "escape the array", "\\[\\]pants", "pants" }, + }; + } + + @Test(dataProvider = "removeEscapedValuesTest" ) + public void testEscapeParsing( String testName, String input, String expected ) { + + String actual = ShiftrSpec.removeEscapedValues( input ); + Assert.assertEquals( actual, expected, "Failed test name " + testName ); + } } diff --git a/jolt-core/src/test/resources/json/shiftr/escapeAllTheThings.json b/jolt-core/src/test/resources/json/shiftr/escapeAllTheThings.json new file mode 100644 index 00000000..7c734499 --- /dev/null +++ b/jolt-core/src/test/resources/json/shiftr/escapeAllTheThings.json @@ -0,0 +1,36 @@ +{ + "input": { + "@context": "atSymbol", + "$name": "Mojito", + "&ingredient": "mint", + "[yield": "open array", + "[]yield": "full array", + "]yield": "back array", + "*" : "star", + "#" : "hash", + "(" : "left paren" + }, + + "spec": { + // TEST escaping all the things when they are the first char(s) of the spec LHS and RHS + "\\@context": "\\@A", + "\\$name": "\\$B", + "\\&ingredient": "\\&C", + "\\[yield": "\\[D", + "\\[\\]yield": "\\[\\]E", + "\\]yield": "\\]F", + "\\*" : "\\*G", + "\\#" : "\\#H" + }, + + "expected": { + "@A" : "atSymbol", + "$B" : "Mojito", + "&C" : "mint", + "[D": "open array", + "[]E" : "full array", + "]F" : "back array", + "*G" : "star", + "#H" : "hash" + } +} diff --git a/jolt-core/src/test/resources/json/shiftr/escapeAllTheThings2.json b/jolt-core/src/test/resources/json/shiftr/escapeAllTheThings2.json new file mode 100644 index 00000000..29e88475 --- /dev/null +++ b/jolt-core/src/test/resources/json/shiftr/escapeAllTheThings2.json @@ -0,0 +1,36 @@ +{ + "input": { + "aaa@context": "atSymbol", + "bbb$name": "Mojito", + "ccc&ingredient": "mint", + "ddd[yield": "open array", + "eee[]yield": "full array", + "fff]yield": "back array", + "ggg*" : "star", + "hhh#" : "hash", + "yyy(" : "left paren" + }, + + "spec": { + // TEST escaping all the things when they are in the middle of the LHS and RHS keys + "aaa\\@context": "aaa\\@A", + "bbb\\$name": "bbb\\$B", + "ccc\\&ingredient": "ccc\\&C", + "ddd\\[yield": "ddd\\[D", + "eee\\[\\]yield": "eee\\[\\]E", + "fff\\]yield": "fff\\]F", + "ggg\\*" : "ggg\\*G", + "hhh\\#" : "hhh\\#H" + }, + + "expected": { + "aaa@A" : "atSymbol", + "bbb$B" : "Mojito", + "ccc&C" : "mint", + "ddd[D": "open array", + "eee[]E" : "full array", + "fff]F" : "back array", + "ggg*G" : "star", + "hhh#H" : "hash" + } +} diff --git a/jolt-core/src/test/resources/json/shiftr/json-ld-escaping.json b/jolt-core/src/test/resources/json/shiftr/json-ld-escaping.json new file mode 100644 index 00000000..7524b8f6 --- /dev/null +++ b/jolt-core/src/test/resources/json/shiftr/json-ld-escaping.json @@ -0,0 +1,113 @@ +{ + "input": { + // pulled from http://json-ld.org/playground/ example recipe. Also, Mojitos are good. + "@context": { + "name": "http://rdf.data-vocabulary.org/#name", + "ingredient": "http://rdf.data-vocabulary.org/#ingredients", + "yield": "http://rdf.data-vocabulary.org/#yield", + "instructions": "http://rdf.data-vocabulary.org/#instructions", + "step": { + "@id": "http://rdf.data-vocabulary.org/#step", + "@type": "xsd:integer" + }, + "description": "http://rdf.data-vocabulary.org/#description", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "name": "Mojito", + "ingredient": [ + "12 fresh mint leaves", + "1/2 lime, juiced with pulp", + "1 tablespoons white sugar", + "1 cup ice cubes", + "2 fluid ounces white rum", + "1/2 cup club soda" + ], + "yield": "1 cocktail", + "instructions": [ + { + "step": 1, + "description": "Crush lime juice, mint and sugar together in glass." + }, + { + "step": 2, + "description": "Fill glass to top with ice cubes." + }, + { + "step": 3, + "description": "Pour white rum over ice." + }, + { + "step": 4, + "description": "Fill the rest of glass with club soda, stir." + }, + { + "step": 5, + "description": "Garnish with a lime wedge." + } + ] + }, + + "spec": { + // In this test / example, we want to change some of the LHS keys in this Json document. + // To accomplish this we escape the leading '@' chars both as spec matches (left hand side) + // and as output paths (right hand side) + "\\@context": { + "name": "&1.Name", + "ingredient": "&1.Inputs", + "yield": "\\@context.Makes", + // pass the rest thru + "*" : "&1.&" + }, + "name": "Name", + "ingredient": "Inputs", + "yield": "Makes", + "*" : "&" + }, + + "expected": { + "@context": { + "Name": "http://rdf.data-vocabulary.org/#name", + "Inputs": "http://rdf.data-vocabulary.org/#ingredients", + "Makes": "http://rdf.data-vocabulary.org/#yield", + "instructions": "http://rdf.data-vocabulary.org/#instructions", + "step": { + "@id": "http://rdf.data-vocabulary.org/#step", + "@type": "xsd:integer" + }, + "description": "http://rdf.data-vocabulary.org/#description", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "Name": "Mojito", + "Inputs": [ + "12 fresh mint leaves", + "1/2 lime, juiced with pulp", + "1 tablespoons white sugar", + "1 cup ice cubes", + "2 fluid ounces white rum", + "1/2 cup club soda" + ], + "Makes": "1 cocktail", + "instructions": [ + { + "step": 1, + "description": "Crush lime juice, mint and sugar together in glass." + }, + { + "step": 2, + "description": "Fill glass to top with ice cubes." + }, + { + "step": 3, + "description": "Pour white rum over ice." + }, + { + "step": 4, + "description": "Fill the rest of glass with club soda, stir." + }, + { + "step": 5, + "description": "Garnish with a lime wedge." + } + ] + } +} diff --git a/jolt-core/src/test/resources/json/shiftr/simpleLHSEscape.json b/jolt-core/src/test/resources/json/shiftr/simpleLHSEscape.json new file mode 100644 index 00000000..24698919 --- /dev/null +++ b/jolt-core/src/test/resources/json/shiftr/simpleLHSEscape.json @@ -0,0 +1,38 @@ +{ + "input": { + "@rating": { + "$primary": { + "value": 4, + "max": 5 + }, + "&quality": { + "value": 3, + "max": 7 + }, + "#sharpness": { + "value": 5, + "max": 7 + } + } + }, + + "spec": { + "\\@rating": { + "\\$primary": { + // escape the '.' so that an output key can have a dot in it + "value": "data.\\$rating" + }, + "*": { + "value": "data.\\$rating-&1" + } + } + }, + + "expected": { + "data" : { + "$rating" : 4, + "$rating-&quality" : 3, + "$rating-#sharpness" : 5 + } + } +}