package org.infinispan.query.dsl.embedded.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import org.infinispan.AdvancedCache; import org.infinispan.commons.util.CloseableIterator; import org.infinispan.objectfilter.ObjectFilter; import org.infinispan.query.dsl.QueryFactory; import org.infinispan.query.dsl.impl.BaseQuery; /** * Base class for embedded-mode query implementations. Subclasses need to implement {@link #getIterator()} and {@link * #getComparator()} methods and this class will take care of sorting (fully in-memory). * * @author anistor@redhat.com * @since 8.0 */ abstract class BaseEmbeddedQuery extends BaseQuery { /** * Initial capacity of the collection used for collecting results when performing internal sorting. */ private static final int INITIAL_CAPACITY = 1000; protected final AdvancedCache<?, ?> cache; /** * The cached results, lazily evaluated. */ private List<Object> results; /** * The total number of results matching the query, ignoring pagination. This is lazily evaluated. */ private int resultSize; protected BaseEmbeddedQuery(QueryFactory queryFactory, AdvancedCache<?, ?> cache, String queryString, Map<String, Object> namedParameters, String[] projection, long startOffset, int maxResults) { super(queryFactory, queryString, namedParameters, projection, startOffset, maxResults); this.cache = cache; } @Override public void resetQuery() { results = null; } @Override public <T> List<T> list() { if (results == null) { results = listInternal(); } return (List<T>) results; } private List<Object> listInternal() { List<Object> results; try (CloseableIterator<ObjectFilter.FilterResult> iterator = getIterator()) { if (!iterator.hasNext()) { results = Collections.emptyList(); } else { Comparator<Comparable[]> comparator = getComparator(); if (comparator == null) { // collect unsorted results and get the requested page if any was specified results = new ArrayList<>(INITIAL_CAPACITY); while (iterator.hasNext()) { ObjectFilter.FilterResult entry = iterator.next(); resultSize++; if (resultSize > startOffset && (maxResults == -1 || results.size() < maxResults)) { results.add(projection != null ? entry.getProjection() : entry.getInstance()); } } } else { // collect and sort results, in reverse order for now PriorityQueue<ObjectFilter.FilterResult> filterResults = new PriorityQueue<>(INITIAL_CAPACITY, new ReverseFilterResultComparator(comparator)); while (iterator.hasNext()) { ObjectFilter.FilterResult entry = iterator.next(); resultSize++; filterResults.add(entry); if (maxResults != -1 && filterResults.size() > startOffset + maxResults) { // remove the head, which is actually the highest result filterResults.remove(); } } // collect and reverse if (filterResults.size() > startOffset) { Object[] res = new Object[filterResults.size() - startOffset]; int i = filterResults.size(); while (i-- > startOffset) { ObjectFilter.FilterResult r = filterResults.remove(); res[i - startOffset] = projection != null ? r.getProjection() : r.getInstance(); } results = Arrays.asList(res); } else { results = Collections.emptyList(); } } } } return results; } /** * Create a comparator to be used for ordering the results returned by {@link #getIterator()}. * * @return the comparator or {@code null} if no sorting needs to be applied */ protected abstract Comparator<Comparable[]> getComparator(); /** * Create an iterator over the results of the query, in no particular order. Ordering will be provided if {@link * #getComparator()} returns a non-null {@link Comparator}. */ protected abstract CloseableIterator<ObjectFilter.FilterResult> getIterator(); @Override public int getResultSize() { list(); return resultSize; } @Override public String toString() { return "BaseEmbeddedQuery{" + "queryString=" + queryString + ", namedParameters=" + namedParameters + ", projection=" + Arrays.toString(projection) + ", startOffset=" + startOffset + ", maxResults=" + maxResults + '}'; } /** * Compares two {@link ObjectFilter.FilterResult} objects based on a given {@link Comparator} and reverses the * result. */ private static class ReverseFilterResultComparator implements Comparator<ObjectFilter.FilterResult> { private final Comparator<Comparable[]> comparator; private ReverseFilterResultComparator(Comparator<Comparable[]> comparator) { this.comparator = comparator; } @Override public int compare(ObjectFilter.FilterResult o1, ObjectFilter.FilterResult o2) { return -comparator.compare(o1.getSortProjection(), o2.getSortProjection()); } } }