You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ro...@apache.org on 2017/06/27 14:54:22 UTC

[3/3] lucene-solr:master: LUCENE-7723: Add hashCode and equals to DoubleValuesSource

LUCENE-7723: Add hashCode and equals to DoubleValuesSource


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/1a278ae8
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/1a278ae8
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/1a278ae8

Branch: refs/heads/master
Commit: 1a278ae89bee3072803abe2387091df9942e96af
Parents: 808171a
Author: Alan Woodward <ro...@apache.org>
Authored: Mon May 8 11:24:17 2017 +0100
Committer: Alan Woodward <ro...@apache.org>
Committed: Tue Jun 27 15:53:39 2017 +0100

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   3 +
 .../lucene/search/DoubleValuesSource.java       | 209 ++++++++++---------
 .../apache/lucene/search/LongValuesSource.java  |  82 ++++++--
 .../lucene/search/TestDoubleValuesSource.java   |  16 +-
 .../facet/range/TestRangeFacetCounts.java       |  75 ++++---
 .../queries/function/FunctionScoreQuery.java    |   9 -
 .../lucene/queries/function/ValueSource.java    | 197 ++++++++++-------
 .../function/TestFunctionScoreExplanations.java |   8 +-
 .../function/TestFunctionScoreQuery.java        |  91 +++++++-
 .../DocumentValueSourceDictionaryTest.java      |  15 ++
 10 files changed, 459 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index eede65b..517d937 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -89,6 +89,9 @@ API Changes
 * LUCENE-7867: The deprecated Token class is now only available in the test
   framework (Alan Woodward, Adrien Grand)
 
+* LUCENE-7723: DoubleValuesSource enforces implementation of equals() and
+  hashCode() (Alan Woodward)
+
 Bug Fixes
 
 * LUCENE-7626: IndexWriter will no longer accept broken token offsets

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
index 929441d..342c08b 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DoubleValuesSource.java
@@ -20,9 +20,7 @@ package org.apache.lucene.search;
 import java.io.IOException;
 import java.util.Objects;
 import java.util.function.DoubleToLongFunction;
-import java.util.function.DoubleUnaryOperator;
 import java.util.function.LongToDoubleFunction;
-import java.util.function.ToDoubleBiFunction;
 
 import org.apache.lucene.index.DocValues;
 import org.apache.lucene.index.LeafReaderContext;
@@ -76,14 +74,33 @@ public abstract class DoubleValuesSource {
     return new DoubleValuesSortField(this, reverse);
   }
 
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public abstract boolean equals(Object obj);
+
+  @Override
+  public abstract String toString();
+
   /**
    * Convert to a LongValuesSource by casting the double values to longs
    */
   public final LongValuesSource toLongValuesSource() {
-    return new LongValuesSource() {
-      @Override
+    return new LongDoubleValuesSource(this);
+  }
+
+  private static class LongDoubleValuesSource extends LongValuesSource {
+
+    private final DoubleValuesSource inner;
+
+    private LongDoubleValuesSource(DoubleValuesSource inner) {
+      this.inner = inner;
+    }
+
+    @Override
       public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        DoubleValues in = DoubleValuesSource.this.getValues(ctx, scores);
+        DoubleValues in = inner.getValues(ctx, scores);
         return new LongValues() {
           @Override
           public long longValue() throws IOException {
@@ -99,9 +116,27 @@ public abstract class DoubleValuesSource {
 
       @Override
       public boolean needsScores() {
-        return DoubleValuesSource.this.needsScores();
+        return inner.needsScores();
       }
-    };
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      LongDoubleValuesSource that = (LongDoubleValuesSource) o;
+      return Objects.equals(inner, that.inner);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(inner);
+    }
+
+    @Override
+    public String toString() {
+      return "long(" + inner.toString() + ")";
+    }
+
   }
 
   /**
@@ -164,115 +199,80 @@ public abstract class DoubleValuesSource {
     public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) {
       return scoreExplanation;
     }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return obj == this;
+    }
+
+    @Override
+    public String toString() {
+      return "scores";
+    }
   };
 
   /**
    * Creates a DoubleValuesSource that always returns a constant value
    */
   public static DoubleValuesSource constant(double value) {
-    return new DoubleValuesSource() {
-      @Override
-      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        return new DoubleValues() {
-          @Override
-          public double doubleValue() throws IOException {
-            return value;
-          }
-
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            return true;
-          }
-        };
-      }
-
-      @Override
-      public boolean needsScores() {
-        return false;
-      }
+    return new ConstantValuesSource(value);
+  }
 
-      @Override
-      public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) {
-        return Explanation.match((float) value, "constant(" + value + ")");
-      }
+  private static class ConstantValuesSource extends DoubleValuesSource {
 
-      @Override
-      public String toString() {
-        return "constant(" + value + ")";
-      }
-    };
-  }
+    private final double value;
 
-  /**
-   * Creates a DoubleValuesSource that is a function of another DoubleValuesSource
-   */
-  public static DoubleValuesSource function(DoubleValuesSource in, String description, DoubleUnaryOperator function) {
-    return new DoubleValuesSource() {
-      @Override
-      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        DoubleValues inputs = in.getValues(ctx, scores);
-        return new DoubleValues() {
-          @Override
-          public double doubleValue() throws IOException {
-            return function.applyAsDouble(inputs.doubleValue());
-          }
+    private ConstantValuesSource(double value) {
+      this.value = value;
+    }
 
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            return inputs.advanceExact(doc);
-          }
-        };
-      }
+    @Override
+    public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+      return new DoubleValues() {
+        @Override
+        public double doubleValue() throws IOException {
+          return value;
+        }
 
-      @Override
-      public boolean needsScores() {
-        return in.needsScores();
-      }
+        @Override
+        public boolean advanceExact(int doc) throws IOException {
+          return true;
+        }
+      };
+    }
 
-      @Override
-      public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
-        Explanation inner = in.explain(ctx, docId, scoreExplanation);
-        return Explanation.match((float) function.applyAsDouble(inner.getValue()), description + ", computed from:", inner, scoreExplanation);
-      }
-    };
-  }
+    @Override
+    public boolean needsScores() {
+      return false;
+    }
 
-  /**
-   * Creates a DoubleValuesSource that is a function of another DoubleValuesSource and a score
-   * @param in        the DoubleValuesSource to use as an input
-   * @param description a description of the function
-   * @param function  a function of the form (source, score) == result
-   */
-  public static DoubleValuesSource scoringFunction(DoubleValuesSource in, String description, ToDoubleBiFunction<Double, Double> function) {
-    return new DoubleValuesSource() {
-      @Override
-      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        DoubleValues inputs = in.getValues(ctx, scores);
-        return new DoubleValues() {
-          @Override
-          public double doubleValue() throws IOException {
-            return function.applyAsDouble(inputs.doubleValue(), scores.doubleValue());
-          }
+    @Override
+    public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) {
+      return Explanation.match((float) value, "constant(" + value + ")");
+    }
 
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            return inputs.advanceExact(doc);
-          }
-        };
-      }
+    @Override
+    public int hashCode() {
+      return Objects.hash(value);
+    }
 
-      @Override
-      public boolean needsScores() {
-        return true;
-      }
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      ConstantValuesSource that = (ConstantValuesSource) o;
+      return Double.compare(that.value, value) == 0;
+    }
 
-      @Override
-      public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
-        Explanation inner = in.explain(ctx, docId, scoreExplanation);
-        return Explanation.match((float) function.applyAsDouble((double)inner.getValue(), (double)scoreExplanation.getValue()),
-                    description + ", computed from:", inner, scoreExplanation);
-      }
-    };
+    @Override
+    public String toString() {
+      return "constant(" + value + ")";
+    }
   }
 
   /**
@@ -313,6 +313,11 @@ public abstract class DoubleValuesSource {
     }
 
     @Override
+    public String toString() {
+      return "double(" + field + ")";
+    }
+
+    @Override
     public int hashCode() {
       return Objects.hash(field, decoder);
     }
@@ -342,9 +347,9 @@ public abstract class DoubleValuesSource {
     public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
       DoubleValues values = getValues(ctx, null);
       if (values.advanceExact(docId))
-        return Explanation.match((float)values.doubleValue(), "double(" + field + ")");
+        return Explanation.match((float)values.doubleValue(), this.toString());
       else
-        return Explanation.noMatch("double(" + field + ")");
+        return Explanation.noMatch(this.toString());
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java b/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
index 9a23cab..975a905 100644
--- a/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
+++ b/lucene/core/src/java/org/apache/lucene/search/LongValuesSource.java
@@ -52,6 +52,15 @@ public abstract class LongValuesSource {
    */
   public abstract boolean needsScores();
 
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public abstract boolean equals(Object obj);
+
+  @Override
+  public abstract String toString();
+
   /**
    * Create a sort field based on the value of this producer
    * @param reverse true if the sort should be decreasing
@@ -78,27 +87,55 @@ public abstract class LongValuesSource {
    * Creates a LongValuesSource that always returns a constant value
    */
   public static LongValuesSource constant(long value) {
-    return new LongValuesSource() {
-      @Override
-      public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        return new LongValues() {
-          @Override
-          public long longValue() throws IOException {
-            return value;
-          }
-
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            return true;
-          }
-        };
-      }
+    return new ConstantLongValuesSource(value);
+  }
+
+  private static class ConstantLongValuesSource extends LongValuesSource {
+
+    private final long value;
+
+    private ConstantLongValuesSource(long value) {
+      this.value = value;
+    }
+
+    @Override
+    public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+      return new LongValues() {
+        @Override
+        public long longValue() throws IOException {
+          return value;
+        }
+
+        @Override
+        public boolean advanceExact(int doc) throws IOException {
+          return true;
+        }
+      };
+    }
+
+    @Override
+    public boolean needsScores() {
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(value);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      ConstantLongValuesSource that = (ConstantLongValuesSource) o;
+      return value == that.value;
+    }
+
+    @Override
+    public String toString() {
+      return "constant(" + value + ")";
+    }
 
-      @Override
-      public boolean needsScores() {
-        return false;
-      }
-    };
   }
 
   private static class FieldValuesSource extends LongValuesSource {
@@ -118,6 +155,11 @@ public abstract class LongValuesSource {
     }
 
     @Override
+    public String toString() {
+      return "long(" + field + ")";
+    }
+
+    @Override
     public int hashCode() {
       return Objects.hash(field);
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java b/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
index 54501e6..de2479c 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDoubleValuesSource.java
@@ -92,6 +92,14 @@ public class TestDoubleValuesSource extends LuceneTestCase {
     assertEquals(vs1.hashCode(), vs2.hashCode());
     DoubleValuesSource v3 = DoubleValuesSource.fromLongField("long");
     assertFalse(vs1.equals(v3));
+
+    assertEquals(DoubleValuesSource.constant(5), DoubleValuesSource.constant(5));
+    assertEquals(DoubleValuesSource.constant(5).hashCode(), DoubleValuesSource.constant(5).hashCode());
+    assertFalse((DoubleValuesSource.constant(5).equals(DoubleValuesSource.constant(6))));
+
+    assertEquals(DoubleValuesSource.SCORES, DoubleValuesSource.SCORES);
+    assertFalse(DoubleValuesSource.constant(5).equals(DoubleValuesSource.SCORES));
+
   }
 
   public void testSimpleFieldSortables() throws Exception {
@@ -184,13 +192,6 @@ public class TestDoubleValuesSource extends LuceneTestCase {
       testExplanations(q, DoubleValuesSource.fromDoubleField("double"));
       testExplanations(q, DoubleValuesSource.fromDoubleField("onefield"));
       testExplanations(q, DoubleValuesSource.constant(5.45));
-      testExplanations(q, DoubleValuesSource.function(
-          DoubleValuesSource.fromDoubleField("double"), "v * 4 + 73",
-          v -> v * 4 + 73
-      ));
-      testExplanations(q, DoubleValuesSource.scoringFunction(
-          DoubleValuesSource.fromDoubleField("double"), "v * score", (v, s) -> v * s
-      ));
     }
   }
 
@@ -227,4 +228,5 @@ public class TestDoubleValuesSource extends LuceneTestCase {
       }
     });
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java
----------------------------------------------------------------------
diff --git a/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java b/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java
index 06bd2e5..5a80c3b 100644
--- a/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java
+++ b/lucene/facet/src/test/org/apache/lucene/facet/range/TestRangeFacetCounts.java
@@ -706,6 +706,51 @@ public class TestRangeFacetCounts extends FacetTestCase {
 
   }
 
+  private static class PlusOneValuesSource extends DoubleValuesSource {
+
+    @Override
+    public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+      return new DoubleValues() {
+        int doc = -1;
+        @Override
+        public double doubleValue() throws IOException {
+          return doc + 1;
+        }
+
+        @Override
+        public boolean advanceExact(int doc) throws IOException {
+          this.doc = doc;
+          return true;
+        }
+      };
+    }
+
+    @Override
+    public boolean needsScores() {
+      return false;
+    }
+
+    @Override
+    public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
+      return Explanation.match(docId + 1, "");
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return obj.getClass() == PlusOneValuesSource.class;
+    }
+
+    @Override
+    public String toString() {
+      return null;
+    }
+  }
+
   public void testCustomDoubleValuesSource() throws Exception {
     Directory dir = newDirectory();
     RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
@@ -718,35 +763,7 @@ public class TestRangeFacetCounts extends FacetTestCase {
     // Test wants 3 docs in one segment:
     writer.forceMerge(1);
 
-    final DoubleValuesSource vs = new DoubleValuesSource() {
-
-      @Override
-      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        return new DoubleValues() {
-          int doc = -1;
-          @Override
-          public double doubleValue() throws IOException {
-            return doc + 1;
-          }
-
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            this.doc = doc;
-            return true;
-          }
-        };
-      }
-
-      @Override
-      public boolean needsScores() {
-        return false;
-      }
-
-      @Override
-      public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
-        return Explanation.match(docId + 1, "");
-      }
-    };
+    final DoubleValuesSource vs = new PlusOneValuesSource();
 
     FacetsConfig config = new FacetsConfig();
     

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java
index 63a98de..aa02c19 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionScoreQuery.java
@@ -121,15 +121,6 @@ public final class FunctionScoreQuery extends Query {
           Explanation.match(boost, "boost"), expl);
     }
 
-    private Explanation scoreExplanation(LeafReaderContext context, int doc, DoubleValues scores) throws IOException {
-      if (valueSource.needsScores() == false)
-        return Explanation.match((float) scores.doubleValue(), valueSource.toString());
-      float score = (float) scores.doubleValue();
-      return Explanation.match(score, "computed from:",
-          Explanation.match(score, valueSource.toString()),
-          inner.explain(context, doc));
-    }
-
     @Override
     public Scorer scorer(LeafReaderContext context) throws IOException {
       Scorer in = inner.scorer(context);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
index e49c823..cb3faa3 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/ValueSource.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.search.DocIdSetIterator;
@@ -120,85 +121,141 @@ public abstract class ValueSource {
    * Expose this ValueSource as a LongValuesSource
    */
   public LongValuesSource asLongValuesSource() {
-    return new LongValuesSource() {
-      @Override
-      public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        Map context = new IdentityHashMap<>();
-        FakeScorer scorer = new FakeScorer();
-        context.put("scorer", scorer);
-        final FunctionValues fv = ValueSource.this.getValues(context, ctx);
-        return new LongValues() {
-
-          @Override
-          public long longValue() throws IOException {
-            return fv.longVal(scorer.current);
-          }
+    return new WrappedLongValuesSource(this);
+  }
+
+  private static class WrappedLongValuesSource extends LongValuesSource {
+
+    private final ValueSource in;
+
+    private WrappedLongValuesSource(ValueSource in) {
+      this.in = in;
+    }
+
+    @Override
+    public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+      Map context = new IdentityHashMap<>();
+      FakeScorer scorer = new FakeScorer();
+      context.put("scorer", scorer);
+      final FunctionValues fv = in.getValues(context, ctx);
+      return new LongValues() {
+
+        @Override
+        public long longValue() throws IOException {
+          return fv.longVal(scorer.current);
+        }
+
+        @Override
+        public boolean advanceExact(int doc) throws IOException {
+          scorer.current = doc;
+          if (scores != null && scores.advanceExact(doc))
+            scorer.score = (float) scores.doubleValue();
+          else
+            scorer.score = 0;
+          return fv.exists(doc);
+        }
+      };
+    }
+
+    @Override
+    public boolean needsScores() {
+      return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      WrappedLongValuesSource that = (WrappedLongValuesSource) o;
+      return Objects.equals(in, that.in);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(in);
+    }
+
+    @Override
+    public String toString() {
+      return in.toString();
+    }
 
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            scorer.current = doc;
-            if (scores != null && scores.advanceExact(doc))
-              scorer.score = (float) scores.doubleValue();
-            else
-              scorer.score = 0;
-            return fv.exists(doc);
-          }
-        };
-      }
-
-      @Override
-      public boolean needsScores() {
-        return false;
-      }
-    };
   }
 
   /**
    * Expose this ValueSource as a DoubleValuesSource
    */
   public DoubleValuesSource asDoubleValuesSource() {
-    return new DoubleValuesSource() {
-      @Override
-      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
-        Map context = new HashMap<>();
-        FakeScorer scorer = new FakeScorer();
-        context.put("scorer", scorer);
-        FunctionValues fv = ValueSource.this.getValues(context, ctx);
-        return new DoubleValues() {
-
-          @Override
-          public double doubleValue() throws IOException {
-            return fv.doubleVal(scorer.current);
-          }
+    return new WrappedDoubleValuesSource(this);
+  }
+
+  private static class WrappedDoubleValuesSource extends DoubleValuesSource {
+
+    private final ValueSource in;
 
-          @Override
-          public boolean advanceExact(int doc) throws IOException {
-            scorer.current = doc;
-            if (scores != null && scores.advanceExact(doc)) {
-              scorer.score = (float) scores.doubleValue();
-            }
-            else
-              scorer.score = 0;
-            return fv.exists(doc);
+    private WrappedDoubleValuesSource(ValueSource in) {
+      this.in = in;
+    }
+
+    @Override
+    public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+      Map context = new HashMap<>();
+      FakeScorer scorer = new FakeScorer();
+      context.put("scorer", scorer);
+      FunctionValues fv = in.getValues(context, ctx);
+      return new DoubleValues() {
+
+        @Override
+        public double doubleValue() throws IOException {
+          return fv.doubleVal(scorer.current);
+        }
+
+        @Override
+        public boolean advanceExact(int doc) throws IOException {
+          scorer.current = doc;
+          if (scores != null && scores.advanceExact(doc)) {
+            scorer.score = (float) scores.doubleValue();
           }
-        };
-      }
-
-      @Override
-      public boolean needsScores() {
-        return true;  // be on the safe side
-      }
-
-      @Override
-      public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
-        Map context = new HashMap<>();
-        FakeScorer scorer = new FakeScorer();
-        scorer.score = scoreExplanation.getValue();
-        context.put("scorer", scorer);
-        FunctionValues fv = ValueSource.this.getValues(context, ctx);
-        return fv.explain(docId);
-      }
-    };
+          else
+            scorer.score = 0;
+          return fv.exists(doc);
+        }
+      };
+    }
+
+    @Override
+    public boolean needsScores() {
+      return true;  // be on the safe side
+    }
+
+    @Override
+    public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
+      Map context = new HashMap<>();
+      FakeScorer scorer = new FakeScorer();
+      scorer.score = scoreExplanation.getValue();
+      context.put("scorer", scorer);
+      FunctionValues fv = in.getValues(context, ctx);
+      return fv.explain(docId);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      WrappedDoubleValuesSource that = (WrappedDoubleValuesSource) o;
+      return Objects.equals(in, that.in);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(in);
+    }
+
+    @Override
+    public String toString() {
+      return in.toString();
+    }
+
   }
 
   //

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreExplanations.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreExplanations.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreExplanations.java
index bc8dbc1..2ed9d72 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreExplanations.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreExplanations.java
@@ -59,19 +59,15 @@ public class TestFunctionScoreExplanations extends BaseExplanationTestCase {
 
   public void testExplanationsIncludingScore() throws Exception {
 
-    DoubleValuesSource scores = DoubleValuesSource.function(DoubleValuesSource.SCORES, "v * 2", v -> v * 2);
-
     Query q = new TermQuery(new Term(FIELD, "w1"));
-    FunctionScoreQuery csq = new FunctionScoreQuery(q, scores);
+    FunctionScoreQuery csq = new FunctionScoreQuery(q, DoubleValuesSource.SCORES);
 
     qtest(csq, new int[] { 0, 1, 2, 3 });
 
     Explanation e1 = searcher.explain(q, 0);
     Explanation e = searcher.explain(csq, 0);
 
-    assertEquals(e.getDetails().length, 2);
-
-    assertEquals(e1.getValue() * 2, e.getValue(), 0.00001);
+    assertEquals(e, e1);
   }
 
   public void testSubExplanations() throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java
----------------------------------------------------------------------
diff --git a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java
index de5ad88..431d5f8 100644
--- a/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java
+++ b/lucene/queries/src/test/org/apache/lucene/queries/function/TestFunctionScoreQuery.java
@@ -17,12 +17,18 @@
 
 package org.apache.lucene.queries.function;
 
+import java.io.IOException;
+import java.util.function.DoubleUnaryOperator;
+import java.util.function.ToDoubleBiFunction;
+
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.BoostQuery;
+import org.apache.lucene.search.DoubleValues;
 import org.apache.lucene.search.DoubleValuesSource;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -70,7 +76,7 @@ public class TestFunctionScoreQuery extends FunctionTestSetup {
   public void testScoreModifyingSource() throws Exception {
 
     DoubleValuesSource iii = DoubleValuesSource.fromIntField("iii");
-    DoubleValuesSource score = DoubleValuesSource.scoringFunction(iii, "v * s", (v, s) -> v * s);
+    DoubleValuesSource score = scoringFunction(iii, (v, s) -> v * s);
 
     BooleanQuery bq = new BooleanQuery.Builder()
         .add(new TermQuery(new Term(TEXT_FIELD, "first")), BooleanClause.Occur.SHOULD)
@@ -95,8 +101,7 @@ public class TestFunctionScoreQuery extends FunctionTestSetup {
   // check boosts with non-distributive score source
   public void testBoostsAreAppliedLast() throws Exception {
 
-    DoubleValuesSource scores
-        = DoubleValuesSource.function(DoubleValuesSource.SCORES, "ln(v + 4)", v -> Math.log(v + 4));
+    DoubleValuesSource scores = function(DoubleValuesSource.SCORES, v -> Math.log(v + 4));
 
     Query q1 = new FunctionScoreQuery(new TermQuery(new Term(TEXT_FIELD, "text")), scores);
     TopDocs plain = searcher.search(q1, 5);
@@ -111,4 +116,84 @@ public class TestFunctionScoreQuery extends FunctionTestSetup {
 
   }
 
+  public static DoubleValuesSource function(DoubleValuesSource in, DoubleUnaryOperator function) {
+    return new DoubleValuesSource() {
+      @Override
+      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+        DoubleValues v = in.getValues(ctx, scores);
+        return new DoubleValues() {
+          @Override
+          public double doubleValue() throws IOException {
+            return function.applyAsDouble(v.doubleValue());
+          }
+
+          @Override
+          public boolean advanceExact(int doc) throws IOException {
+            return v.advanceExact(doc);
+          }
+        };
+      }
+
+      @Override
+      public boolean needsScores() {
+        return in.needsScores();
+      }
+
+      @Override
+      public int hashCode() {
+        return 0;
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+        return false;
+      }
+
+      @Override
+      public String toString() {
+        return "fn";
+      }
+    };
+  }
+
+  private static DoubleValuesSource scoringFunction(DoubleValuesSource in, ToDoubleBiFunction<Double, Double> function) {
+    return new DoubleValuesSource() {
+      @Override
+      public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+        DoubleValues v = in.getValues(ctx, scores);
+        return new DoubleValues() {
+          @Override
+          public double doubleValue() throws IOException {
+            return function.applyAsDouble(v.doubleValue(), scores.doubleValue());
+          }
+
+          @Override
+          public boolean advanceExact(int doc) throws IOException {
+            return v.advanceExact(doc);
+          }
+        };
+      }
+
+      @Override
+      public boolean needsScores() {
+        return true;
+      }
+
+      @Override
+      public int hashCode() {
+        return 0;
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+        return false;
+      }
+
+      @Override
+      public String toString() {
+        return "fn";
+      }
+    };
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/1a278ae8/lucene/suggest/src/test/org/apache/lucene/search/suggest/DocumentValueSourceDictionaryTest.java
----------------------------------------------------------------------
diff --git a/lucene/suggest/src/test/org/apache/lucene/search/suggest/DocumentValueSourceDictionaryTest.java b/lucene/suggest/src/test/org/apache/lucene/search/suggest/DocumentValueSourceDictionaryTest.java
index 55970e4..fb91e20 100644
--- a/lucene/suggest/src/test/org/apache/lucene/search/suggest/DocumentValueSourceDictionaryTest.java
+++ b/lucene/suggest/src/test/org/apache/lucene/search/suggest/DocumentValueSourceDictionaryTest.java
@@ -171,6 +171,21 @@ public class DocumentValueSourceDictionaryTest extends LuceneTestCase {
       public boolean needsScores() {
         return false;
       }
+
+      @Override
+      public int hashCode() {
+        return 0;
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+        return false;
+      }
+
+      @Override
+      public String toString() {
+        return null;
+      }
     };
   }