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;
}
}