View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.search.lucene.query;
25  
26  import java.io.IOException;
27  import org.apache.lucene.document.Document;
28  import org.apache.lucene.document.FieldSelector;
29  import org.apache.lucene.document.FieldSelectorResult;
30  import org.apache.lucene.index.IndexReader;
31  import org.apache.lucene.search.Explanation;
32  import org.apache.lucene.search.Query;
33  import org.apache.lucene.search.Scorer;
34  import org.apache.lucene.search.Searcher;
35  import org.apache.lucene.search.Similarity;
36  import org.apache.lucene.search.Weight;
37  
38  /**
39   * A Lucene {@link Query} implementation that is satisfied if there is at least one value for a document.
40   */
41  public class HasValueQuery extends Query {
42  
43      private static final long serialVersionUID = 1L;
44  
45      protected final String fieldName;
46      protected final FieldSelector fieldSelector;
47  
48      /**
49       * Construct a {@link Query} implementation that scores nodes according to the supplied comparator.
50       * 
51       * @param fieldName the name of the document field containing the value; may not be null
52       */
53      public HasValueQuery( final String fieldName ) {
54          this.fieldName = fieldName;
55          this.fieldSelector = new FieldSelector() {
56              private static final long serialVersionUID = 1L;
57  
58              public FieldSelectorResult accept( String fieldName ) {
59                  return HasValueQuery.this.fieldName.equals(fieldName) ? FieldSelectorResult.LOAD_AND_BREAK : FieldSelectorResult.NO_LOAD;
60              }
61          };
62      }
63  
64      /**
65       * {@inheritDoc}
66       * 
67       * @see org.apache.lucene.search.Query#clone()
68       */
69      @Override
70      public Object clone() {
71          return new HasValueQuery(fieldName);
72      }
73  
74      /**
75       * {@inheritDoc}
76       * 
77       * @see org.apache.lucene.search.Query#createWeight(org.apache.lucene.search.Searcher)
78       */
79      @Override
80      public Weight createWeight( Searcher searcher ) {
81          return new ExistsWeight(searcher);
82      }
83  
84      /**
85       * {@inheritDoc}
86       * 
87       * @see org.apache.lucene.search.Query#toString(java.lang.String)
88       */
89      @Override
90      public String toString( String field ) {
91          return fieldName + " exists";
92      }
93  
94      protected boolean hasValue( IndexReader reader,
95                                  int docId ) throws IOException {
96          Document doc = reader.document(docId, fieldSelector);
97          String valueString = doc.get(fieldName);
98          return valueString != null;
99      }
100 
101     /**
102      * Calculates query weights and builds query scores for our NOT queries.
103      */
104     protected class ExistsWeight extends Weight {
105         private static final long serialVersionUID = 1L;
106         private final Searcher searcher;
107 
108         protected ExistsWeight( Searcher searcher ) {
109             this.searcher = searcher;
110             assert this.searcher != null;
111         }
112 
113         /**
114          * {@inheritDoc}
115          * 
116          * @see org.apache.lucene.search.Weight#getQuery()
117          */
118         @Override
119         public Query getQuery() {
120             return HasValueQuery.this;
121         }
122 
123         /**
124          * {@inheritDoc}
125          * <p>
126          * This implementation always returns a weight factor of 1.0.
127          * </p>
128          * 
129          * @see org.apache.lucene.search.Weight#getValue()
130          */
131         @Override
132         public float getValue() {
133             return 1.0f; // weight factor of 1.0
134         }
135 
136         /**
137          * {@inheritDoc}
138          * <p>
139          * This implementation always returns a normalization factor of 1.0.
140          * </p>
141          * 
142          * @see org.apache.lucene.search.Weight#sumOfSquaredWeights()
143          */
144         @Override
145         public float sumOfSquaredWeights() {
146             return 1.0f; // normalization factor of 1.0
147         }
148 
149         /**
150          * {@inheritDoc}
151          * <p>
152          * This implementation always does nothing, as there is nothing to normalize.
153          * </p>
154          * 
155          * @see org.apache.lucene.search.Weight#normalize(float)
156          */
157         @Override
158         public void normalize( float norm ) {
159             // No need to do anything here
160         }
161 
162         /**
163          * {@inheritDoc}
164          * 
165          * @see org.apache.lucene.search.Weight#scorer(org.apache.lucene.index.IndexReader, boolean, boolean)
166          */
167         @Override
168         public Scorer scorer( IndexReader reader,
169                               boolean scoreDocsInOrder,
170                               boolean topScorer ) {
171             // Return a custom scorer ...
172             return new ExistsScorer(reader);
173         }
174 
175         /**
176          * {@inheritDoc}
177          * 
178          * @see org.apache.lucene.search.Weight#explain(org.apache.lucene.index.IndexReader, int)
179          */
180         @Override
181         public Explanation explain( IndexReader reader,
182                                     int doc ) {
183             return new Explanation(getValue(), getQuery().toString());
184         }
185     }
186 
187     /**
188      * A scorer for the Path query.
189      */
190     protected class ExistsScorer extends Scorer {
191         private int docId = -1;
192         private final int pastMaxDocId;
193         private final IndexReader reader;
194 
195         protected ExistsScorer( IndexReader reader ) {
196             // We don't care which Similarity we have, because we don't use it. So get the default.
197             super(Similarity.getDefault());
198             this.reader = reader;
199             assert this.reader != null;
200             this.pastMaxDocId = this.reader.maxDoc();
201         }
202 
203         /**
204          * {@inheritDoc}
205          * 
206          * @see org.apache.lucene.search.DocIdSetIterator#docID()
207          */
208         @Override
209         public int docID() {
210             return docId;
211         }
212 
213         /**
214          * {@inheritDoc}
215          * 
216          * @see org.apache.lucene.search.DocIdSetIterator#nextDoc()
217          */
218         @Override
219         public int nextDoc() throws IOException {
220             do {
221                 ++docId;
222                 if (docId == pastMaxDocId) return Scorer.NO_MORE_DOCS;
223                 if (reader.isDeleted(docId)) {
224                     // We should skip this document ...
225                     continue;
226                 }
227                 if (hasValue(reader, docId)) return docId;
228             } while (true);
229         }
230 
231         /**
232          * {@inheritDoc}
233          * 
234          * @see org.apache.lucene.search.DocIdSetIterator#advance(int)
235          */
236         @Override
237         public int advance( int target ) throws IOException {
238             if (target == Scorer.NO_MORE_DOCS) return target;
239             while (true) {
240                 int doc = nextDoc();
241                 if (doc >= target) return doc;
242             }
243         }
244 
245         /**
246          * {@inheritDoc}
247          * <p>
248          * This method always returns a score of 1.0 for the current document, since only those documents that satisfy the NOT are
249          * scored by this scorer.
250          * </p>
251          * 
252          * @see org.apache.lucene.search.Scorer#score()
253          */
254         @Override
255         public float score() {
256             return 1.0f;
257         }
258     }
259 }