| // Copyright (C) 2016 The Android Open Source Project |
| // |
| // 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.google.gerrit.index.query; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.FluentIterable; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.gwtorm.server.ListResultSet; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.gwtorm.server.OrmRuntimeException; |
| import com.google.gwtorm.server.ResultSet; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| public class AndSource<T> extends AndPredicate<T> |
| implements DataSource<T>, Comparator<Predicate<T>> { |
| protected final DataSource<T> source; |
| |
| private final IsVisibleToPredicate<T> isVisibleToPredicate; |
| private final int start; |
| private final int cardinality; |
| |
| public AndSource(Collection<? extends Predicate<T>> that) { |
| this(that, null, 0); |
| } |
| |
| public AndSource(Predicate<T> that, IsVisibleToPredicate<T> isVisibleToPredicate) { |
| this(that, isVisibleToPredicate, 0); |
| } |
| |
| public AndSource(Predicate<T> that, IsVisibleToPredicate<T> isVisibleToPredicate, int start) { |
| this(ImmutableList.of(that), isVisibleToPredicate, start); |
| } |
| |
| public AndSource( |
| Collection<? extends Predicate<T>> that, |
| IsVisibleToPredicate<T> isVisibleToPredicate, |
| int start) { |
| super(that); |
| checkArgument(start >= 0, "negative start: %s", start); |
| this.isVisibleToPredicate = isVisibleToPredicate; |
| this.start = start; |
| |
| int c = Integer.MAX_VALUE; |
| DataSource<T> s = null; |
| int minCost = Integer.MAX_VALUE; |
| for (Predicate<T> p : sort(getChildren())) { |
| if (p instanceof DataSource) { |
| c = Math.min(c, ((DataSource<?>) p).getCardinality()); |
| |
| int cost = p.estimateCost(); |
| if (cost < minCost) { |
| s = toDataSource(p); |
| minCost = cost; |
| } |
| } |
| } |
| this.source = s; |
| this.cardinality = c; |
| } |
| |
| @Override |
| public ResultSet<T> read() throws OrmException { |
| try { |
| return readImpl(); |
| } catch (OrmRuntimeException err) { |
| if (err.getCause() != null) { |
| Throwables.throwIfInstanceOf(err.getCause(), OrmException.class); |
| } |
| throw new OrmException(err); |
| } |
| } |
| |
| private ResultSet<T> readImpl() throws OrmException { |
| if (source == null) { |
| throw new OrmException("No DataSource: " + this); |
| } |
| List<T> r = new ArrayList<>(); |
| T last = null; |
| int nextStart = 0; |
| boolean skipped = false; |
| for (T data : buffer(source.read())) { |
| if (!isMatchable() || match(data)) { |
| r.add(data); |
| } else { |
| skipped = true; |
| } |
| last = data; |
| nextStart++; |
| } |
| |
| if (skipped && last != null && source instanceof Paginated) { |
| // If our source is a paginated source and we skipped at |
| // least one of its results, we may not have filled the full |
| // limit the caller wants. Restart the source and continue. |
| // |
| @SuppressWarnings("unchecked") |
| Paginated<T> p = (Paginated<T>) source; |
| while (skipped && r.size() < p.getOptions().limit() + start) { |
| skipped = false; |
| ResultSet<T> next = p.restart(nextStart); |
| |
| for (T data : buffer(next)) { |
| if (match(data)) { |
| r.add(data); |
| } else { |
| skipped = true; |
| } |
| nextStart++; |
| } |
| } |
| } |
| |
| if (start >= r.size()) { |
| r = ImmutableList.of(); |
| } else if (start > 0) { |
| r = ImmutableList.copyOf(r.subList(start, r.size())); |
| } |
| return new ListResultSet<>(r); |
| } |
| |
| @Override |
| public boolean isMatchable() { |
| return isVisibleToPredicate != null || super.isMatchable(); |
| } |
| |
| @Override |
| public boolean match(T object) throws OrmException { |
| if (isVisibleToPredicate != null && !isVisibleToPredicate.match(object)) { |
| return false; |
| } |
| |
| if (super.isMatchable() && !super.match(object)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private Iterable<T> buffer(ResultSet<T> scanner) { |
| return FluentIterable.from(Iterables.partition(scanner, 50)) |
| .transformAndConcat(this::transformBuffer); |
| } |
| |
| protected List<T> transformBuffer(List<T> buffer) throws OrmRuntimeException { |
| return buffer; |
| } |
| |
| @Override |
| public int getCardinality() { |
| return cardinality; |
| } |
| |
| private List<Predicate<T>> sort(Collection<? extends Predicate<T>> that) { |
| List<Predicate<T>> r = new ArrayList<>(that); |
| Collections.sort(r, this); |
| return r; |
| } |
| |
| @Override |
| public int compare(Predicate<T> a, Predicate<T> b) { |
| int ai = a instanceof DataSource ? 0 : 1; |
| int bi = b instanceof DataSource ? 0 : 1; |
| int cmp = ai - bi; |
| |
| if (cmp == 0) { |
| cmp = a.estimateCost() - b.estimateCost(); |
| } |
| |
| if (cmp == 0 && a instanceof DataSource && b instanceof DataSource) { |
| DataSource<?> as = (DataSource<?>) a; |
| DataSource<?> bs = (DataSource<?>) b; |
| cmp = as.getCardinality() - bs.getCardinality(); |
| } |
| return cmp; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private DataSource<T> toDataSource(Predicate<T> pred) { |
| return (DataSource<T>) pred; |
| } |
| } |