diff --git a/CHANGES.txt b/CHANGES.txt index f66794655c1..b248ad637f4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -29,7 +29,12 @@ API Changes 4. LUCENE-608: Document.fields() has been deprecated and a new method Document.getFields() has been added that returns a List instead of an Enumeration (Daniel Naber) - + + 5. LUCENE-605: New Explanation.isMatch() method and new ComplexExplanation + subclass allows explain methods to produce Explanations which model + "matching" independent of having a positive value. + (Chris Hostetter) + Bug fixes 1. Fixed the web application demo (built with "ant war-demo") which @@ -60,7 +65,11 @@ Bug fixes 9. LUCENE-610,LUCENE-611: Simple syntax changes to allow compilation with ecj: disambiguate inner class scorer's use of doc() in BooleanScorer2, other test code changes. (DM Smith via Yonik Seeley) - + +10. LUCENE-451: All core query types now use ComplexExplanations so that + boosts of zero don't confuse the BooleanWeight explain method. + (Chris Hostetter) + Optimizations 1. LUCENE-586: TermDocs.skipTo() is now more efficient for multi-segment diff --git a/src/java/org/apache/lucene/search/BooleanQuery.java b/src/java/org/apache/lucene/search/BooleanQuery.java index 6271f690aea..4a20c52658a 100644 --- a/src/java/org/apache/lucene/search/BooleanQuery.java +++ b/src/java/org/apache/lucene/search/BooleanQuery.java @@ -263,7 +263,7 @@ public class BooleanQuery extends Query { throws IOException { final int minShouldMatch = BooleanQuery.this.getMinimumNumberShouldMatch(); - Explanation sumExpl = new Explanation(); + ComplexExplanation sumExpl = new ComplexExplanation(); sumExpl.setDescription("sum of:"); int coord = 0; int maxCoord = 0; @@ -275,14 +275,14 @@ public class BooleanQuery extends Query { Weight w = (Weight)weights.elementAt(i); Explanation e = w.explain(reader, doc); if (!c.isProhibited()) maxCoord++; - if (e.getValue() > 0) { + if (e.isMatch()) { if (!c.isProhibited()) { sumExpl.addDetail(e); sum += e.getValue(); coord++; } else { Explanation r = - new Explanation(0.0f, "match on prohibited clause"); + new Explanation(0.0f, "match on prohibited clause (" + c.getQuery().toString() + ")"); r.addDetail(e); sumExpl.addDetail(r); fail = true; @@ -290,36 +290,39 @@ public class BooleanQuery extends Query { if (c.getOccur().equals(Occur.SHOULD)) shouldMatchCount++; } else if (c.isRequired()) { - Explanation r = new Explanation(0.0f, "no match on required clause"); + Explanation r = new Explanation(0.0f, "no match on required clause (" + c.getQuery().toString() + ")"); r.addDetail(e); sumExpl.addDetail(r); fail = true; } } if (fail) { + sumExpl.setMatch(Boolean.FALSE); sumExpl.setValue(0.0f); sumExpl.setDescription ("Failure to meet condition(s) of required/prohibited clause(s)"); return sumExpl; } else if (shouldMatchCount < minShouldMatch) { + sumExpl.setMatch(Boolean.FALSE); sumExpl.setValue(0.0f); sumExpl.setDescription("Failure to match minimum number "+ "of optional clauses: " + minShouldMatch); return sumExpl; } + sumExpl.setMatch(0 < coord ? Boolean.TRUE : Boolean.FALSE); sumExpl.setValue(sum); float coordFactor = similarity.coord(coord, maxCoord); if (coordFactor == 1.0f) // coord is no-op return sumExpl; // eliminate wrapper else { - Explanation result = new Explanation(); - result.setDescription("product of:"); + ComplexExplanation result = new ComplexExplanation(sumExpl.isMatch(), + sum*coordFactor, + "product of:"); result.addDetail(sumExpl); result.addDetail(new Explanation(coordFactor, "coord("+coord+"/"+maxCoord+")")); - result.setValue(sum*coordFactor); return result; } } diff --git a/src/java/org/apache/lucene/search/ComplexExplanation.java b/src/java/org/apache/lucene/search/ComplexExplanation.java new file mode 100644 index 00000000000..cd54aa44944 --- /dev/null +++ b/src/java/org/apache/lucene/search/ComplexExplanation.java @@ -0,0 +1,69 @@ +package org.apache.lucene.search; + +/** + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.ArrayList; + +/** Expert: Describes the score computation for document and query, andcan distinguish a match independent of a positive value. */ +public class ComplexExplanation extends Explanation { + private Boolean match; + + public ComplexExplanation() { + super(); + } + + public ComplexExplanation(boolean match, float value, String description) { + // NOTE: use of "boolean" instead of "Boolean" in params is concious + // choice to encourage clients to be specific. + super(value, description); + this.match = Boolean.valueOf(match); + } + + /** + * The match status of this explanation node. + * @return May be null if match status is unknown + */ + public Boolean getMatch() { return match; } + /** + * Sets the match status assigned to this explanation node. + * @param match May be null if match status is unknown + */ + public void setMatch(Boolean match) { this.match = match; } + /** + * Indicates wether or not this Explanation models a good match. + * + *

+ * If the match statis is explicitly set (ie: not null) this method + * uses it; otherwise it defers to the superclass. + *

+ * @see #getMatch + */ + public boolean isMatch() { + Boolean m = getMatch(); + return (null != m ? m.booleanValue() : super.isMatch()); + } + + protected String getSummary() { + if (null == getMatch()) + return super.getSummary(); + + return getValue() + " = " + + (isMatch() ? "(MATCH) " : "(NON-MATCH) ") + + getDescription(); + } + +} diff --git a/src/java/org/apache/lucene/search/ConstantScoreQuery.java b/src/java/org/apache/lucene/search/ConstantScoreQuery.java index 2d063908eaf..a5ecfb7fb41 100644 --- a/src/java/org/apache/lucene/search/ConstantScoreQuery.java +++ b/src/java/org/apache/lucene/search/ConstantScoreQuery.java @@ -81,18 +81,20 @@ public class ConstantScoreQuery extends Query { ConstantScorer cs = (ConstantScorer)scorer(reader); boolean exists = cs.bits.get(doc); - Explanation result = new Explanation(); + ComplexExplanation result = new ComplexExplanation(); if (exists) { result.setDescription("ConstantScoreQuery(" + filter + "), product of:"); result.setValue(queryWeight); + result.setMatch(Boolean.TRUE); result.addDetail(new Explanation(getBoost(), "boost")); result.addDetail(new Explanation(queryNorm,"queryNorm")); } else { result.setDescription("ConstantScoreQuery(" + filter + ") doesn't match id " + doc); result.setValue(0); + result.setMatch(Boolean.FALSE); } return result; } diff --git a/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java b/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java index 9a32728d2a0..c8427eb52f3 100644 --- a/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java +++ b/src/java/org/apache/lucene/search/DisjunctionMaxQuery.java @@ -138,12 +138,13 @@ public class DisjunctionMaxQuery extends Query { /* Explain the score we computed for doc */ public Explanation explain(IndexReader reader, int doc) throws IOException { if ( disjuncts.size() == 1) return ((Weight) weights.get(0)).explain(reader,doc); - Explanation result = new Explanation(); + ComplexExplanation result = new ComplexExplanation(); float max = 0.0f, sum = 0.0f; result.setDescription(tieBreakerMultiplier == 0.0f ? "max of:" : "max plus " + tieBreakerMultiplier + " times others of:"); for (int i = 0 ; i < weights.size(); i++) { Explanation e = ((Weight) weights.get(i)).explain(reader, doc); - if (e.getValue() > 0) { + if (e.isMatch()) { + result.setMatch(Boolean.TRUE); result.addDetail(e); sum += e.getValue(); max = Math.max(max, e.getValue()); diff --git a/src/java/org/apache/lucene/search/Explanation.java b/src/java/org/apache/lucene/search/Explanation.java index 1950bc24b56..058eeb3b193 100644 --- a/src/java/org/apache/lucene/search/Explanation.java +++ b/src/java/org/apache/lucene/search/Explanation.java @@ -31,6 +31,20 @@ public class Explanation implements java.io.Serializable { this.description = description; } + /** + * Indicates wether or not this Explanation models a good match. + * + *

+ * By default, an Explanation represents a "match" if the value is positive. + *

+ * @see #getValue + */ + public boolean isMatch() { + return (0.0f < getValue()); + } + + + /** The value assigned to this explanation node. */ public float getValue() { return value; } /** Sets the value assigned to this explanation node. */ @@ -43,6 +57,14 @@ public class Explanation implements java.io.Serializable { this.description = description; } + /** + * A short one line summary which should contain all high level + * information about this Explanation, without the "Details" + */ + protected String getSummary() { + return getValue() + " = " + getDescription(); + } + /** The sub-nodes of this explanation node. */ public Explanation[] getDetails() { if (details == null) @@ -61,14 +83,12 @@ public class Explanation implements java.io.Serializable { public String toString() { return toString(0); } - private String toString(int depth) { + protected String toString(int depth) { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < depth; i++) { buffer.append(" "); } - buffer.append(getValue()); - buffer.append(" = "); - buffer.append(getDescription()); + buffer.append(getSummary()); buffer.append("\n"); Explanation[] details = getDetails(); @@ -88,9 +108,7 @@ public class Explanation implements java.io.Serializable { buffer.append("