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
diff --git a/lucene-java-3.5.0/lucene/contrib/facet/src/java/org/apache/lucene/facet/enhancements/association/AssociationsPayloadIterator.java b/lucene-java-3.5.0/lucene/contrib/facet/src/java/org/apache/lucene/facet/enhancements/association/AssociationsPayloadIterator.java
new file mode 100644 (file)
index 0000000..bae9a41
--- /dev/null
@@ -0,0 +1,235 @@
+package org.apache.lucene.facet.enhancements.association;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+
+import org.apache.lucene.facet.index.params.CategoryListParams;
+import org.apache.lucene.facet.search.PayloadIntDecodingIterator;
+import org.apache.lucene.util.collections.IntIterator;
+import org.apache.lucene.util.collections.IntToIntMap;
+import org.apache.lucene.util.encoding.SimpleIntDecoder;
+
+/**
+ * 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.
+ */
+
+/**
+ * Allows easy iteration over the associations payload, decoding and breaking it
+ * to (ordinal, value) pairs, stored in a hash.
+ * 
+ * @lucene.experimental
+ */
+public class AssociationsPayloadIterator {
+
+  /**
+   * Default Term for associations
+   */
+  public static final Term ASSOCIATION_POSTING_TERM = new Term(
+      CategoryListParams.DEFAULT_TERM.field(),
+      AssociationEnhancement.CATEGORY_LIST_TERM_TEXT);
+
+  /**
+   * Hash mapping to ordinals to the associated int value
+   */
+  private IntToIntMap ordinalToAssociationMap;
+
+  /**
+   * An inner payload decoder which actually goes through the posting and
+   * decode the ints representing the ordinals and the values
+   */
+  private PayloadIntDecodingIterator associationPayloadIter;
+
+  /**
+   * Marking whether there are associations (at all) in the given index
+   */
+  private boolean hasAssociations = false;
+
+  /**
+   * The long-special-value returned for ordinals which have no associated int
+   * value. It is not in the int range of values making it a valid mark.
+   */
+  public final static long NO_ASSOCIATION = Integer.MAX_VALUE + 1;
+
+  /**
+   * Construct a new association-iterator, initializing the inner payload
+   * iterator, with the supplied term and checking whether there are any
+   * associations within the given index
+   * 
+   * @param reader
+   *            a reader containing the postings to be iterated
+   * @param field
+   *            the field containing the relevant associations list term
+   */
+  public AssociationsPayloadIterator(IndexReader reader, String field)
+      throws IOException {
+    // Initialize the payloadDecodingIterator
+    associationPayloadIter = new PayloadIntDecodingIterator(
+        reader,
+        // TODO (Facet): should consolidate with AssociationListTokenizer which
+        // uses AssociationEnhancement.getCatTermText()
+        new Term(field, AssociationEnhancement.CATEGORY_LIST_TERM_TEXT),
+        new SimpleIntDecoder());
+
+    // Check whether there are any associations
+    hasAssociations = associationPayloadIter.init();
+
+    ordinalToAssociationMap = new IntToIntMap();
+  }
+
+  /**
+   * Skipping to the next document, fetching its associations & populating the
+   * map.
+   * 
+   * @param docId
+   *            document id to be skipped to
+   * @return true if the document contains associations and they were fetched
+   *         correctly. false otherwise.
+   * @throws IOException
+   *             on error
+   */
+  public boolean setNextDoc(int docId) throws IOException {
+    ordinalToAssociationMap.clear();
+    boolean docContainsAssociations = false;
+    try {
+      docContainsAssociations = fetchAssociations(docId);
+    } catch (IOException e) {
+      IOException ioe = new IOException(
+          "An Error occured while reading a document's associations payload (docId="
+              + docId + ")");
+      ioe.initCause(e);
+      throw ioe;
+    }
+
+    return docContainsAssociations;
+  }
+
+  /**
+   * Get int association value for the given ordinal. <br>
+   * The return is either an int value casted as long if the ordinal has an
+   * associated value. Otherwise the returned value would be
+   * {@link #NO_ASSOCIATION} which is 'pure long' value (e.g not in the int
+   * range of values)
+   * 
+   * @param ordinal
+   *            for which the association value is requested
+   * @return the associated int value (encapsulated in a long) if the ordinal
+   *         had an associated value, or {@link #NO_ASSOCIATION} otherwise
+   */
+  public long getAssociation(int ordinal) {
+    if (ordinalToAssociationMap.containsKey(ordinal)) {
+      return ordinalToAssociationMap.get(ordinal);
+    }
+
+    return NO_ASSOCIATION;
+  }
+
+  /**
+   * Get an iterator over the ordinals which has an association for the
+   * document set by {@link #setNextDoc(int)}.
+   */
+  public IntIterator getAssociatedOrdinals() {
+    return ordinalToAssociationMap.keyIterator();
+  }
+
+  /**
+   * Skips to the given docId, getting the values in pairs of (ordinal, value)
+   * and populating the map
+   * 
+   * @param docId
+   *            document id owning the associations
+   * @return true if associations were fetched successfully, false otherwise
+   * @throws IOException
+   *             on error
+   */
+  private boolean fetchAssociations(int docId) throws IOException {
+    // No associations at all? don't bother trying to seek the docID in the
+    // posting
+    if (!hasAssociations) {
+      return false;
+    }
+
+    // No associations for this document? well, nothing to decode than,
+    // return false
+    if (!associationPayloadIter.skipTo(docId)) {
+      return false;
+    }
+
+    // loop over all the values decoded from the payload in pairs.
+    for (;;) {
+      // Get the ordinal
+      long ordinal = associationPayloadIter.nextCategory();
+
+      // if no ordinal - it's the end of data, break the loop
+      if (ordinal > Integer.MAX_VALUE) {
+        break;
+      }
+
+      // get the associated value
+      long association = associationPayloadIter.nextCategory();
+      // If we're at this step - it means we have an ordinal, do we have
+      // an association for it?
+      if (association > Integer.MAX_VALUE) {
+        // No association!!! A Broken Pair!! PANIC!
+        throw new IOException(
+            "ERROR! Associations should come in pairs of (ordinal, value), yet this payload has an odd number of values! (docId="
+                + docId + ")");
+      }
+      // Populate the map with the given ordinal and association pair
+      ordinalToAssociationMap.put((int) ordinal, (int) association);
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime
+        * result
+        + ((associationPayloadIter == null) ? 0
+            : associationPayloadIter.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    
+    if (obj == null) {
+      return false;
+    }
+    
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+    
+    AssociationsPayloadIterator other = (AssociationsPayloadIterator) obj;
+    if (associationPayloadIter == null) {
+      if (other.associationPayloadIter != null) {
+        return false;
+      }
+    } else if (!associationPayloadIter.equals(other.associationPayloadIter)) {
+      return false;
+    }
+    return true;
+  }
+
+}