package com.browseengine.bobo.facets.impl; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import java.io.IOException; import java.util.List; import java.util.Properties; import com.browseengine.bobo.api.BoboSegmentReader; import com.browseengine.bobo.api.BrowseSelection; import com.browseengine.bobo.api.FacetSpec; import com.browseengine.bobo.facets.FacetCountCollector; import com.browseengine.bobo.facets.FacetCountCollectorSource; import com.browseengine.bobo.facets.FacetHandler; import com.browseengine.bobo.facets.data.FacetDataCache; import com.browseengine.bobo.facets.data.MultiValueFacetDataCache; import com.browseengine.bobo.facets.data.TermListFactory; import com.browseengine.bobo.facets.filter.EmptyFilter; import com.browseengine.bobo.facets.filter.FacetOrFilter; import com.browseengine.bobo.facets.filter.FacetValueConverter; import com.browseengine.bobo.facets.filter.MultiValueORFacetFilter; import com.browseengine.bobo.facets.filter.RandomAccessFilter; import com.browseengine.bobo.facets.filter.RandomAccessNotFilter; import com.browseengine.bobo.sort.DocComparatorSource; public class PathFacetHandler extends FacetHandler<FacetDataCache<?>> { private static final String DEFAULT_SEP = "/"; public static final String SEL_PROP_NAME_STRICT = "strict"; public static final String SEL_PROP_NAME_DEPTH = "depth"; private final boolean _multiValue; @SuppressWarnings("rawtypes") private final TermListFactory _termListFactory; private String _separator; private final String _indexedName; public PathFacetHandler(String name) { this(name, false); } public PathFacetHandler(String name, boolean multiValue) { this(name, name, multiValue); } public PathFacetHandler(String name, String indexedName, boolean multiValue) { super(name); _indexedName = indexedName; _multiValue = multiValue; _termListFactory = TermListFactory.StringListFactory; _separator = DEFAULT_SEP; } /** * Sets is strict applied for counting. Used if the field is of type <b><i>path</i></b>. * @param strict is strict applied */ public static void setStrict(Properties props, boolean strict) { props.setProperty(PathFacetHandler.SEL_PROP_NAME_STRICT, String.valueOf(strict)); } /** * Sets the depth. Used if the field is of type <b><i>path</i></b>. * @param depth depth */ public static void setDepth(Properties props, int depth) { props.setProperty(PathFacetHandler.SEL_PROP_NAME_DEPTH, String.valueOf(depth)); } /** * Gets if strict applied for counting. Used if the field is of type <b><i>path</i></b>. * @return is strict applied */ public static boolean isStrict(Properties selectionProp) { try { return Boolean.valueOf(selectionProp.getProperty(PathFacetHandler.SEL_PROP_NAME_STRICT)); } catch (Exception e) { return false; } } @Override public int getNumItems(BoboSegmentReader reader, int id) { FacetDataCache<?> data = getFacetData(reader); if (data == null) return 0; return data.getNumItems(id); } /** * Gets the depth. Used if the field is of type <b><i>path</i></b>. * @return depth */ public static int getDepth(Properties selectionProp) { try { return Integer.parseInt(selectionProp.getProperty(PathFacetHandler.SEL_PROP_NAME_DEPTH)); } catch (Exception e) { return 1; } } @Override public DocComparatorSource getDocComparatorSource() { return new FacetDataCache.FacetDocComparatorSource(this); } @Override public String[] getFieldValues(BoboSegmentReader reader, int id) { FacetDataCache<?> dataCache = getFacetData(reader); if (dataCache == null) return new String[0]; if (_multiValue) { return ((MultiValueFacetDataCache<?>) dataCache)._nestedArray.getTranslatedData(id, dataCache.valArray); } else { return new String[] { dataCache.valArray.get(dataCache.orderArray.get(id)) }; } } @Override public Object[] getRawFieldValues(BoboSegmentReader reader, int id) { return getFieldValues(reader, id); } public void setSeparator(String separator) { _separator = separator; } public String getSeparator() { return _separator; } private static int getPathDepth(String path, String separator) { return path.split(String.valueOf(separator)).length; } private static class PathValueConverter implements FacetValueConverter { private final boolean _strict; private final String _sep; private final int _depth; PathValueConverter(int depth, boolean strict, String sep) { _strict = strict; _sep = sep; _depth = depth; } private void getFilters(FacetDataCache<?> dataCache, IntSet intSet, String[] vals, int depth, boolean strict) { for (String val : vals) { getFilters(dataCache, intSet, val, depth, strict); } } private void getFilters(FacetDataCache<?> dataCache, IntSet intSet, String val, int depth, boolean strict) { List<String> termList = dataCache.valArray; int index = termList.indexOf(val); int startDepth = getPathDepth(val, _sep); if (index < 0) { int nextIndex = -(index + 1); if (nextIndex == termList.size()) { return; } index = nextIndex; } for (int i = index; i < termList.size(); ++i) { String path = termList.get(i); if (path.startsWith(val)) { if (!strict || getPathDepth(path, _sep) - startDepth == depth) { intSet.add(i); } } else { break; } } } @Override public int[] convert(FacetDataCache<String> dataCache, String[] vals) { IntSet intSet = new IntOpenHashSet(); getFilters(dataCache, intSet, vals, _depth, _strict); return intSet.toIntArray(); } } @Override public RandomAccessFilter buildRandomAccessFilter(String value, Properties props) throws IOException { int depth = getDepth(props); boolean strict = isStrict(props); PathValueConverter valConverter = new PathValueConverter(depth, strict, _separator); String[] vals = new String[] { value }; return _multiValue ? new MultiValueORFacetFilter(this, vals, valConverter, false) : new FacetOrFilter(this, vals, false, valConverter); } @Override public RandomAccessFilter buildRandomAccessAndFilter(String[] vals, Properties prop) throws IOException { if (vals.length > 1) { return EmptyFilter.getInstance(); } else { RandomAccessFilter f = buildRandomAccessFilter(vals[0], prop); if (f != null) { return f; } else { return EmptyFilter.getInstance(); } } } @Override public RandomAccessFilter buildRandomAccessOrFilter(String[] vals, Properties prop, boolean isNot) throws IOException { if (vals.length > 1) { if (vals.length > 0) { int depth = getDepth(prop); boolean strict = isStrict(prop); PathValueConverter valConverter = new PathValueConverter(depth, strict, _separator); return _multiValue ? new MultiValueORFacetFilter(this, vals, valConverter, isNot) : new FacetOrFilter(this, vals, isNot, valConverter); } else { if (isNot) { return null; } else { return EmptyFilter.getInstance(); } } } else { RandomAccessFilter f = buildRandomAccessFilter(vals[0], prop); if (f == null) return f; if (isNot) { f = new RandomAccessNotFilter(f); } return f; } } @Override public FacetCountCollectorSource getFacetCountCollectorSource(final BrowseSelection sel, final FacetSpec ospec) { return new FacetCountCollectorSource() { @Override public FacetCountCollector getFacetCountCollector(BoboSegmentReader reader, int docBase) { FacetDataCache<?> dataCache = PathFacetHandler.this.getFacetData(reader); if (_multiValue) { return new MultiValuedPathFacetCountCollector(_name, _separator, sel, ospec, dataCache); } else { return new PathFacetCountCollector(_name, _separator, sel, ospec, dataCache); } } }; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public FacetDataCache<?> load(BoboSegmentReader reader) throws IOException { if (!_multiValue) { FacetDataCache<?> dataCache = new FacetDataCache(); dataCache.load(_indexedName, reader, _termListFactory); return dataCache; } else { MultiValueFacetDataCache<?> dataCache = new MultiValueFacetDataCache(); dataCache.load(_indexedName, reader, _termListFactory); return dataCache; } } }