diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-search/src/main/java/com/yahoo/search/result/HitGroup.java |
Publish
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/result/HitGroup.java')
-rw-r--r-- | container-search/src/main/java/com/yahoo/search/result/HitGroup.java | 898 |
1 files changed, 898 insertions, 0 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/result/HitGroup.java b/container-search/src/main/java/com/yahoo/search/result/HitGroup.java new file mode 100644 index 00000000000..e58c3dc847e --- /dev/null +++ b/container-search/src/main/java/com/yahoo/search/result/HitGroup.java @@ -0,0 +1,898 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.search.result; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.ListenableFuture; +import com.yahoo.collections.ListenableArrayList; +import com.yahoo.net.URI; +import com.yahoo.processing.response.ArrayDataList; +import com.yahoo.processing.response.DataList; +import com.yahoo.processing.response.DefaultIncomingData; +import com.yahoo.processing.response.IncomingData; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static com.yahoo.collections.CollectionUtil.first; + +/** + * <p>A group of ordered hits. Since hitGroup is itself a kind of Hit, + * this can compose hierarchies of grouped hits.</p> + * + * <p>Group hits has a relevancy just as other hits - they can be ordered + * between each other and in comparison to other hits. + * + * <p>Note that a group is by default a meta hit, but it can also contain its own content + * in addition to subgroup content, in which case it should be set to non-meta.</p> + * + * @author bratseth + */ +public class HitGroup extends Hit implements DataList<Hit>, Cloneable, Iterable<Hit> { + + // This does its own book-keeping of its various state variables + // (see methods towards the end). For state variables which are recursive + // (depending on the state of hits in subgroups), the strategy is to do + // book-keeping on only this immediate level, but not do recursive calls to + // find the true recursive state when queried. This is sort of a middle ground + // between handling the complexity of recursive state book-keeping and the + // query cost of not doing any book-keeping. + // There is also a method, analyse which recursively updates the recursive + // state of the group and all subgroups. This should be called if the hits + // may have changed their own state in a way that may impact the recursive + // state of this. + + private ListenableArrayList<Hit> hits = new ListenableArrayList<>(16); + + transient private List<Hit> unmodifiableHits = Collections.unmodifiableList(hits); + + /** Whether or not the hits are sorted */ + private boolean hitsSorted = true; + + /** Whether or not deletion of hits breaks the sorted ordering */ + private boolean deletionBreaksOrdering = false; + + /** Whether the hits should be sorted (again) */ + private boolean orderedHits = false; + + /** The current number of concrete (non-meta) hits in the result */ + private int concreteHitCount = 0; + + /** The class used to determine the ordering of the hits of this */ + transient private HitOrderer hitOrderer = null; + + /** Accounting the number of subgroups to allow some early returns when the number is 0 */ + private int subgroupCount=0; + + /** + * The number of hits not cached at this level, not counting hits in subgroups or + * any nested hitgroups themselves + */ + private int notCachedCount=0; + + /** + * A direct reference to the errors of this result, or null if there are no errors. + * The error hit will also be listed in the set of this of this result + */ + private ErrorHit errorHit = null; + + private final ListenableFuture<DataList<Hit>> completedFuture; + + private final IncomingData<Hit> incomingHits; + + /** Creates an invalid group of hits. Id must be set before handoff. */ + public HitGroup() { + incomingHits = new IncomingData.NullIncomingData<>(this); + setRelevance(new Relevance(1)); + setMeta(true); + completedFuture = new IncomingData.NullIncomingData.ImmediateFuture<>(this); + } + + /** + * Creates a hit group with max relevancy (1) + * + * @param id the id of this hit - any string, it is convenient to make this unique in the result containing this + */ + public HitGroup(String id) { + this(id,new Relevance(1)); + } + + /** + * Creates a hit group + * + * @param id the id of this hit - any string, it is convenient to make this unique in the result containing this + * @param relevance the relevance of this group of hits, preferably a number between 0 and 1 + */ + public HitGroup(String id,double relevance) { + this(id,new Relevance(relevance)); + } + + /** + * Creates a group hit + * + * @param id the id of this hit - any string, it is convenient to make this unique in the result containing this + * @param relevance the relevancy of this group of hits + */ + public HitGroup(String id, Relevance relevance) { + super(id, relevance); + this.incomingHits = new IncomingData.NullIncomingData<>(this); + setMeta(true); + completedFuture = new IncomingData.NullIncomingData.ImmediateFuture<>(this); + } + + /** + * Creates a group hit + * + * @param id the id of this hit - any string, it is convenient to make this unique in the result containing this + * @param relevance the relevancy of this group of hits + * @param incomingHits the incoming buffer to which new hits can be added asynchronously + */ + protected HitGroup(String id, Relevance relevance, IncomingData<Hit> incomingHits) { + super(id, relevance); + this.incomingHits = incomingHits; + setMeta(true); + completedFuture = new ArrayDataList.DrainOnGetFuture<>(this); + } + + /** + * Creates a HitGroup which contains data which arrives in the future. + * + * @param id the id of this + * @return a HitGroup which is incomplete and which has an {@link #incoming} where new hits can be added later + */ + public static HitGroup createAsync(String id) { + DefaultIncomingData<Hit> incomingData = new DefaultIncomingData<>(); + HitGroup hitGroup = new HitGroup(id, new Relevance(1), incomingData); + incomingData.assignOwner(hitGroup); + return hitGroup; + } + + /** Calls setId(new URI(id)) */ + @Override + public void setId(String id) { + setId(new URI(id)); + } + + /** + * Assign an id to this hit. + * For HitGroups, this is a legal call also when an id is already set, + * i.e hit groups allows their ids to be reassigned. + * This is to allow hit groups to be inserted in new structures with an id reflecting their + * role/placement in the structure. + * + * @param id the new or initial iof of this hit + */ + @Override + public void setId(URI id) { + super.assignId(id); + } + + /** + * Turn off internal resorting of hits. + * + * @param ordered set to true to tell this group that the hits set in it is already correctly ordered and should + * never be resorted. Set to false to use the default lazy resorting by hit ordering. + */ + public void setOrdered(boolean ordered) { this.orderedHits = ordered; } + + /** + * Returns the number of hits available immediately in this group + * (counting a subgroup as one hit). + */ + public int size() { + return hits.size(); + } + + /** + * <p>Returns the number of concrete hits contained in this group + * and all subgroups. This should equal the + * requested hits count if the query has that many matches.</p> + */ + public int getConcreteSize() { + if (subgroupCount<1) return concreteHitCount; + int recursiveConcreteCount=concreteHitCount; + for (Hit hit : hits) { + if (hit instanceof HitGroup) + recursiveConcreteCount+=((HitGroup)hit).getConcreteSize(); + } + return recursiveConcreteCount; + } + + /** + * <p>Returns the number of concrete hits contained in <i>this</i> group, + * without counting hits in subgroups. + */ + public int getConcreteSizeShallow() { return concreteHitCount; } + + /** + * Returns the number of HitGroups present immediately in this list of hits. + */ + public int getSubgroupCount() { return subgroupCount; } + + /** + * Adds a hit to this group. + * If the given hit is an ErrorHit and this group already have an error hit, + * the errors in the given hit are merged into the errors of this. + * + * @return the resulting hit - this is usually the input hit, but if an error hit was added, + * and there was already an error hit present, that hit, containing the merged information + * is returned + */ + @Override + public Hit add(Hit hit) { + if (hit.isMeta() && hit instanceof ErrorHit) { + boolean add = mergeErrors((ErrorHit) hit); + if (!add) return (Hit)errorHit; + } + handleNewHit(hit); + hits.add(hit); + return hit; + } + + /** + * Adds a list of hits to this group, the same + */ + public void addAll(List<Hit> hits) { + for (Hit hit : hits) + add(hit); + } + + /** + * Returns the hit at the given (0-base) index in this group of hit + * (without searching any subgroups). + * + * @param index the index into this list + * @throws IndexOutOfBoundsException if there is no hit at the given index + */ + public Hit get(int index) { + updateHits(); + ensureSorted(); + return hits.get(index); + } + + /** Same as {@link #get(String,int)} */ + public Hit get(String id) { + return get(id,-1); + } + + public Hit get(String id, int depth) { + return get(new URI(id), depth); + } + + /** + * Returns the hit with the given id, or null if there is no hit with this id + * in this group or any subgroup. + * This method is o(min(number of nested hits in this result,depth)). + * + * @param id the id of the hit to return from this or any nested group + * @param depth the max depth to recurse into nested groups: -1: Recurse infinitely deep, 0: Only look at hits in + * the list of this group, 1: Look at hits in this group, and the hits of any immediate nested HitGroups, + * etc. + * @return The hit, or null if not found. + */ + public Hit get(URI id, int depth) { + updateHits(); + for (Iterator<Hit> i = unorderedIterator(); i.hasNext();) { + Hit hit = i.next(); + URI hitUri = hit.getId(); + + if (hitUri != null && hitUri.equals(id)) { + return hit; + } + + if (hit instanceof HitGroup && depth!=0) { + Hit found=((HitGroup)hit).get(id,depth-1); + if (found!=null) return found; + } + } + return null; + } + + /** + * Inserts the given hit at the specified index in this group. + */ + public void set(int index, Hit hit) { + updateHits(); + if (hit instanceof ErrorHit) { // Merge instead + add(hit); + return; + } + + handleNewHit(hit); + Hit oldHit = hits.set(index, hit); + + if (oldHit!=null) + handleRemovedHit(oldHit); + } + + /** + * Adds a hit to this group in the specified index, + * all existing hits on this index and higher will have their index + * increased by one. + * <b>Note:</b> If the group was sorted, it will still be considered sorted + * after this call. + */ + public void add(int index, Hit hit) { + if (hit instanceof ErrorHit) { // Merge instead + add(hit); + return; + } + + boolean wasSorted = hitsSorted; + handleNewHit(hit); + hits.add(index, hit); + hitsSorted = wasSorted; + } + + /** + * Removes a hit from this group or any subgroup + * + * @param uriString the uri of the hit to remove + * @return the hit to remove, or null if the hit was not present + */ + public Hit remove(String uriString) { + return remove(new URI(uriString)); + } + + /** + * Removes a hit from this group or any subgroup. + * + * @param uri The uri of the hit to remove. + * @return The hit removed, or null if not found. + */ + public Hit remove(URI uri) { + for (Iterator<Hit> it = hits.iterator(); it.hasNext(); ) { + Hit hit = it.next(); + if (uri.equals(hit.getId())) { + it.remove(); + handleRemovedHit(hit); + return hit; + } + if (hit instanceof HitGroup) { + Hit removed = ((HitGroup)hit).remove(uri); + if (removed != null) { + return removed; + } + } + } + return null; + } + + /** + * Removes a hit from this group (not considering the hits of any subgroup) + * + * @param index the position of the hit to remove + * @return the hit removed + * @throws IndexOutOfBoundsException if there is no hit at the given position + */ + public Hit remove(int index) { + updateHits(); + Hit hit = hits.remove(index); + handleRemovedHit(hit); + + return hit; + } + + /** Sets the main error of this result. Prefer addError to add some error information. */ + public void setError(ErrorMessage error) { + if (errorHit == null) + add((Hit)createErrorHit(error)); + else + errorHit.addError(error); + } + + /** Adds an error to this result */ + public void addError(ErrorMessage error) { + if (errorHit == null) + add((Hit)createErrorHit(error)); + else + errorHit.addError(error); + } + + /** + * Returns the error hit containing all error information, + * or null if no error has occurred + */ + public ErrorHit getErrorHit() { + getError(); // Make sure the error hit is updated + return errorHit; + } + + /** + * Returns the first error in this result, + * or null if no searcher has produced an error AND the query doesn't contain an error + */ + public ErrorMessage getError() { + // See updateHits if this method is changed + if (errorHit != null) { + return errorHit.errors().iterator().next(); + } + + if (getQuery() != null && getQuery().errors().size() != 0) { + updateHits(); + } // Pull them over + + if (errorHit == null) { + return null; + } + + return errorHit.errors().iterator().next(); + } + + /** + * Handles the addition of a new error hit, whether or not we already have one + * + * @return true if this shouls also be added to the list of hits of this reslt + */ + private boolean mergeErrors(ErrorHit newHit) { + if (errorHit == null) { + errorHit = newHit; + return true; + } else { + errorHit.addErrors(newHit); + return false; + } + } + + /** + * Must be called before the list of hits, or anything dependent on the list of hits, is removed. + * Merges errors from the query if there is one set for this group + */ + private void updateHits() { + if (getQuery()==null) return; + + if (getQuery().errors().size() == 0) return; + + if (errorHit == null) // Creates an error hit where the first error is "main" + add((Hit)createErrorHit(toSearchError(getQuery().errors().get(0)))); + + // Add the rest of the errors + for (int i=1; i<getQuery().errors().size(); i++) + errorHit.addError(toSearchError(getQuery().errors().get(i))); + getQuery().errors().clear(); // TODO: Really clear them from here? + } + + protected ErrorHit createErrorHit(ErrorMessage errorMessage) { + return new DefaultErrorHit(getSource(), errorMessage); + } + + /** Compatibility */ + private ErrorMessage toSearchError(com.yahoo.processing.request.ErrorMessage error) { + if (error instanceof ErrorMessage) return (ErrorMessage)error; + else return new ErrorMessage(error.getCode(),error.getMessage(),error.getDetailedMessage(),error.getCause()); + } + + /** + * Remove the first <code>offset</code> <i>concrete</i> hits in this group, + * and hits beyond <code>offset+numHits</code> + */ + public void trim(int offset, int numHits) { + updateHits(); + ensureSorted(); + + int highBound = numHits + offset; // Largest offset +1 + + int currentIndex = -1; + + for (Iterator<Hit> i = hits.iterator(); i.hasNext();) { + Hit hit = i.next(); + + if (hit.isAuxiliary()) continue; + + currentIndex++; + if (currentIndex < offset || currentIndex >= highBound) { + i.remove(); + handleRemovedHit(hit); + } + } + } + + /** + * Returns an iterator of the hits in this group. + * <p> + * This iterator is modifiable - removals will take effect in this group of hits. + */ + public Iterator<Hit> iterator() { + updateHits(); + ensureSorted(); + return new HitIterator(this, hits); + } + + /** + * Returns an iterator that does depth-first traversal of leaf hits of this group. Calling this method has the + * side-effect of sorting the internal list of hits. + * + * @return A modifiable iterator. + */ + public Iterator<Hit> deepIterator() { + return new DeepHitIterator(iterator(), true); + } + + /** + * Returns an iterator that does depth-first traversal of leaf hits of this group, in a potentially unsorted order. + * As opposed to {@link #deepIterator()}, this method has no side-effect. + * + * @return A modifiable iterator. + */ + public Iterator<Hit> unorderedDeepIterator() { + return new DeepHitIterator(unorderedIterator(), false); + } + + /** Returns a read only list view of the hits in this */ + public List<Hit> asList() { + updateHits(); + ensureSorted(); + return unmodifiableHits; + } + + /** + * Returns a read only list view of the hits in this which is potentially unsorted. + * Using this over getHits is potentially faster when a sorted view is not needed. + */ + public List<Hit> asUnorderedHits() { + updateHits(); + return unmodifiableHits; + } + + /** + * Returns an iterator of the hits in this group in a potentially unsorted order. + * <p> + * Using this over getPreludeHitIterator is potentially faster when a sorted view is not needed. + * <p> + * This iterator is modifiable - removals will take effect in this group of hits. + */ + public Iterator<Hit> unorderedIterator() { + updateHits(); + return new HitIterator(this, hits); + } + + /** + * Force hit sorting now. + * This is not normally useful because a group will stay sorted automatically, + * but it is in the case where + * the hits have changed their internal state in a way that should change ordering + */ + public void sort() { + if (hitOrderer == null) { + Collections.sort(hits); + hitsSorted = true; + } else { + // This may or may not lead to a sorted result set, but + // it's a best effort + hitOrderer.order(hits); + if (likelyHitsHaveCorrectValueForSortFields()) { + hitsSorted = true; + } + } + } + + private boolean likelyHitsHaveCorrectValueForSortFields() { + if (hitOrderer == null) { + return true; + } else { + Set<String> filledFields = getFilled(); + return filledFields == null || !filledFields.isEmpty(); + } + } + + /** + * <p>Sets the hit orderer for this group.</p> + * + * @param hitOrderer the new hit orderer, or null to use default relevancy ordering + */ + public void setOrderer(HitOrderer hitOrderer) { + this.hitOrderer = hitOrderer; + if (hits.size() > 1) { + hitsSorted = false; + } + } + + /** + * Explicitly set whether the hits in this group are correctly sorted at this moment. + * If the contained hits are modified directly in a way that + * may break ordering, you should call setSorted(false). + */ + public void setSorted(boolean sorted) { + this.hitsSorted = sorted; + } + + + /** Returns the orderer used by this group, or null if the default relevancy order is used */ + public HitOrderer getOrderer() { + return hitOrderer; + } + + public void setDeletionBreaksOrdering(boolean flag) { deletionBreaksOrdering = flag; } + + public boolean getDeletionBreaksOrdering() { return deletionBreaksOrdering; } + + /** Called before hit lists or positions are used */ + private void ensureSorted() { + if ( ! orderedHits && ! hitsSorted && likelyHitsHaveCorrectValueForSortFields()) { + sort(); + } + } + + /** + * Returns true if all the hits recursively contained in this + * is cached + */ + public @Override boolean isCached() { + if (notCachedCount<1) return true; + if (subgroupCount<1) return false; // No need to check below + + // Else check recursively + for (Hit hit : hits) { + if (hit instanceof HitGroup) { + if (hit.isCached()) return true; + } + } + return false; + } + + /** + * Returns whether all hits in this result have been filled with + * the properties contained in the given summary class. Note that + * this method will also return true if no hits in this result are + * fillable. + */ + public boolean isFilled(String summaryClass) { + Set<String> filled = getFilled(); + return (filled == null || filled.contains(summaryClass)); + } + + + /** + * Sets sorting information to be the same as for the provided hitGroup. + * The contained hits should already be sorted in the order specified by + * the hitGroup given as argument. + */ + public void copyOrdering(HitGroup hitGroup) { + setOrderer(hitGroup.getOrderer()); + setDeletionBreaksOrdering(hitGroup.getDeletionBreaksOrdering()); + setOrdered(hitGroup.orderedHits); + } + + // -------------- State bookkeeping + + /** Ensures result invariants. Must be called when a hit is added to this result. */ + private void handleNewHit(Hit hit) { + if (!hit.isAuxiliary()) + concreteHitCount++; + + if (hit.getAddNumber() < 0) { + hit.setAddNumber(size()); + } + + hitsSorted = false; + Set<String> hitFilled = hit.getFilled(); + + if (hitFilled != null) { + Set<String> filled = getFilledInternal(); + if (filled == null) { + if (hitFilled.isEmpty()) { + filled = null; + } else if (hitFilled.size() == 1) { + filled = Collections.singleton(hitFilled.iterator().next()); + } else { + filled = new HashSet<>(hitFilled); + } + setFilledInternal(filled); + } else { + if (filled.size() == 1) { + if ( ! hitFilled.contains(filled.iterator().next())) { + filled = null; // No intersection + setFilledInternal(filled); + } + } else { + filled.retainAll(hitFilled); + } + } + } + + if (hit instanceof HitGroup) { + subgroupCount++; + } + if (!hit.isCached()) { + notCachedCount++; + } + } + + // Filled is not kept in sync at removal + private void handleRemovedHit(Hit hit) { + if (!hit.isAuxiliary()) { + concreteHitCount--; + if (!hit.isCached()) + notCachedCount--; + } + else if (hit instanceof HitGroup) { + subgroupCount--; + } + + if (deletionBreaksOrdering) { + hitsSorted = false; + } + } + + private void analyzeHit(Hit hit) { + if (hit instanceof HitGroup) { + ((HitGroup)hit).analyze(); + } + if (!hit.isAuxiliary()) + concreteHitCount++; + + if (!hit.isCached()) + notCachedCount++; + } + + /** + * Update concreteHitCount, cached and filled by iterating trough the hits of this result. + * Recursively also update all subgroups. + */ + public void analyze() { + concreteHitCount=0; + setFilledInternal(null); + notCachedCount=0; + Set<String> filled = getFilledInternal(); + + Iterator<Hit> i = unorderedIterator(); + while (filled == null && i.hasNext()) { + Hit hit = i.next(); + analyzeHit(hit); + Set<String> hitFilled = hit.getFilled(); + if (hitFilled != null) { + filled = (hitFilled.size() == 1) + ? Collections.singleton(hitFilled.iterator().next()) + : hitFilled.isEmpty() ? null : new HashSet<>(hitFilled); + setFilledInternal(filled); + } + } + String singleKey = null; + if (filled != null && filled.size() == 1) { + singleKey = filled.iterator().next(); + } + + + for (; i.hasNext();) { + Hit hit = i.next(); + analyzeHit(hit); + + if (filled != null) { + Set<String> hitFilled = hit.getFilled(); + if (hitFilled == null) { + // Intentionally empty. Strange semantic, null -> matches everything + } else if (hitFilled.isEmpty()) { + filled = null; // No intersection + setFilledInternal(filled); + } else { + if (filled.size() == 1) { + if ( ! hitFilled.contains(singleKey)) { + filled = null; // No intersection + setFilledInternal(filled); + singleKey = null; + } + } else { + filled.retainAll(hitFilled); + if (filled.size() == 1) { + singleKey = filled.iterator().next(); + } + } + } + } + } + } + + public HitGroup clone() { + HitGroup hitGroupClone = (HitGroup) super.clone(); + hitGroupClone.hits = new ListenableArrayList<>(this.hits.size()); + hitGroupClone.unmodifiableHits = Collections.unmodifiableList(hitGroupClone.hits); + for (Iterator<Hit> i = this.hits.iterator(); i.hasNext();) { + Hit hitClone = i.next().clone(); + hitGroupClone.hits.add(hitClone); + } + if (this.errorHit!=null) { // Find the cloned error and assign it + for (Hit hit : hitGroupClone.asList()) { + if (hit instanceof ErrorHit) + hitGroupClone.errorHit=(ErrorHit)hit; + } + } + + if (this.getFilledInternal()!=null) { + hitGroupClone.setFilledInternal(new HashSet<>(this.getFilledInternal())); + } + + return hitGroupClone; + } + + @Override + public void setFillable() {} + + /** Ignored as this should always be derived from the content hits */ + @Override + public void setFilled(String summaryClass) {} + + @Override + public boolean isFillable() { + return fillableHits().iterator().hasNext(); + } + + @Override + public Set<String> getFilled() { + Iterator<Hit> hitIterator = hits.iterator(); + Set<String> firstSummaryNames = getSummaryNamesNextFilledHit(hitIterator); + if (firstSummaryNames == null || firstSummaryNames.isEmpty()) + return firstSummaryNames; + + Set<String> intersection = firstSummaryNames; + while (true) { + Set<String> summaryNames = getSummaryNamesNextFilledHit(hitIterator); + if (summaryNames == null) + break; + + if (intersection.size() == 1) + return getFilledSingle(first(intersection), hitIterator); + + + boolean notInSet = false; + if (intersection == firstSummaryNames) { + if (intersection.size() == summaryNames.size()) { + for(String s : summaryNames) { + if ( ! intersection.contains(s)) { + intersection = new HashSet<>(firstSummaryNames); + notInSet = true; + break; + } + } + } + } + if (notInSet) { + intersection.retainAll(summaryNames); + } + + } + + return intersection; + } + + private Set<String> getSummaryNamesNextFilledHit(Iterator<Hit> hitIterator) { + while (hitIterator.hasNext()) { + Set<String> filled = hitIterator.next().getFilled(); + if (filled != null) + return filled; + } + return null; + } + + private Set<String> getFilledSingle(String summaryName, Iterator<Hit> hitIterator) { + while (true) { + Set<String> summaryNames = getSummaryNamesNextFilledHit(hitIterator); + if (summaryNames == null) { + return Collections.singleton(summaryName); + } else if (!summaryNames.contains(summaryName)) { + return Collections.emptySet(); + } + } + } + + private Iterable<Hit> fillableHits() { + Predicate<Hit> isFillable = hit -> hit.isFillable(); + + return Iterables.filter(hits, isFillable); + } + + /** Returns the incoming hit buffer to which new hits can be added to this asynchronous, if supported by the instance */ + @Override + public IncomingData<Hit> incoming() { return incomingHits; } + + @Override + public ListenableFuture<DataList<Hit>> complete() { return completedFuture; } + + @Override + public void addDataListener(Runnable runnable) { + hits.addListener(runnable); + } + +} |