/* * Copyright (c) 2011 LinkedIn, Inc * * Licensed 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. */ package com.flaptor.indextank.index.scorer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.flaptor.indextank.index.DocId; import com.flaptor.indextank.index.scorer.CategoryMaskManager.CategoryInfo; import com.flaptor.indextank.index.scorer.DynamicDataManager.DynamicData; import com.flaptor.indextank.index.scorer.DynamicDataManager.FacetsCollector; import com.flaptor.indextank.query.QueryVariables; import com.google.common.collect.HashMultiset; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.ObjectArrays; public class DynamicDataFacetingManager extends FacetingManager { private final DynamicDataManager dynamicDataManager; public class DynamicDataFacetFilter implements MatchFilter { private List<CategoryFilter> matchingCategories = new ArrayList<CategoryFilter>(); private int dataOffset; //TODO: optimize to avoid searching the index when true private boolean isNoneMatchingFilter = false; private class CategoryFilter { public int[] bitmask; public int[][] values; public CategoryFilter(int[] bitmask, int[][] values) { this.bitmask = bitmask; this.values = values; } } public DynamicDataFacetFilter(Multimap<String, String> filteringFacets) { CategoryMaskManager maskManager = dynamicDataManager.getMaskManager(); int maxMaskSize = maskManager.getMaxMaskSize(); dataOffset = dynamicDataManager.getNumberOfBoosts() + 1; if (maxMaskSize == 0) { if (filteringFacets.size() == 0) { return; } else { isNoneMatchingFilter = true; return; //throw new IllegalArgumentException("Facets can't be filtered if no facets are defined"); } } if (filteringFacets.values().size() == filteringFacets.keySet().size()) { // Only one value per category optimization int[] totalMask = new int[maxMaskSize]; int[] totalValue = new int[maxMaskSize]; for (Entry<String, String> entry : filteringFacets.entries()) { CategoryInfo categoryInfo = maskManager.getCategoryInfos().get(entry.getKey()); if (categoryInfo == null) { isNoneMatchingFilter = true; return; } int[] bitmask = categoryInfo.getBitmask(); Integer valueCode = categoryInfo.getValueCode(entry.getValue()); if (valueCode == null) { isNoneMatchingFilter = true; return; } CategoryEncoder.encode(totalValue, 0, bitmask, valueCode); orArrays(bitmask, totalMask); } matchingCategories.add(new CategoryFilter(totalMask, new int[][] { totalValue } )); } else { for (String categoryKey : filteringFacets.keySet()) { CategoryInfo categoryInfo = maskManager.getCategoryInfos().get(categoryKey); if (categoryInfo == null) { isNoneMatchingFilter = true; return; } int[] bitmask = categoryInfo.getBitmask(); Collection<String> values = filteringFacets.get(categoryKey); List<int[]> valuesBitmaps = Lists.newArrayList(); for (String value : values) { Integer valueCode = categoryInfo.getValueCode(value); if (valueCode != null) { int[] encodedValue = CategoryEncoder.encode(new int[0], 0, bitmask, valueCode); valuesBitmaps.add(encodedValue); } } if (valuesBitmaps.isEmpty()) { isNoneMatchingFilter = true; return; } matchingCategories.add(new CategoryFilter(bitmask, valuesBitmaps.toArray(new int[valuesBitmaps.size()][]))); } } } private void orArrays(int[] source, int[] target) { for (int i = 0; i < source.length; i++) { target[i] |= source[i]; } } @Override public boolean matches(DocId documentId, double textualScore, int now, QueryVariables queryVars) { if (isNoneMatchingFilter) { return false; } DynamicData dynamicData = dynamicDataManager.getDynamicData(documentId); int[] data = dynamicData.getData(); /* * For every category we checked that at least one of the * values matches. */ for (CategoryFilter categoryFilter : matchingCategories) { int[][] values = categoryFilter.values; boolean matched = false; for (int i = 0; i < values.length; i++) { if (CategoryEncoder.matches(data, dataOffset, categoryFilter.bitmask, values[i])) { matched = true; break; } } if (!matched) { return false; } } return true; } } public DynamicDataFacetingManager(DynamicDataManager dynamicDataManager) { this.dynamicDataManager = dynamicDataManager; } public class DynamicDataFaceter implements Faceter, FacetsCollector { private Map<String, int[]> rawFacets = Maps.newHashMap(); @Override public void computeDocument(DocId documentId) { dynamicDataManager.populateCollector(documentId, this); } @Override public Map<String, Multiset<String>> getFacets() { CategoryMaskManager maskManager = dynamicDataManager.getMaskManager(); Map<String, Multiset<String>> results = Maps.newHashMap(); for (Entry<String, int[]> entry : rawFacets.entrySet()) { Multiset<String> counts = HashMultiset.create(); String category = entry.getKey(); CategoryInfo categoryInfo = maskManager.getCategoryInfos().get(category); int[] rawCounts = entry.getValue(); for (int i = 0; i < rawCounts.length; i++) { String value = categoryInfo.getValue(i+1); counts.add(value, rawCounts[i]); } results.put(category, counts); } return results; } @Override public void addCategoryValue(String category, Integer valueCode) { int[] categorySet = rawFacets.get(category); if (categorySet == null) { categorySet = new int[dynamicDataManager.getMaskManager().getCategoryInfos().get(category).getSize()]; rawFacets.put(category, categorySet); } categorySet[valueCode - 1]++; } } @Override public Faceter createFaceter() { return new DynamicDataFaceter(); } @Override public MatchFilter getFacetFilter(Multimap<String, String> filteringFacets) { return new DynamicDataFacetFilter(filteringFacets); } }