package org.apache.lucene.facet.index.streaming;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.facet.index.attributes.CategoryAttribute;
import org.apache.lucene.facet.index.attributes.CategoryProperty;
import org.apache.lucene.facet.index.attributes.OrdinalProperty;
import org.apache.lucene.facet.index.categorypolicy.OrdinalPolicy;
import org.apache.lucene.facet.index.categorypolicy.PathPolicy;
import org.apache.lucene.facet.index.params.FacetIndexingParams;
import org.apache.lucene.facet.taxonomy.CategoryPath;
import org.apache.lucene.facet.taxonomy.TaxonomyWriter;
/**
* 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.
*/
/**
* This class adds parents to a {@link CategoryAttributesStream}. The parents
* are added according to the {@link PathPolicy} and {@link OrdinalPolicy} from
* the {@link FacetIndexingParams} given in the constructor.<br>
* By default, category properties are removed when creating parents of a
* certain category. However, it is possible to retain certain property types
* using {@link #addRetainableProperty(Class)}.
*
* @lucene.experimental
*/
public class CategoryParentsStream extends TokenFilter {
/**
* A {@link TaxonomyWriter} for adding categories and retrieving their
* ordinals.
*/
protected TaxonomyWriter taxonomyWriter;
/** An attribute containing all data related to the category */
protected CategoryAttribute categoryAttribute;
/** A category property containing the category ordinal */
protected OrdinalProperty ordinalProperty;
/**
* A set of property classes that are to be retained when creating a parent
* token.
*/
private Set<Class<? extends CategoryProperty>> retainableProperties;
/** A {@link PathPolicy} for the category's parents' category paths. */
private PathPolicy pathPolicy;
/** An {@link OrdinalPolicy} for the category's parents' ordinals. */
private OrdinalPolicy ordinalPolicy;
/**
* Constructor.
*
* @param input
* The input stream to handle, must be derived from
* {@link CategoryAttributesStream}.
* @param taxonomyWriter
* The taxonomy writer to use for adding categories and
* retrieving their ordinals.
* @param indexingParams
* The indexing params used for filtering parents.
*/
public CategoryParentsStream(CategoryAttributesStream input,
TaxonomyWriter taxonomyWriter, FacetIndexingParams indexingParams) {
super(input);
this.categoryAttribute = this.addAttribute(CategoryAttribute.class);
this.taxonomyWriter = taxonomyWriter;
this.pathPolicy = indexingParams.getPathPolicy();
this.ordinalPolicy = indexingParams.getOrdinalPolicy();
this.ordinalPolicy.init(taxonomyWriter);
this.ordinalProperty = new OrdinalProperty();
}
@Override
public final boolean incrementToken() throws IOException {
if (this.categoryAttribute.getCategoryPath() != null) {
// try adding the parent of the current category to the stream
clearCategoryProperties();
boolean added = false;
// set the parent's ordinal, if illegal set -1
int ordinal = this.ordinalProperty.getOrdinal();
if (ordinal != -1) {
ordinal = this.taxonomyWriter.getParent(ordinal);
if (this.ordinalPolicy.shouldAdd(ordinal)) {
this.ordinalProperty.setOrdinal(ordinal);
try {
this.categoryAttribute.addProperty(ordinalProperty);
} catch (UnsupportedOperationException e) {
throw new IOException(e.getLocalizedMessage());
}
added = true;
} else {
this.ordinalProperty.setOrdinal(-1);
}
}
// set the parent's category path, if illegal set null
CategoryPath cp = this.categoryAttribute.getCategoryPath();
if (cp != null) {
cp.trim(1);
// if ordinal added, must also have category paths
if (added || this.pathPolicy.shouldAdd(cp)) {
this.categoryAttribute.setCategoryPath(cp);
added = true;
} else {
this.categoryAttribute.clear();
}
}
if (added) {
// a legal parent exists
return true;
}
}
// no more parents - get new category
if (input.incrementToken()) {
int ordinal = taxonomyWriter.addCategory(this.categoryAttribute.getCategoryPath());
this.ordinalProperty.setOrdinal(ordinal);
try {
this.categoryAttribute.addProperty(this.ordinalProperty);
} catch (UnsupportedOperationException e) {
throw new IOException(e.getLocalizedMessage());
}
return true;
}
return false;
}
/**
* Clear the properties of the current {@link CategoryAttribute} attribute
* before setting the parent attributes. <br>
* It is possible to retain properties of certain types the parent tokens,
* using {@link #addRetainableProperty(Class)}.
*/
protected void clearCategoryProperties() {
if (this.retainableProperties == null
|| this.retainableProperties.isEmpty()) {
this.categoryAttribute.clearProperties();
} else {
List<Class<? extends CategoryProperty>> propertyClassesToRemove =
new LinkedList<Class<? extends CategoryProperty>>();
for (Class<? extends CategoryProperty> propertyClass : this.categoryAttribute
.getPropertyClasses()) {
if (!this.retainableProperties.contains(propertyClass)) {
propertyClassesToRemove.add(propertyClass);
}
}
for (Class<? extends CategoryProperty> propertyClass : propertyClassesToRemove) {
this.categoryAttribute.remove(propertyClass);
}
}
}
/**
* Add a {@link CategoryProperty} class which is retained when creating
* parent tokens.
*
* @param toRetain
* The property class to retain.
*/
public void addRetainableProperty(Class<? extends CategoryProperty> toRetain) {
if (this.retainableProperties == null) {
this.retainableProperties = new HashSet<Class<? extends CategoryProperty>>();
}
this.retainableProperties.add(toRetain);
}
}