pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / contrib / facet / src / java / org / apache / lucene / facet / enhancements / association / AssociationsPayloadIterator.java
1 package org.apache.lucene.facet.enhancements.association;
2
3 import java.io.IOException;
4
5 import org.apache.lucene.index.IndexReader;
6 import org.apache.lucene.index.Term;
7
8 import org.apache.lucene.facet.index.params.CategoryListParams;
9 import org.apache.lucene.facet.search.PayloadIntDecodingIterator;
10 import org.apache.lucene.util.collections.IntIterator;
11 import org.apache.lucene.util.collections.IntToIntMap;
12 import org.apache.lucene.util.encoding.SimpleIntDecoder;
13
14 /**
15  * Licensed to the Apache Software Foundation (ASF) under one or more
16  * contributor license agreements.  See the NOTICE file distributed with
17  * this work for additional information regarding copyright ownership.
18  * The ASF licenses this file to You under the Apache License, Version 2.0
19  * (the "License"); you may not use this file except in compliance with
20  * the License.  You may obtain a copy of the License at
21  *
22  *     http://www.apache.org/licenses/LICENSE-2.0
23  *
24  * Unless required by applicable law or agreed to in writing, software
25  * distributed under the License is distributed on an "AS IS" BASIS,
26  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27  * See the License for the specific language governing permissions and
28  * limitations under the License.
29  */
30
31 /**
32  * Allows easy iteration over the associations payload, decoding and breaking it
33  * to (ordinal, value) pairs, stored in a hash.
34  * 
35  * @lucene.experimental
36  */
37 public class AssociationsPayloadIterator {
38
39   /**
40    * Default Term for associations
41    */
42   public static final Term ASSOCIATION_POSTING_TERM = new Term(
43       CategoryListParams.DEFAULT_TERM.field(),
44       AssociationEnhancement.CATEGORY_LIST_TERM_TEXT);
45
46   /**
47    * Hash mapping to ordinals to the associated int value
48    */
49   private IntToIntMap ordinalToAssociationMap;
50
51   /**
52    * An inner payload decoder which actually goes through the posting and
53    * decode the ints representing the ordinals and the values
54    */
55   private PayloadIntDecodingIterator associationPayloadIter;
56
57   /**
58    * Marking whether there are associations (at all) in the given index
59    */
60   private boolean hasAssociations = false;
61
62   /**
63    * The long-special-value returned for ordinals which have no associated int
64    * value. It is not in the int range of values making it a valid mark.
65    */
66   public final static long NO_ASSOCIATION = Integer.MAX_VALUE + 1;
67
68   /**
69    * Construct a new association-iterator, initializing the inner payload
70    * iterator, with the supplied term and checking whether there are any
71    * associations within the given index
72    * 
73    * @param reader
74    *            a reader containing the postings to be iterated
75    * @param field
76    *            the field containing the relevant associations list term
77    */
78   public AssociationsPayloadIterator(IndexReader reader, String field)
79       throws IOException {
80     // Initialize the payloadDecodingIterator
81     associationPayloadIter = new PayloadIntDecodingIterator(
82         reader,
83         // TODO (Facet): should consolidate with AssociationListTokenizer which
84         // uses AssociationEnhancement.getCatTermText()
85         new Term(field, AssociationEnhancement.CATEGORY_LIST_TERM_TEXT),
86         new SimpleIntDecoder());
87
88     // Check whether there are any associations
89     hasAssociations = associationPayloadIter.init();
90
91     ordinalToAssociationMap = new IntToIntMap();
92   }
93
94   /**
95    * Skipping to the next document, fetching its associations & populating the
96    * map.
97    * 
98    * @param docId
99    *            document id to be skipped to
100    * @return true if the document contains associations and they were fetched
101    *         correctly. false otherwise.
102    * @throws IOException
103    *             on error
104    */
105   public boolean setNextDoc(int docId) throws IOException {
106     ordinalToAssociationMap.clear();
107     boolean docContainsAssociations = false;
108     try {
109       docContainsAssociations = fetchAssociations(docId);
110     } catch (IOException e) {
111       IOException ioe = new IOException(
112           "An Error occured while reading a document's associations payload (docId="
113               + docId + ")");
114       ioe.initCause(e);
115       throw ioe;
116     }
117
118     return docContainsAssociations;
119   }
120
121   /**
122    * Get int association value for the given ordinal. <br>
123    * The return is either an int value casted as long if the ordinal has an
124    * associated value. Otherwise the returned value would be
125    * {@link #NO_ASSOCIATION} which is 'pure long' value (e.g not in the int
126    * range of values)
127    * 
128    * @param ordinal
129    *            for which the association value is requested
130    * @return the associated int value (encapsulated in a long) if the ordinal
131    *         had an associated value, or {@link #NO_ASSOCIATION} otherwise
132    */
133   public long getAssociation(int ordinal) {
134     if (ordinalToAssociationMap.containsKey(ordinal)) {
135       return ordinalToAssociationMap.get(ordinal);
136     }
137
138     return NO_ASSOCIATION;
139   }
140
141   /**
142    * Get an iterator over the ordinals which has an association for the
143    * document set by {@link #setNextDoc(int)}.
144    */
145   public IntIterator getAssociatedOrdinals() {
146     return ordinalToAssociationMap.keyIterator();
147   }
148
149   /**
150    * Skips to the given docId, getting the values in pairs of (ordinal, value)
151    * and populating the map
152    * 
153    * @param docId
154    *            document id owning the associations
155    * @return true if associations were fetched successfully, false otherwise
156    * @throws IOException
157    *             on error
158    */
159   private boolean fetchAssociations(int docId) throws IOException {
160     // No associations at all? don't bother trying to seek the docID in the
161     // posting
162     if (!hasAssociations) {
163       return false;
164     }
165
166     // No associations for this document? well, nothing to decode than,
167     // return false
168     if (!associationPayloadIter.skipTo(docId)) {
169       return false;
170     }
171
172     // loop over all the values decoded from the payload in pairs.
173     for (;;) {
174       // Get the ordinal
175       long ordinal = associationPayloadIter.nextCategory();
176
177       // if no ordinal - it's the end of data, break the loop
178       if (ordinal > Integer.MAX_VALUE) {
179         break;
180       }
181
182       // get the associated value
183       long association = associationPayloadIter.nextCategory();
184       // If we're at this step - it means we have an ordinal, do we have
185       // an association for it?
186       if (association > Integer.MAX_VALUE) {
187         // No association!!! A Broken Pair!! PANIC!
188         throw new IOException(
189             "ERROR! Associations should come in pairs of (ordinal, value), yet this payload has an odd number of values! (docId="
190                 + docId + ")");
191       }
192       // Populate the map with the given ordinal and association pair
193       ordinalToAssociationMap.put((int) ordinal, (int) association);
194     }
195
196     return true;
197   }
198
199   @Override
200   public int hashCode() {
201     final int prime = 31;
202     int result = 1;
203     result = prime
204         * result
205         + ((associationPayloadIter == null) ? 0
206             : associationPayloadIter.hashCode());
207     return result;
208   }
209
210   @Override
211   public boolean equals(Object obj) {
212     if (this == obj) {
213       return true;
214     }
215     
216     if (obj == null) {
217       return false;
218     }
219     
220     if (getClass() != obj.getClass()) {
221       return false;
222     }
223     
224     AssociationsPayloadIterator other = (AssociationsPayloadIterator) obj;
225     if (associationPayloadIter == null) {
226       if (other.associationPayloadIter != null) {
227         return false;
228       }
229     } else if (!associationPayloadIter.equals(other.associationPayloadIter)) {
230       return false;
231     }
232     return true;
233   }
234
235 }