lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dpg...@apache.org
Subject [4/6] lucene-solr:branch_7x: SOLR-11283: Refactors all Stream Evaluators in solrj.io.eval to simplify them
Date Sat, 26 Aug 2017 02:14:37 GMT
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
index 4ffaee5..f50a521 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FindDelayEvaluator.java
@@ -14,83 +14,63 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import org.apache.commons.math3.util.MathArrays;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class FindDelayEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public FindDelayEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class FindDelayEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public FindDelayEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-    StreamEvaluator colEval2 = subEvaluators.get(1);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> numbers2 = (List<Number>)colEval2.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-    double[] column2 = new double[numbers2.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    //Reverse the second column.
-    //The convolve function will reverse it back.
-    //This allows correlation to be represented using the convolution math.
-    int rIndex=0;
-    for(int i=numbers2.size()-1; i>=0; i--) {
-      column2[rIndex++] = numbers2.get(i).doubleValue();
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
     }
 
-    double[] convolution = MathArrays.convolve(column1, column2);
-    double max = -Double.MAX_VALUE;
-    double maxIndex = -1;
-
-    for(int i=0; i< convolution.length; i++) {
-      double abs = Math.abs(convolution[i]);
-      if(abs > max) {
-        max = abs;
-        maxIndex = i;
+    // Get first and second lists as arrays, where second is in reverse order
+    double[] firstArray = ((List)first).stream().mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray();
+    double[] secondArray = StreamSupport.stream(Spliterators.spliteratorUnknownSize(
+        ((LinkedList)((List)second).stream().collect(Collectors.toCollection(LinkedList::new))).descendingIterator(),
+        Spliterator.ORDERED), false).mapToDouble(value -> ((BigDecimal)value).doubleValue()).toArray();
+    
+    double[] convolution = MathArrays.convolve(firstArray, secondArray);
+    double maxValue = -Double.MAX_VALUE;
+    double indexOfMaxValue = -1;
+
+    for(int idx = 0; idx < convolution.length; ++idx) {
+      double abs = Math.abs(convolution[idx]);
+      if(abs > maxValue) {
+        maxValue = abs;
+        indexOfMaxValue = idx;
       }
     }
 
-    return (maxIndex+1)-column2.length;
-  }
+    return (indexOfMaxValue + 1) - secondArray.length;
 
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
index 96a221e..c6d8b84 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/FloorEvaluator.java
@@ -18,41 +18,35 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class FloorEvaluator extends NumberEvaluator {
+public class FloorEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
-
+  
   public FloorEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    List<BigDecimal> results = evaluateAll(tuple);
-
-    // we're still doing these checks because if we ever add an array-flatten evaluator,
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-
-    return normalizeType(results.get(0).setScale(0, RoundingMode.FLOOR));
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.floor(((BigDecimal)value).doubleValue());
+    }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
index 80cde67..d8e4a0c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEqualToEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class GreaterThanEqualToEvaluator extends BooleanEvaluator {
+public class GreaterThanEqualToEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public GreaterThanEqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class GreaterThanEqualToEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
index 65f844a..0d7aa59 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/GreaterThanEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class GreaterThanEvaluator extends BooleanEvaluator {
+public class GreaterThanEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public GreaterThanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class GreaterThanEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
index beabe3a..f58f319 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HistogramEvaluator.java
@@ -18,6 +18,7 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -25,77 +26,62 @@ import java.util.Map;
 
 import org.apache.commons.math3.random.EmpiricalDistribution;
 import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HistogramEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public HistogramEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class HistogramEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public HistogramEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
-  public List<Tuple> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
+  @Override
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
     }
-
-    int bins = 10;
-    if(subEvaluators.size() == 2) {
-      StreamEvaluator binsEval = subEvaluators.get(1);
-      Number binsNum = (Number) binsEval.evaluate(tuple);
-      bins = binsNum.intValue();
+    
+    List<?> sourceValues;
+    Integer bins = 10;
+    
+    if(values.length >= 1){
+      sourceValues = values[0] instanceof List<?> ? (List<?>)values[0] : Arrays.asList(values[0]); 
+            
+      if(values.length >= 2){
+        if(values[1] instanceof Number){
+          bins = ((Number)values[1]).intValue();
+        }
+        else{
+          throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - if second parameter is provided then it must be a valid number but found %s instead",toExpression(constructingFactory), values[1].getClass().getSimpleName()));
+        }        
+      }      
     }
-
-    EmpiricalDistribution empiricalDistribution = new EmpiricalDistribution(bins);
-    empiricalDistribution.load(column1);
-
-    List<Tuple> binList = new ArrayList();
-
-    List<SummaryStatistics> summaries = empiricalDistribution.getBinStats();
-    for(SummaryStatistics statisticalSummary : summaries) {
-      Map map = new HashMap();
-      map.put("max", statisticalSummary.getMax());
-      map.put("mean", statisticalSummary.getMean());
-      map.put("min", statisticalSummary.getMin());
-      map.put("stdev", statisticalSummary.getStandardDeviation());
-      map.put("sum", statisticalSummary.getSum());
-      map.put("N", statisticalSummary.getN());
-      map.put("var", statisticalSummary.getVariance());
-      binList.add(new Tuple(map));
+    else{
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",toExpression(constructingFactory),containedEvaluators.size()));
     }
 
-    return binList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    EmpiricalDistribution distribution = new EmpiricalDistribution(bins);
+    distribution.load(((List<?>)sourceValues).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray());;
+
+    List<Map<String,Number>> histogramBins = new ArrayList<>();
+    for(SummaryStatistics binSummary : distribution.getBinStats()) {
+      Map<String,Number> map = new HashMap<>();
+      map.put("max", binSummary.getMax());
+      map.put("mean", binSummary.getMean());
+      map.put("min", binSummary.getMin());
+      map.put("stdev", binSummary.getStandardDeviation());
+      map.put("sum", binSummary.getSum());
+      map.put("N", binSummary.getN());
+      map.put("var", binSummary.getVariance());
+      histogramBins.add(map);
+    }
+    
+    return histogramBins;
   }
-}
\ No newline at end of file
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
index 28f5e37..a9e7c0a 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicCosineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HyperbolicCosineEvaluator extends NumberEvaluator {
+public class HyperbolicCosineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public HyperbolicCosineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.cosh(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.cosh(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
index ed1be4f..c23f64e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicSineEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HyperbolicSineEvaluator extends NumberEvaluator {
+public class HyperbolicSineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public HyperbolicSineEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.sinh(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.sinh(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
index 4f6b5e1..4f8136e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/HyperbolicTangentEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class HyperbolicTangentEvaluator extends NumberEvaluator {
+public class HyperbolicTangentEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public HyperbolicTangentEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-    
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-    
-    return Math.tanh(results.get(0).doubleValue());
-  }  
-
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.tanh(((BigDecimal)value).doubleValue());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
index d3e23ab..9453fb0 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/IfThenElseEvaluator.java
@@ -17,43 +17,32 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class IfThenElseEvaluator extends ConditionalEvaluator {
+public class IfThenElseEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public IfThenElseEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(3 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression,subEvaluators.size()));
+    if(3 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression, containedEvaluators.size()));
     }
-    
-    if(!(subEvaluators.get(0) instanceof BooleanEvaluator)){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a boolean as the first parameter but found %s",expression,subEvaluators.get(0).getClass().getSimpleName()));
-    }
-
   }
 
   @Override
-  public Object evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(3 != results.size()){
-      String message = String.format(Locale.ROOT,"%s(...) only works with 3 values but %s were provided", constructingFactory.getFunctionName(getClass()), results.size());
-      throw new IOException(message);
+  public Object doWork(Object... values) throws IOException {
+    if(3 != values.length){
+      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with 3 values but %d were provided", constructingFactory.getFunctionName(getClass()), values.length));
     }
     
-    if(!(results.get(0) instanceof Boolean)){
-      throw new IOException(String.format(Locale.ROOT,"$s(...) only works with a boolean as the first parameter but found %s",results.get(0).getClass().getSimpleName()));
+    if(!(values[0] instanceof Boolean)){
+      throw new IOException(String.format(Locale.ROOT,"$s(...) only works with a boolean as the first parameter but found %s",values[0].getClass().getSimpleName()));
     }
-  
-    return (boolean)results.get(0) ? results.get(1) : results.get(2);
+    
+    return (boolean)values[0] ? values[1] : values[2];
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
index aa7c537..2f7c236 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/KolmogorovSmirnovEvaluator.java
@@ -17,86 +17,57 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.commons.math3.distribution.RealDistribution;
 import org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest;
 import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class KolmogorovSmirnovEvaluator extends ComplexEvaluator implements Expressible {
+public class KolmogorovSmirnovEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public KolmogorovSmirnovEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(subEvaluators.size() != 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
-    }
   }
-
-  public Tuple evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator se1 = subEvaluators.get(0);
-    StreamEvaluator se2 = subEvaluators.get(1);
-
-    KolmogorovSmirnovTest ks = new KolmogorovSmirnovTest();
-    List<Number> sample = (List<Number>)se2.evaluate(tuple);
-    double[] data = new double[sample.size()];
-
-    for(int i=0; i<data.length; i++) {
-      data[i] = sample.get(i).doubleValue();
+  
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first || (first instanceof List<?> && 0 != ((List<?>)first).stream().filter(item -> null == item).count())){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    Object o = se1.evaluate(tuple);
-
-    if(o instanceof RealDistribution) {
-      RealDistribution realDistribution = (RealDistribution)o;
-      double d = ks.kolmogorovSmirnovStatistic(realDistribution, data);
-      double p = ks.kolmogorovSmirnovTest(realDistribution, data);
-
-
-      Map m = new HashMap();
-      m.put("p-value", p);
-      m.put("d-statistic", d);
+    if(null == second || (second instanceof List<?> && 0 != ((List<?>)second).stream().filter(item -> null == item).count())){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
+    if(!(second instanceof List<?>) || 0 != ((List<?>)second).stream().filter(item -> !(item instanceof Number)).count()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a List of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    KolmogorovSmirnovTest ks = new KolmogorovSmirnovTest();
+    double[] data = ((List<?>)second).stream().mapToDouble(item -> ((Number)item).doubleValue()).toArray();
+    
+    if(first instanceof RealDistribution){
+      RealDistribution realDistribution = (RealDistribution)first;
+
+      Map<String,Double> m = new HashMap<>();
+      m.put("p-value", ks.kolmogorovSmirnovTest(realDistribution, data));
+      m.put("d-statistic", ks.kolmogorovSmirnovStatistic(realDistribution, data));
       return new Tuple(m);
-    } else {
-      List<Number> sample2 = (List<Number>)o;
-      double[] data2 = new double[sample2.size()];
-      for(int i=0; i<data2.length; i++) {
-        data2[i] = sample2.get(i).doubleValue();
-      }
-
-      double d = ks.kolmogorovSmirnovStatistic(data, data2);
-      //double p = ks.(data, data2);
-      Map m = new HashMap();
-      //m.put("p-value", p);
-      m.put("d-statistic", d);
+    }
+    else if(first instanceof List<?> && 0 == ((List<?>)first).stream().filter(item -> !(item instanceof Number)).count()){
+      double[] data2 = ((List<?>)first).stream().mapToDouble(item -> ((Number)item).doubleValue()).toArray();
+      
+      Map<String,Double> m = new HashMap<>();
+      m.put("d-statistic", ks.kolmogorovSmirnovTest(data, data2));
       return new Tuple(m);
     }
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    else{
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RealDistribution or list of numbers",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
index b070fe8..f703db6 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LengthEvaluator.java
@@ -20,54 +20,30 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class LengthEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public LengthEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class LengthEvaluator extends RecursiveObjectEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public LengthEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    Object result = subEvaluators.get(0).evaluate(tuple);
-    
-    if(null == result){
+  
+  @Override
+  public Object doWork(Object value) throws IOException{
+    if(null == value){
       throw new IOException(String.format(Locale.ROOT, "Unable to find %s(...) because the value is null", constructingFactory.getFunctionName(getClass())));
     }
-    
-    if(result instanceof Collection<?>){
-      // Cause other evaluators expect Long instead of Integer
-      return new Long(((Collection<?>)result).size());
+    else if(value instanceof Collection<?>){
+      return ((Collection<?>)value).size();
+    }
+    else{
+      throw new IOException(String.format(Locale.ROOT, "Unable to find %s(...) because the value is not a collection, instead a %s was found", constructingFactory.getFunctionName(getClass()), value.getClass().getSimpleName()));
     }
-    
-    throw new IOException(String.format(Locale.ROOT, "Unable to find %s(...) because the value is not a collection, instead a %s was found", constructingFactory.getFunctionName(getClass()), result.getClass().getSimpleName()));
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
index 1590b8f..b5b216b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEqualToEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class LessThanEqualToEvaluator extends BooleanEvaluator {
+public class LessThanEqualToEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public LessThanEqualToEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class LessThanEqualToEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
index af389ae..51cbb1d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/LessThanEvaluator.java
@@ -18,59 +18,23 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class LessThanEvaluator extends BooleanEvaluator {
+public class LessThanEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public LessThanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(idx - 1), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -93,4 +57,5 @@ public class LessThanEvaluator extends BooleanEvaluator {
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java
new file mode 100644
index 0000000..311885b
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ManyValueWorker.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+
+public interface ManyValueWorker extends ValueWorker {
+
+  Object doWork(Object ... values) throws IOException;
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
index a965440..9e76204 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ModuloEvaluator.java
@@ -19,57 +19,57 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.MathContext;
-import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class ModuloEvaluator extends NumberEvaluator {
+public class ModuloEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public ModuloEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
+
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression, containedEvaluators.size()));
     }
   }
 
+  // override to give a slightly better error message than the default
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
-    
-    // we're still doing these checks because if we ever add an array-flatten evaluator, 
-    // two found in the constructor could become != 2
-    if(2 != results.size()){
+  public Object doWork(Object ... values) throws IOException{
+    if(2 != values.length){
       String message = null;
-      if(1 == results.size()){
+      if(1 == values.length){
         message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but 1 was provided", constructingFactory.getFunctionName(getClass())); 
       }
       else{
-        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
+        message = String.format(Locale.ROOT,"%s(...) only works with a 2 values (numerator,denominator) but %d were provided", constructingFactory.getFunctionName(getClass()), values.length);
       }
       throw new IOException(message);
     }
     
-    BigDecimal numerator = results.get(0);
-    BigDecimal denominator = results.get(1);
-    
-    if(null == numerator){
+    return doWork(values[0], values[1]);
+  }
+
+
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null numerator", constructingFactory.getFunctionName(getClass())));
     }
     
-    if(null == denominator){
+    if(null == second){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a null denominator", constructingFactory.getFunctionName(getClass())));
     }
     
+    BigDecimal numerator = (BigDecimal)first;
+    BigDecimal denominator = (BigDecimal)second;
+    
     if(0 == denominator.compareTo(BigDecimal.ZERO)){
       throw new IOException(String.format(Locale.ROOT,"Unable to %s(...) with a 0 denominator", constructingFactory.getFunctionName(getClass())));
     }
     
-    return normalizeType(numerator.remainder(denominator, MathContext.DECIMAL64));
+    return numerator.remainder(denominator, MathContext.DECIMAL64);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
index bb4909b..6e71bf1 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MovingAverageEvaluator.java
@@ -22,63 +22,45 @@ import java.util.List;
 import java.util.Locale;
 
 import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class MovingAverageEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public MovingAverageEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class MovingAverageEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public MovingAverageEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval = subEvaluators.get(0);
-    StreamEvaluator windowEval = subEvaluators.get(1);
-
-    int window = ((Number)windowEval.evaluate(tuple)).intValue();
-    List<Number> numbers = (List<Number>)colEval.evaluate(tuple);
-
-    if(window > numbers.size()) {
-      throw new IOException("The window size cannot be larger then the array");
+  @Override
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
     }
-
-    List<Number> moving = new ArrayList();
-
-    DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics(window);
-    for(int i=0; i<numbers.size(); i++) {
-      descriptiveStatistics.addValue(numbers.get(i).doubleValue());
-      if(descriptiveStatistics.getN() >= window) {
-        moving.add(descriptiveStatistics.getMean());
-      }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
     }
-
+    if(!(first instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a List",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a Number",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    
+    List<?> values = (List<?>)first;
+    int window = ((Number)second).intValue();
+    
+    List<Number> moving = new ArrayList<>();
+    DescriptiveStatistics slider = new DescriptiveStatistics(window);
+    for(Object value : values){
+      slider.addValue(((Number)value).doubleValue());
+      
+      if(slider.getN() >= window){
+        moving.add(slider.getMean());
+      }      
+    }
+    
     return moving;
   }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
-
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
-  }
-}
\ No newline at end of file
+  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
index 636624c..11b608c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/MultiplyEvaluator.java
@@ -18,42 +18,59 @@ package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.math.MathContext;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class MultiplyEvaluator extends NumberEvaluator {
+public class MultiplyEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public MultiplyEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
+  public Object doWork(Object... values) throws IOException {
+    if(Arrays.stream(values).anyMatch(item -> null == item)){
+      return null;
+    }
     
-    if(results.stream().anyMatch(item -> null == item)){
+    if(0 == values.length){
       return null;
     }
     
-    BigDecimal result = null;
-    if(results.size() > 0){
-      result = results.get(0);
-      for(int idx = 1; idx < results.size(); ++idx){
-        result = result.multiply(results.get(idx), MathContext.DECIMAL64);
-      }
+    BigDecimal result = BigDecimal.ONE;
+    for(Object value : values){
+      result = multiply(result, value);
     }
     
-    return normalizeType(result);
+    return result;
   }
+  
+  private BigDecimal multiply(BigDecimal left, Object right) throws IOException{
+    if(null == left || null == right){
+      return null;
+    }
+    else if(right instanceof BigDecimal){
+      return left.multiply((BigDecimal)right);
+    }
+    else if(right instanceof Number){
+      return multiply(left, new BigDecimal(right.toString()));
+    }
+    else if(right instanceof List){
+      return multiply(left, doWork(((List<?>)right).toArray()));
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", right.getClass().getName(), right.toString());
+    }
+
+  }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
index 5280600..b59ba38 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NaturalLogEvaluator.java
@@ -20,38 +20,33 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NaturalLogEvaluator extends NumberEvaluator {
+public class NaturalLogEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
-
+  
   public NaturalLogEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
   }
 
   @Override
-  public Number evaluate(Tuple tuple) throws IOException {
-
-    List<BigDecimal> results = evaluateAll(tuple);
-
-    // we're still doing these checks because if we ever add an array-flatten evaluator,
-    // one found in the constructor could become != 1
-    if(1 != results.size()){
-      throw new IOException(String.format(Locale.ROOT,"%s(...) only works with a 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size()));
-    }
-
-    if(null == results.get(0)){
+  public Object doWork(Object value){
+    if(null == value){
       return null;
     }
-
-    return Math.log(results.get(0).doubleValue());
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // we know it's a BigDecimal
+      return Math.log(((BigDecimal)value).doubleValue());
+    }
   }
-
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
index 698402c..f6a445b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalDistributionEvaluator.java
@@ -19,51 +19,30 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.util.Locale;
 
-
 import org.apache.commons.math3.distribution.NormalDistribution;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NormalDistributionEvaluator extends ComplexEvaluator implements Expressible {
+public class NormalDistributionEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public NormalDistributionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
     super(expression, factory);
-
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one column but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator numEval1 = subEvaluators.get(0);
-    StreamEvaluator numEval2 = subEvaluators.get(1);
-
-    Number mean = (Number)numEval1.evaluate(tuple);
-    Number stdDev = (Number)numEval2.evaluate(tuple);
-
-    return new NormalDistribution(mean.doubleValue(), stdDev.doubleValue());
   }
 
   @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
-  }
+  public Object doWork(Object first, Object second) throws IOException{
+    if(null == first){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the first value",toExpression(constructingFactory)));
+    }
+    if(null == second){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null found for the second value",toExpression(constructingFactory)));
+    }
 
-  @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+    Number mean = (Number)first;
+    Number standardDeviation = (Number)second;
+    
+    return new NormalDistribution(mean.doubleValue(), standardDeviation.doubleValue());
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
index c85ac20..afe4c5d 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NormalizeEvaluator.java
@@ -17,58 +17,37 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.stat.StatUtils;
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation;
-import org.apache.solr.client.solrj.io.stream.expr.Explanation.ExpressionType;
-import org.apache.solr.client.solrj.io.stream.expr.Expressible;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NormalizeEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public NormalizeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class NormalizeEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public NormalizeEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    double[] column1 = new double[numbers1.size()];
-
-    for(int i=0; i<numbers1.size(); i++) {
-      column1[i] = numbers1.get(i).doubleValue();
-    }
-
-    double[] normalized = StatUtils.normalize(column1);
-
-    List<Number> normalizeList = new ArrayList(normalized.length);
-    for(double d : normalized) {
-      normalizeList.add(d);
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 1 value but found %d",expression,containedEvaluators.size()));
     }
-
-    return normalizeList;
-  }
-
-  @Override
-  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
-    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
-    return expression;
   }
 
   @Override
-  public Explanation toExplanation(StreamFactory factory) throws IOException {
-    return new Explanation(nodeId.toString())
-        .withExpressionType(ExpressionType.EVALUATOR)
-        .withFunctionName(factory.getFunctionName(getClass()))
-        .withImplementingClass(getClass().getName())
-        .withExpression(toExpression(factory).toString());
+  public Object doWork(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List){
+      return Arrays.stream(StatUtils.normalize(((List<?>)value).stream().mapToDouble(innerValue -> ((Number)innerValue).doubleValue()).toArray())).mapToObj(Double::new).collect(Collectors.toList());
+    }
+    else{
+      return doWork(Arrays.asList((BigDecimal)value));
+    }
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
index 90054c5..03a90d2 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NotEvaluator.java
@@ -19,41 +19,44 @@ package org.apache.solr.client.solrj.io.eval;
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class NotEvaluator extends BooleanEvaluator {
+public class NotEvaluator extends RecursiveBooleanEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public NotEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(1 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 1){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least one value but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(1 != results.size()){
-      String message = String.format(Locale.ROOT,"%s(...) only works with 1 value but %d were provided", constructingFactory.getFunctionName(getClass()), results.size());
-      throw new IOException(message);
+  
+  protected Checker constructChecker(Object value) throws IOException{
+    return null;
+  }
+  
+  public Object doWork(Object ... values) throws IOException{
+    if(1 != values.length){
+      throw new IOException(String.format(Locale.ROOT, "Expecting 1 value but found %d", values.length));
     }
+    
+    return doWork(values[0]);
+  }
 
-    Object result = results.get(0);
-    if(null == result){
-      throw new IOException(String.format(Locale.ROOT,"Unable to evaluate %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
+  public Object doWork(Object value) {
+    if(null == value){
+      return null;
     }
-    if(!(result instanceof Boolean)){
-      throw new IOException(String.format(Locale.ROOT,"Unable to evaluate %s(...) of a non-boolean value [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue));
+    }
+    else{
+      // we know it's a boolean
+      return !((Boolean)value);
     }
-    
-    return !((Boolean)result);
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
deleted file mode 100644
index 5434c01..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/NumberEvaluator.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.solr.client.solrj.io.eval;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import org.apache.solr.client.solrj.io.Tuple;
-import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
-import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
-
-public abstract class NumberEvaluator extends ComplexEvaluator {
-  protected static final long serialVersionUID = 1L;
-  
-  public NumberEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
-    super(expression, factory);
-  }
-  
-  // restrict result to a Number
-  public abstract Number evaluate(Tuple tuple) throws IOException;
-  
-  public List<BigDecimal> evaluateAll(final Tuple tuple) throws IOException {
-    // evaluate each and confirm they are all either null or numeric
-    List<BigDecimal> results = new ArrayList<BigDecimal>();
-    for(StreamEvaluator subEvaluator : subEvaluators){
-      Object result = subEvaluator.evaluate(tuple);
-      
-      if(null == result){
-        results.add(null);
-      }
-      else if(result instanceof Number){
-        results.add(new BigDecimal(result.toString()));
-      } else if(result instanceof List) {
-        List l = (List) result;
-        for(Object o : l) {
-          if(o instanceof Number) {
-            results.add(new BigDecimal(o.toString()));
-          } else {
-            String message = String.format(Locale.ROOT,"Failed to evaluate to a numeric value - evaluator '%s' resulted in type '%s' and value '%s'",
-                                           subEvaluator.toExpression(constructingFactory),
-                                           o.getClass().getName(),
-                                           o.toString());
-            throw new IOException(message);
-          }
-        }
-      }
-      else{
-        String message = String.format(Locale.ROOT,"Failed to evaluate to a numeric value - evaluator '%s' resulted in type '%s' and value '%s'", 
-                                        subEvaluator.toExpression(constructingFactory),
-                                        result.getClass().getName(),
-                                        result.toString());
-        throw new IOException(message);
-      }
-    }
-    
-    return results;
-  }
-  
-  public Number normalizeType(BigDecimal value){
-    if(null == value){
-      return null;
-    }
-    
-    if(value.signum() == 0 || value.scale() <= 0 || value.stripTrailingZeros().scale() <= 0){
-      return value.longValue();
-    }
-    
-    return value.doubleValue();
-
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java
new file mode 100644
index 0000000..5f6557fc
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OneValueWorker.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.solr.client.solrj.io.eval;
+
+import java.io.IOException;
+import java.util.Locale;
+
+public interface OneValueWorker extends ValueWorker {
+
+  Object doWork(Object value) throws IOException;
+  
+  default Object doWork(Object ... values) throws IOException{
+    if(1 != values.length){
+      throw new IOException(String.format(Locale.ROOT, "Expecting 1 value but found %d", values.length));
+    }
+    
+    return doWork(values[0]);
+  }
+    
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
index 0c1886c..4e38537 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/OrEvaluator.java
@@ -17,59 +17,23 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Collectors;
 
-import org.apache.solr.client.solrj.io.Tuple;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class OrEvaluator extends BooleanEvaluator {
+public class OrEvaluator extends RecursiveBooleanEvaluator implements ManyValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public OrEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
     
-    if(subEvaluators.size() < 2){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,subEvaluators.size()));
+    if(containedEvaluators.size() < 2){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting at least two values but found %d",expression,containedEvaluators.size()));
     }
   }
-
-  @Override
-  public Boolean evaluate(Tuple tuple) throws IOException {
-    
-    List<Object> results = evaluateAll(tuple);
-    
-    if(results.size() < 2){
-      String message = null;
-      if(1 == results.size()){
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 1 was provided", constructingFactory.getFunctionName(getClass())); 
-      }
-      else{
-        message = String.format(Locale.ROOT,"%s(...) only works with at least 2 values but 0 were provided", constructingFactory.getFunctionName(getClass()));
-      }
-      throw new IOException(message);
-    }
-    
-    Checker checker = constructChecker(results.get(0));
-    if(results.stream().anyMatch(result -> null == result && !checker.isNullAllowed())){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
-    }
-    if(results.stream().anyMatch(result -> !checker.isCorrectType(result))){
-      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), results.stream().map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
-    }
-
-    for(int idx = 1; idx < results.size(); ++idx){
-      if(!checker.test(results.get(0), results.get(idx))){
-        return false;
-      }
-    }
-    
-    return true;
-  }
   
-  private Checker constructChecker(Object fromValue) throws IOException{
+  protected Checker constructChecker(Object fromValue) throws IOException{
     if(null == fromValue){
       throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) because a null value was found", constructingFactory.getFunctionName(getClass())));
     }
@@ -77,11 +41,12 @@ public class OrEvaluator extends BooleanEvaluator {
       return new BooleanChecker(){
         @Override
         public boolean test(Object left, Object right) {
-          return (boolean)left || (boolean)right;
+          return (Boolean)left || (Boolean)right;
         }
       };
     }
     
     throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) for values of type '%s'", constructingFactory.getFunctionName(getClass()), fromValue.getClass().getSimpleName()));
   }
+
 }


Mime
View raw message