VOL-3182 Support dotted syntax for nested filter

Change-Id: Ib4a244c85dd36300bc6f7f7507867a4f1f0d0f38
diff --git a/VERSION b/VERSION
index b08cf5b..e25d8d9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.1.5-dev
+1.1.5
diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go
index f566d94..e704790 100644
--- a/pkg/filter/filter.go
+++ b/pkg/filter/filter.go
@@ -17,6 +17,7 @@
 
 import (
 	"fmt"
+	"log"
 	"reflect"
 	"regexp"
 	"strings"
@@ -138,37 +139,65 @@
 	return true
 }
 
-func (f Filter) Evaluate(item interface{}) bool {
-	val := reflect.ValueOf(item)
-
+func (f Filter) EvaluateTerm(k string, v FilterTerm, val reflect.Value, recurse bool) bool {
 	// If we have been given a pointer, then deference it
 	if val.Kind() == reflect.Ptr {
 		val = reflect.Indirect(val)
 	}
 
-	for k, v := range f {
-		field := val.FieldByName(k)
+	// If the user gave us an explicitly named dotted field, then split it
+	if strings.Contains(k, ".") {
+		parts := strings.SplitN(k, ".", 2)
+		field := val.FieldByName(parts[0])
 		if !field.IsValid() {
+			log.Printf("Failed to find dotted field %s while filtering\n", parts[0])
 			return false
 		}
+		return f.EvaluateTerm(parts[1], v, field, false)
+	}
 
-		if (field.Kind() == reflect.Slice) || (field.Kind() == reflect.Array) {
-			// For an array, check to see if any item matches
-			someMatch := false
-			for i := 0; i < field.Len(); i++ {
-				arrayElem := field.Index(i)
-				if testField(v, arrayElem) {
-					someMatch = true
-				}
-			}
-			if !someMatch {
-				return false
-			}
-		} else {
-			if !testField(v, field) {
-				return false
+	field := val.FieldByName(k)
+	if !field.IsValid() {
+		log.Printf("Failed to find field %s while filtering\n", k)
+		return false
+	}
+
+	if (field.Kind() == reflect.Slice) || (field.Kind() == reflect.Array) {
+		// For an array, check to see if any item matches
+		someMatch := false
+		for i := 0; i < field.Len(); i++ {
+			arrayElem := field.Index(i)
+			if testField(v, arrayElem) {
+				someMatch = true
 			}
 		}
+		if !someMatch {
+			//if recurse && val.Kind() == reflect.Struct {
+			//    TODO: implement automatic recursion when the user did not
+			//          use a dotted notation. Go through the list of fields
+			//          in the struct, recursively check each one.
+			//}
+			return false
+		}
+	} else {
+		if !testField(v, field) {
+			return false
+		}
 	}
+
+	return true
+}
+
+func (f Filter) Evaluate(item interface{}) bool {
+	val := reflect.ValueOf(item)
+
+	for k, v := range f {
+		matches := f.EvaluateTerm(k, v, val, true)
+		if !matches {
+			// If any of the filter fail, the overall match fails
+			return false
+		}
+	}
+
 	return true
 }