lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dpg...@apache.org
Subject [3/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:36 GMT
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
index 6a51360..b545f4b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PercentileEvaluator.java
@@ -14,62 +14,41 @@
  * 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.List;
 import java.util.Locale;
-import java.io.IOException;
 
-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.commons.math3.stat.descriptive.rank.Percentile;
 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;
-import org.apache.commons.math3.stat.descriptive.rank.Percentile;
-
-public class PercentileEvaluator extends ComplexEvaluator implements Expressible {
 
-  private static final long serialVersionUID = 1;
-
-  public PercentileEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class PercentileEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public PercentileEvaluator(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 (array and number) but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public Number evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator colEval = subEvaluators.get(0);
-    List<Number> column = (List<Number>)colEval.evaluate(tuple);
-
-    double[] data = new double[column.size()];
-    for(int i=0; i<data.length; i++) {
-      data[i] = column.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)));
     }
-
+    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()));
+    }
+    
     Percentile percentile = new Percentile();
-    percentile.setData(data);
-    StreamEvaluator numEval = subEvaluators.get(1);
-    Number num  = (Number)numEval.evaluate(tuple);
-    return percentile.evaluate(num.doubleValue());
-  }
-
-  @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());
+    percentile.setData(((List<?>)first).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray());
+    return percentile.evaluate(((Number)second).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/PowerEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
index 415ecc8..12957f7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PowerEvaluator.java
@@ -18,41 +18,32 @@ 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 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 PowerEvaluator extends NumberEvaluator {
+public class PowerEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public PowerEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
     super(expression, factory);
-    
-    if(2 != subEvaluators.size()){
-      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting exactly 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
-  public Number evaluate(Tuple tuple) throws IOException {
-    
-    List<BigDecimal> results = evaluateAll(tuple);
+  public Object doWork(Object first, Object second) throws IOException{
     
-    if(results.stream().anyMatch(item -> null == item)){
+    if(null == first || null == second){
       return null;
     }
     
-    BigDecimal value = results.get(0);
-    BigDecimal exponent = results.get(1);
-    
-    double result = Math.pow(value.doubleValue(), exponent.doubleValue());
-    if(Double.isNaN(result)){
-      return result;
-    }
-    
-    return normalizeType(BigDecimal.valueOf(result));
+    BigDecimal value = (BigDecimal)first;
+    BigDecimal exponent = (BigDecimal)second;
+        
+    return Math.pow(value.doubleValue(), exponent.doubleValue());
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
index ed9034a..5d9368b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/PredictEvaluator.java
@@ -14,67 +14,46 @@
  * 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;
 import java.util.List;
-import java.util.ArrayList;
+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.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 PredictEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public PredictEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class PredictEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public PredictEvaluator(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 (regression result and a number) but found %d",expression,subEvaluators.size()));
-    }
-  }
-
-  public Object evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator r = subEvaluators.get(0);
-    StreamEvaluator d = subEvaluators.get(1);
-
-    RegressionEvaluator.RegressionTuple rt= (RegressionEvaluator.RegressionTuple)r.evaluate(tuple);
-
-    Object o = d.evaluate(tuple);
-    if(o instanceof Number) {
-      Number n = (Number)o;
-      return rt.predict(n.doubleValue());
-    } else {
-      List<Number> list = (List<Number>)o;
-      List<Number> predications = new ArrayList();
-      for(Number n : list) {
-        predications.add(rt.predict(n.doubleValue()));
-      }
-      return predications;
-    }
   }
 
   @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)));
+    }
+    if(!(first instanceof RegressionEvaluator.RegressionTuple)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RegressionTuple",toExpression(constructingFactory), first.getClass().getSimpleName()));
+    }
+    if(!(second instanceof Number) && !(second instanceof List<?>)){
+      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()));
+    }
+    
+    RegressionEvaluator.RegressionTuple regressedTuple = (RegressionEvaluator.RegressionTuple)first;
 
-  @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());
+    if(second instanceof Number){
+      return regressedTuple.predict(((Number)second).doubleValue());
+    }
+    else{
+      return ((List<?>)second).stream().map(value -> regressedTuple.predict(((Number)value).doubleValue())).collect(Collectors.toList());
+    }
   }
-}
\ 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/RankEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
index 88a730d..3b193c7 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RankEvaluator.java
@@ -17,64 +17,38 @@
 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.ranking.NaturalRanking;
-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 RankEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public RankEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class RankEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public RankEvaluator(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 List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval = subEvaluators.get(0);
-
-    List<Number> numbers = (List<Number>)colEval.evaluate(tuple);
-    double[] values = new double[numbers.size()];
-    for(int i=0; i<numbers.size(); i++) {
-      values[i] = numbers.get(i).doubleValue();
+  @Override
+  public Object doWork(Object value){
+    if(null == value){
+      return null;
     }
-
-    NaturalRanking rank = new NaturalRanking();
-    double[] ranked = rank.rank(values);
-    List<Number> rankedList = new ArrayList();
-    for(int i=0; i<numbers.size(); i++) {
-      rankedList.add(ranked[i]);
+    else if(value instanceof List){
+      NaturalRanking rank = new NaturalRanking();      
+      return Arrays.stream(rank.rank(((List<?>)value).stream().mapToDouble(innerValue -> ((Number)innerValue).doubleValue()).toArray())).mapToObj(Double::new).collect(Collectors.toList());
+    }
+    else{
+      return doWork(Arrays.asList((BigDecimal)value));
     }
-
-    return rankedList;
-  }
-
-  @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/RawValueEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
index e83d481..026f8a2 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RawValueEvaluator.java
@@ -28,7 +28,7 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
 import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
 import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
 
-public class RawValueEvaluator extends SimpleEvaluator {
+public class RawValueEvaluator extends SourceEvaluator {
   private static final long serialVersionUID = 1L;
   
   private Object value;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java
new file mode 100644
index 0000000..f555ee3
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveBooleanEvaluator.java
@@ -0,0 +1,112 @@
+/*
+ * 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.Arrays;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveBooleanEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  protected abstract Checker constructChecker(Object value) throws IOException;
+  
+  public RecursiveBooleanEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return null;
+    }
+    else{
+      return value;
+    }
+  }
+  
+  public Object doWork(Object ... values) throws IOException {
+    if(values.length < 2){
+      String message = null;
+      if(1 == values.length){
+        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(values[0]);
+    if(Arrays.stream(values).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(Arrays.stream(values).anyMatch(result -> !checker.isCorrectType(result))){
+      throw new IOException(String.format(Locale.ROOT,"Unable to check %s(...) of differing types [%s]", constructingFactory.getFunctionName(getClass()), Arrays.stream(values).map(item -> item.getClass().getSimpleName()).collect(Collectors.joining(","))));
+    }
+
+    for(int idx = 1; idx < values.length; ++idx){
+      if(!checker.test(values[idx - 1], values[idx])){
+        return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  public interface Checker {
+    default boolean isNullAllowed(){
+      return false;
+    }
+    boolean isCorrectType(Object value);
+    boolean test(Object left, Object right);
+  }
+  
+  public interface NullChecker extends Checker {
+    default boolean isNullAllowed(){
+      return true;
+    }
+    default boolean isCorrectType(Object value){
+      return true;
+    }
+    default boolean test(Object left, Object right){
+      return null == left && null == right;
+    }
+  }
+  
+  public interface BooleanChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Boolean;
+    }
+  }
+  
+  public interface NumberChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof Number;
+    }
+  }
+  
+  public interface StringChecker extends Checker {
+    default boolean isCorrectType(Object value){
+      return value instanceof String;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java
new file mode 100644
index 0000000..8df46b1
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveEvaluator.java
@@ -0,0 +1,240 @@
+/*
+ * 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.io.UncheckedIOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.solr.client.solrj.io.Tuple;
+import org.apache.solr.client.solrj.io.stream.StreamContext;
+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.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParameter;
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionValue;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveEvaluator implements StreamEvaluator, ValueWorker {
+  protected static final long serialVersionUID = 1L;
+  protected StreamContext streamContext;
+  
+  protected UUID nodeId = UUID.randomUUID();
+  
+  protected StreamFactory constructingFactory;
+  
+  protected List<StreamEvaluator> containedEvaluators = new ArrayList<StreamEvaluator>();
+  
+  public RecursiveEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    this(expression, factory, new ArrayList<>());
+  }
+  
+  protected Object normalizeInputType(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return null;
+      }
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof BigDecimal){
+      return (BigDecimal)value;
+    }
+    else if(value instanceof Number){
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof Collection){
+      return ((Collection<?>)value).stream().map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else if(value.getClass().isArray()){
+      Stream<?> stream = Stream.empty();
+      if(value instanceof double[]){
+        stream = Arrays.stream((double[])value).boxed();
+      }
+      else if(value instanceof int[]){
+        stream = Arrays.stream((int[])value).boxed();
+      }
+      else if(value instanceof long[]){
+        stream = Arrays.stream((long[])value).boxed();
+      }
+      else if(value instanceof String[]){
+        stream = Arrays.stream((String[])value);
+      }      
+      return stream.map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // anything else can just be returned as is
+      return value;
+    }
+
+  }
+  
+  protected Object normalizeOutputType(Object value) {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof BigDecimal){
+      BigDecimal bd = (BigDecimal)value;
+      if(bd.signum() == 0 || bd.scale() <= 0 || bd.stripTrailingZeros().scale() <= 0){
+        try{
+          return bd.longValueExact();
+        }
+        catch(ArithmeticException e){
+          // value was too big for a long, so use a double which can handle scientific notation
+        }
+      }
+      
+      return bd.doubleValue();
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return value;
+      }
+      
+      // could be a long so recurse back in as a BigDecimal
+      return normalizeOutputType(new BigDecimal((Double)value));
+    }
+    else if(value instanceof Number){
+      return normalizeOutputType(new BigDecimal(((Number)value).toString()));
+    }
+    else if(value instanceof List){
+      // normalize each value in the list
+      return ((List<?>)value).stream().map(innerValue -> normalizeOutputType(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // anything else can just be returned as is
+      return value;
+    }
+
+  }
+    
+  public RecursiveEvaluator(StreamExpression expression, StreamFactory factory, List<String> ignoredNamedParameters) throws IOException{
+    this.constructingFactory = factory;
+    
+    // We have to do this because order of the parameters matter
+    List<StreamExpressionParameter> parameters = factory.getOperandsOfType(expression, StreamExpressionParameter.class);
+    
+    for(StreamExpressionParameter parameter : parameters){
+      if(parameter instanceof StreamExpression){
+        // possible evaluator
+        StreamExpression streamExpression = (StreamExpression)parameter;
+        if(factory.doesRepresentTypes(streamExpression, RecursiveEvaluator.class)){
+          containedEvaluators.add(factory.constructEvaluator(streamExpression));
+        }
+        else if(factory.doesRepresentTypes(streamExpression, SourceEvaluator.class)){
+          containedEvaluators.add(factory.constructEvaluator(streamExpression));
+        }
+        else{
+          // Will be treated as a field name
+          containedEvaluators.add(new FieldValueEvaluator(streamExpression.toString()));
+        }
+      }
+      else if(parameter instanceof StreamExpressionValue){
+        if(0 != ((StreamExpressionValue)parameter).getValue().length()){
+          // special case - if evaluates to a number, boolean, or null then we'll treat it 
+          // as a RawValueEvaluator
+          Object value = factory.constructPrimitiveObject(((StreamExpressionValue)parameter).getValue());
+          if(null == value || value instanceof Boolean || value instanceof Number){
+            containedEvaluators.add(new RawValueEvaluator(value));
+          }
+          else if(value instanceof String){
+            containedEvaluators.add(new FieldValueEvaluator((String)value));
+          }
+        }
+      }
+    }
+    
+    Set<String> namedParameters = factory.getNamedOperands(expression).stream().map(param -> param.getName()).collect(Collectors.toSet());
+    long ignorableCount = ignoredNamedParameters.stream().filter(name -> namedParameters.contains(name)).count();
+    
+    if(0 != expression.getParameters().size() - containedEvaluators.size() - ignorableCount){
+      if(namedParameters.isEmpty()){
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators or field names", expression));
+      }
+      else{
+        throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - unknown operands found - expecting only StreamEvaluators, field names, or named parameters [%s]", expression, namedParameters.stream().collect(Collectors.joining(","))));
+      }
+    }
+  }
+  
+  @Override
+  public Object evaluate(Tuple tuple) throws IOException {    
+    try{
+      List<Object> containedResults = recursivelyEvaluate(tuple);
+      // this needs to be treated as an array of objects when going into doWork(Object ... values)
+      return normalizeOutputType(doWork(containedResults.toArray()));
+    }
+    catch(UncheckedIOException e){
+      throw e.getCause();
+    }
+  }  
+  
+  public List<Object> recursivelyEvaluate(Tuple tuple) throws IOException {
+    List<Object> results = new ArrayList<>();
+    try{
+      for(StreamEvaluator containedEvaluator : containedEvaluators){
+        results.add(normalizeInputType(containedEvaluator.evaluate(tuple)));
+      }
+    }
+    catch(StreamEvaluatorException e){
+      throw new IOException(String.format(Locale.ROOT, "Failed to evaluate expression %s - %s", toExpression(constructingFactory), e.getMessage()), e);
+    }
+    
+    return results;
+  }
+
+  @Override
+  public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
+    StreamExpression expression = new StreamExpression(factory.getFunctionName(getClass()));
+    
+    for(StreamEvaluator evaluator : containedEvaluators){
+      expression.addParameter(evaluator.toExpression(factory));
+    }
+    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 void setStreamContext(StreamContext context) {
+    this.streamContext = context;
+    
+    for(StreamEvaluator containedEvaluator : containedEvaluators){
+      containedEvaluator.setStreamContext(context);
+    }
+  }
+  public StreamContext getStreamContext(){
+    return streamContext;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java
new file mode 100644
index 0000000..a1cdeb5
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericEvaluator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveNumericEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public RecursiveNumericEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+    
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return Double.NaN;
+      }
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof BigDecimal){
+      return value;
+    }
+    else if(value instanceof Number){
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof Collection){
+      return ((Collection<?>)value).stream().map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else if(value.getClass().isArray()){
+      Stream<?> stream = Stream.empty();
+      if(value instanceof double[]){
+        stream = Arrays.stream((double[])value).boxed();
+      }
+      else if(value instanceof int[]){
+        stream = Arrays.stream((int[])value).boxed();
+      }
+      else if(value instanceof long[]){
+        stream = Arrays.stream((long[])value).boxed();
+      }
+      else if(value instanceof String[]){
+        stream = Arrays.stream((String[])value);
+      }      
+      return stream.map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+    }
+  }  
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java
new file mode 100644
index 0000000..08305b1
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveNumericListEvaluator.java
@@ -0,0 +1,67 @@
+/*
+ * 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.List;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveNumericListEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public RecursiveNumericListEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+    
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List){
+      return ((List<?>)value).stream().map(innerValue -> convertToNumber(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric list value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+    }
+  }
+  
+  private BigDecimal convertToNumber(Object value){
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof Double){
+      if(Double.isNaN((Double)value)){
+        return null;
+      }
+      return new BigDecimal(value.toString());
+    }
+    else if(value instanceof BigDecimal){
+      return (BigDecimal)value;
+    }
+    else if(value instanceof Number){
+      return new BigDecimal(value.toString());
+    }
+    else{
+      throw new StreamEvaluatorException("Numeric value expected but found type %s for value %s", value.getClass().getName(), value.toString());
+    }
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java
new file mode 100644
index 0000000..7c6f282
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveObjectEvaluator.java
@@ -0,0 +1,35 @@
+/*
+ * 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.List;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveObjectEvaluator extends RecursiveEvaluator {
+  protected static final long serialVersionUID = 1L;
+  
+  public RecursiveObjectEvaluator(StreamExpression expression, StreamFactory factory) throws IOException{
+    super(expression, factory);
+  }
+  
+  public RecursiveObjectEvaluator(StreamExpression expression, StreamFactory factory, List<String> ignoredNamedParameters) throws IOException{
+    super(expression, factory, ignoredNamedParameters);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java
new file mode 100644
index 0000000..e092130
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RecursiveTemporalEvaluator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.io.UncheckedIOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
+import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
+
+public abstract class RecursiveTemporalEvaluator extends RecursiveEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  private String functionName;
+  
+  public RecursiveTemporalEvaluator(StreamExpression expression, StreamFactory factory, String functionName) throws IOException{
+    super(expression, factory);
+    this.functionName = functionName;
+    
+    if(1 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,containedEvaluators.size()));
+    }
+  }
+  
+  public String getFunction() {
+    return functionName;
+  }
+  
+  protected abstract Object getDatePart(TemporalAccessor value);
+    
+  public Object normalizeInputType(Object value) throws StreamEvaluatorException {
+    if(null == value){
+      return value;
+    }
+    
+    // There is potential for various types to be coming in which are valid temporal values.
+    // The order in which we check these types is actually somewhat critical in how we handle them.
+    
+    if(value instanceof Instant){
+      // Convert to LocalDateTime at UTC
+      return LocalDateTime.ofInstant((Instant)value, ZoneOffset.UTC);
+    }
+    else if(value instanceof TemporalAccessor){
+      // Any other TemporalAccessor can just be returned
+      return value;
+    }
+    else if(value instanceof Long){
+      // Convert to Instant and recurse in
+      return normalizeInputType(Instant.ofEpochMilli((Long)value));
+    }
+    else if(value instanceof Date){
+      // Convert to Instant and recurse in
+      return normalizeInputType(((Date)value).toInstant());
+    }
+    else if(value instanceof String){
+      String valueStr = (String)value;      
+      if (!valueStr.isEmpty()) {
+        try {
+          // Convert to Instant and recurse in
+          return normalizeInputType(Instant.parse(valueStr));
+        } catch (DateTimeParseException e) {
+          throw new UncheckedIOException(new IOException(String.format(Locale.ROOT, "Invalid parameter %s - The String must be formatted in the ISO_INSTANT date format.", valueStr)));
+        }
+      }
+    }
+    else if(value instanceof List){
+      // for each list value, recurse in
+      return ((List<?>)value).stream().map(innerValue -> normalizeInputType(innerValue)).collect(Collectors.toList());
+    }
+
+    throw new UncheckedIOException(new IOException(String.format(Locale.ROOT, "Invalid parameter %s - The parameter must be a string formatted ISO_INSTANT or of type Long,Instant,Date,LocalDateTime or TemporalAccessor.", String.valueOf(value))));
+  }
+  
+  public Object doWork(Object value) {
+    if(null == value){
+      return null;
+    }
+    else if(value instanceof List<?>){
+      return ((List<?>)value).stream().map(innerValue -> doWork(innerValue)).collect(Collectors.toList());
+    }
+    else{
+      // We know it's an Instant
+      try {
+        return getDatePart((TemporalAccessor)value);
+      } catch (UnsupportedTemporalTypeException utte) {
+        throw new UncheckedIOException(new IOException(String.format(Locale.ROOT, "It is not possible to call '%s' function on %s", getFunction(), value.getClass().getName())));
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
index f61c409..b38b6b3 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RegressionEvaluator.java
@@ -17,6 +17,7 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -24,50 +25,44 @@ import java.util.Map;
 
 import org.apache.commons.math3.stat.regression.SimpleRegression;
 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 RegressionEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public RegressionEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class RegressionEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public RegressionEvaluator(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 columns but found %d",expression,subEvaluators.size()));
-    }
-
   }
 
-  public Tuple 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)));
     }
-
-    for(int i=0; i<numbers2.size(); i++) {
-      column2[i] = 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()));
+    }
+    
+    List<?> l1 = (List<?>)first;
+    List<?> l2 = (List<?>)second;
+    
+    if(l2.size() < l1.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - first list (%d) has more values than the second list (%d)",toExpression(constructingFactory), l1.size(), l2.size()));      
     }
 
     SimpleRegression regression = new SimpleRegression();
-    for(int i=0; i<column1.length; i++) {
-      regression.addData(column1[i], column2[i]);
+    for(int idx = 0; idx < l1.size(); ++idx){
+      regression.addData(((BigDecimal)l1.get(idx)).doubleValue(), ((BigDecimal)l2.get(idx)).doubleValue());
     }
-
-    Map map = new HashMap();
+    
+    Map<String,Number> map = new HashMap<>();
     map.put("slope", regression.getSlope());
     map.put("intercept", regression.getIntercept());
     map.put("R", regression.getR());
@@ -79,35 +74,21 @@ public class RegressionEvaluator extends ComplexEvaluator implements Expressible
     map.put("totalSumSquares", regression.getTotalSumSquares());
     map.put("significance", regression.getSignificance());
     map.put("meanSquareError", regression.getMeanSquareError());
+    
     return new RegressionTuple(regression, map);
   }
-
+  
   public static class RegressionTuple extends Tuple {
-
     private SimpleRegression simpleRegression;
 
-    public RegressionTuple(SimpleRegression simpleRegression, Map map) {
+    public RegressionTuple(SimpleRegression simpleRegression, Map<?,?> map) {
       super(map);
       this.simpleRegression = simpleRegression;
     }
 
-    public double predict(double d) {
-      return this.simpleRegression.predict(d);
+    public double predict(double value) {
+      return this.simpleRegression.predict(value);
     }
   }
 
-  @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/ResidualsEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
index 9a9c869..8c5ca34 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ResidualsEvaluator.java
@@ -14,69 +14,70 @@
  * 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.ArrayList;
-import java.util.Locale;
+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.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 ResidualsEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ResidualsEvaluator(StreamExpression expression,
-                            StreamFactory factory) throws IOException {
+public class ResidualsEvaluator extends RecursiveObjectEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ResidualsEvaluator(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 (regression result and two numeric arrays) but found %d",expression,subEvaluators.size()));
-    }
   }
 
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator r = subEvaluators.get(0);
-    StreamEvaluator a = subEvaluators.get(1);
-    StreamEvaluator b = subEvaluators.get(2);
-
-    RegressionEvaluator.RegressionTuple rt= (RegressionEvaluator.RegressionTuple)r.evaluate(tuple);
-    List<Number> listA = (List<Number>)a.evaluate(tuple);
-    List<Number> listB = (List<Number>)b.evaluate(tuple);
-    List<Number> residuals = new ArrayList();
+  @Override
+  public Object doWork(Object ... values) throws IOException{
+    if(3 != values.length){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - three values expected but found %d",toExpression(constructingFactory), values.length));
+    }
+    
+    if(Arrays.stream(values).filter(value -> null == value).count() > 0){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - null value found",toExpression(constructingFactory)));
+    }
 
-    for(int i=0; i<listA.size(); i++) {
-      double valueA = listA.get(i).doubleValue();
-      double prediction = rt.predict(valueA);
-      double valueB = listB.get(i).doubleValue();
-      double residual = valueB - prediction;
+    if(!(values[0] instanceof RegressionEvaluator.RegressionTuple)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RegressionTuple",toExpression(constructingFactory), values[0].getClass().getSimpleName()));
+    }
+    if(!(values[1] instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the second value, expecting a list",toExpression(constructingFactory), values[1].getClass().getSimpleName()));
+    }
+    if(!(values[2] instanceof List<?>)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the third value, expecting a list",toExpression(constructingFactory), values[2].getClass().getSimpleName()));
+    }
+    if(((List<?>)values[1]).stream().filter(value -> !(value instanceof Number)).count() > 0){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a list of numbers for the second value",toExpression(constructingFactory)));
+    }
+    if(((List<?>)values[2]).stream().filter(value -> !(value instanceof Number)).count() > 0){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting a list of numbers for the third value",toExpression(constructingFactory)));
+    }    
+    
+    RegressionEvaluator.RegressionTuple regressedTuple = (RegressionEvaluator.RegressionTuple)values[0];
+    List<?> l1 = (List<?>)values[1];
+    List<?> l2 = (List<?>)values[2];
+    
+    if(l2.size() < l1.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - first list (%d) has more values than the second list (%d)",toExpression(constructingFactory), l1.size(), l2.size()));      
+    }
+    
+    List<Number> residuals = new ArrayList<>();
+    for(int idx = 0; idx < l1.size(); ++idx){
+      double value1 = ((Number)l1.get(idx)).doubleValue();
+      double value2 = ((Number)l2.get(idx)).doubleValue();
+      
+      double prediction = regressedTuple.predict(value1);
+      double residual = value2 - prediction;
+      
       residuals.add(residual);
     }
-
+    
     return residuals;
   }
-
-  @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/ReverseEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
index 56e0b63..7ece9c9 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ReverseEvaluator.java
@@ -21,52 +21,37 @@ 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.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 ReverseEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ReverseEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class ReverseEvaluator extends RecursiveObjectEvaluator implements OneValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ReverseEvaluator(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 List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator colEval1 = subEvaluators.get(0);
-
-    List<Number> numbers1 = (List<Number>)colEval1.evaluate(tuple);
-    List<Number> rev = new ArrayList();
-    for(int i=numbers1.size()-1; i>=0; i--) {
-      rev.add(numbers1.get(i));
-    }
-
-    return rev;
-  }
-
-  @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){
+      List<?> actual = (List<?>)value;
+      
+      List<Object> reversed = new ArrayList<>();
+      for(int idx = actual.size() - 1; idx >= 0; --idx){
+        reversed.add(actual.get(idx));
+      }
+      
+      return reversed;
+    }
+    else{
+      return 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/RoundEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java
index 959ad39..0cfb3d4 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/RoundEvaluator.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 RoundEvaluator extends NumberEvaluator {
+public class RoundEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public RoundEvaluator(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.round(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.round(((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/SampleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
index 8b725cf..5732f85 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SampleEvaluator.java
@@ -18,57 +18,40 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
 import org.apache.commons.math3.distribution.RealDistribution;
-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;
 
-import java.util.List;
-import java.util.ArrayList;
-
-public class SampleEvaluator extends ComplexEvaluator implements Expressible {
+public class SampleEvaluator extends RecursiveObjectEvaluator implements TwoValueWorker {
 
   private static final long serialVersionUID = 1;
 
   public SampleEvaluator(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 (regression result and a number) but found %d",expression,subEvaluators.size()));
-    }
   }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-    StreamEvaluator r = subEvaluators.get(0);
-    StreamEvaluator d = subEvaluators.get(1);
-    Number number = (Number)d.evaluate(tuple);
-    RealDistribution rd= (RealDistribution)r.evaluate(tuple);
-    double[] sample = rd.sample(number.intValue());
-    List<Number> list = new ArrayList();
-    for(double n : sample) {
-      list.add(n);
-    }
-    return list;
-  }
-
+  
   @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)));
+    }
+    if(!(first instanceof RealDistribution)){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - found type %s for the first value, expecting a RealDistribution",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()));
+    }
+    
+    RealDistribution realDistribution = (RealDistribution)first;
+    
+    return Arrays.stream(realDistribution.sample(((Number)second).intValue())).mapToObj(item -> item).collect(Collectors.toList());    
   }
 
-  @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/ScaleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
index 806e6f6..99950da 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/ScaleEvaluator.java
@@ -17,67 +17,46 @@
 package org.apache.solr.client.solrj.io.eval;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
 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 ScaleEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public ScaleEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class ScaleEvaluator extends RecursiveNumericEvaluator implements TwoValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public ScaleEvaluator(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 numEval = subEvaluators.get(0);
-    StreamEvaluator colEval1 = subEvaluators.get(1);
-
-    Number num = (Number)numEval.evaluate(tuple);
-    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[] scaled = MathArrays.scale(num.doubleValue(), column1);
 
-    List<Number> scaledList = new ArrayList(scaled.length);
-    for(double d : scaled) {
-      scaledList.add(d);
+    if(2 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting two values but found %d",expression, containedEvaluators.size()));
     }
-
-    return scaledList;
-  }
-
-  @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 first, Object second) throws IOException{
+    
+    if(null == first || null == second){
+      return null;
+    }
+    
+    // we know these are all numbers or lists of numbers
+    if(first instanceof List<?>){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting Number as first value but found a list", toExpression(constructingFactory)));
+    }
+    
+    double[] scaleOver;
+    if(second instanceof Number){
+      scaleOver = Arrays.asList((Number)second).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray();
+    }
+    else{
+      scaleOver = ((List<?>)second).stream().mapToDouble(value -> ((Number)value).doubleValue()).toArray();
+    }
+      
+    return Arrays.stream(MathArrays.scale(((Number)first).doubleValue(), scaleOver)).mapToObj(Double::new).collect(Collectors.toList());    
   }
-}
\ 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/SequenceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
index c6db106..ee9dece 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SequenceEvaluator.java
@@ -22,58 +22,35 @@ import java.util.List;
 import java.util.Locale;
 
 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 SequenceEvaluator extends ComplexEvaluator implements Expressible {
-
-  private static final long serialVersionUID = 1;
-
-  public SequenceEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
+public class SequenceEvaluator extends RecursiveNumericEvaluator implements ManyValueWorker {
+  protected static final long serialVersionUID = 1L;
+  
+  public SequenceEvaluator(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()));
-    }
-
-  }
-
-  public List<Number> evaluate(Tuple tuple) throws IOException {
-
-    StreamEvaluator sizeEval = subEvaluators.get(0);
-    StreamEvaluator startEval = subEvaluators.get(1);
-    StreamEvaluator strideEval = subEvaluators.get(2);
-
-    Number sizeNum = (Number)sizeEval.evaluate(tuple);
-    Number startNum = (Number)startEval.evaluate(tuple);
-    Number strideNum = (Number)strideEval.evaluate(tuple);
-
-    int[] sequence = MathArrays.sequence(sizeNum.intValue(), startNum.intValue(), strideNum.intValue());
-    List<Number> numbers = new ArrayList(sequence.length);
-    for(int i : sequence) {
-      numbers.add(i);
+    if(3 != containedEvaluators.size()){
+      throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting three values but found %d",expression, containedEvaluators.size()));
     }
-
-    return numbers;
   }
 
   @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... 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));
+    }
+    
+    Number sizeNum = (Number)values[0];
+    Number startNum = (Number)values[1];
+    Number strideNum = (Number)values[2];
+    
+    List<Number> sequence = new ArrayList<>(sizeNum.intValue());
+    for(int number : MathArrays.sequence(sizeNum.intValue(), startNum.intValue(), strideNum.intValue())){
+      sequence.add(number);
+    }
+    
+    return sequence;
   }
-}
\ 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/SimpleEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
deleted file mode 100644
index 356c495..0000000
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SimpleEvaluator.java
+++ /dev/null
@@ -1,36 +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.util.UUID;
-
-import org.apache.solr.client.solrj.io.stream.StreamContext;
-
-public abstract class SimpleEvaluator implements StreamEvaluator {
-  private static final long serialVersionUID = 1L;
-  
-  protected UUID nodeId = UUID.randomUUID();
-  protected StreamContext streamContext;
-
-  public void setStreamContext(StreamContext streamContext) {
-    this.streamContext = streamContext;
-  }
-  public StreamContext getStreamContext(){
-    return streamContext;
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
index c5ac397..5426100 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SineEvaluator.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 SineEvaluator extends NumberEvaluator {
+public class SineEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public SineEvaluator(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.sin(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.sin(((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/SourceEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java
new file mode 100644
index 0000000..f391129
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SourceEvaluator.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util.UUID;
+
+import org.apache.solr.client.solrj.io.stream.StreamContext;
+
+public abstract class SourceEvaluator implements StreamEvaluator {
+  private static final long serialVersionUID = 1L;
+  
+  protected UUID nodeId = UUID.randomUUID();
+  protected StreamContext streamContext;
+
+  public void setStreamContext(StreamContext streamContext) {
+    this.streamContext = streamContext;
+  }
+  public StreamContext getStreamContext(){
+    return streamContext;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bd587e1f/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
index a39fdb2..e20109e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/SquareRootEvaluator.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 SquareRootEvaluator extends NumberEvaluator {
+public class SquareRootEvaluator extends RecursiveNumericEvaluator implements OneValueWorker {
   protected static final long serialVersionUID = 1L;
   
   public SquareRootEvaluator(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.sqrt(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.sqrt(((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/StreamEvaluator.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
index 0b0d509..934f4e4 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluator.java
@@ -50,4 +50,4 @@ public interface StreamEvaluator extends Expressible, Serializable {
     return null;
   }
   
-}
+}
\ 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/StreamEvaluatorException.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java
new file mode 100644
index 0000000..de9689e
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/io/eval/StreamEvaluatorException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util.Locale;
+
+public class StreamEvaluatorException extends RuntimeException {
+
+  private static final long serialVersionUID = 1L;
+
+  public StreamEvaluatorException(String message, Object ... args){
+    super(String.format(Locale.ROOT, message, args));
+  }
+  
+  public StreamEvaluatorException(Throwable cause, String message, Object ... args){
+    super(String.format(Locale.ROOT, message, args), cause);
+  }
+}


Mime
View raw message