// Copyright (C) 2010 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.server.query.change; import static com.google.common.base.Preconditions.checkArgument; import com.google.common.base.Function; 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.common.collect.Lists; import com.google.gerrit.server.query.AndPredicate; import com.google.gerrit.server.query.Predicate; 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 extends AndPredicate<ChangeData> implements ChangeDataSource { private static final Comparator<Predicate<ChangeData>> CMP = new Comparator<Predicate<ChangeData>>() { @Override public int compare(Predicate<ChangeData> a, Predicate<ChangeData> b) { int ai = a instanceof ChangeDataSource ? 0 : 1; int bi = b instanceof ChangeDataSource ? 0 : 1; int cmp = ai - bi; if (cmp == 0) { cmp = a.getCost() - b.getCost(); } if (cmp == 0 // && a instanceof ChangeDataSource // && b instanceof ChangeDataSource) { ChangeDataSource as = (ChangeDataSource) a; ChangeDataSource bs = (ChangeDataSource) b; cmp = as.getCardinality() - bs.getCardinality(); if (cmp == 0) { cmp = (as.hasChange() ? 0 : 1) - (bs.hasChange() ? 0 : 1); } } return cmp; } }; private static List<Predicate<ChangeData>> sort( Collection<? extends Predicate<ChangeData>> that) { List<Predicate<ChangeData>> r = new ArrayList<>(that); Collections.sort(r, CMP); return r; } private final int start; private int cardinality = -1; public AndSource(Collection<? extends Predicate<ChangeData>> that) { this(that, 0); } public AndSource(Collection<? extends Predicate<ChangeData>> that, int start) { super(sort(that)); checkArgument(start >= 0, "negative start: %s", start); this.start = start; } @Override public boolean hasChange() { ChangeDataSource source = source(); return source != null && source.hasChange(); } @Override public ResultSet<ChangeData> read() throws OrmException { try { return readImpl(); } catch (OrmRuntimeException err) { Throwables.propagateIfInstanceOf(err.getCause(), OrmException.class); throw new OrmException(err); } } private ResultSet<ChangeData> readImpl() throws OrmException { ChangeDataSource source = source(); if (source == null) { throw new OrmException("No ChangeDataSource: " + this); } @SuppressWarnings("unchecked") Predicate<ChangeData> pred = (Predicate<ChangeData>) source; boolean useSortKey = ChangeQueryBuilder.hasSortKey(pred); List<ChangeData> r = Lists.newArrayList(); ChangeData last = null; int nextStart = 0; boolean skipped = false; for (ChangeData data : buffer(source, source.read())) { if (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. // Paginated p = (Paginated) source; while (skipped && r.size() < p.limit() + start) { ChangeData lastBeforeRestart = last; skipped = false; last = null; ResultSet<ChangeData> next = useSortKey ? p.restart(lastBeforeRestart) : p.restart(nextStart); for (ChangeData data : buffer(source, next)) { if (match(data)) { r.add(data); } else { skipped = true; } last = data; nextStart++; } } } if (start >= r.size()) { r = ImmutableList.of(); } else if (start > 0) { r = ImmutableList.copyOf(r.subList(start, r.size())); } return new ListResultSet<>(r); } private Iterable<ChangeData> buffer( ChangeDataSource source, ResultSet<ChangeData> scanner) { final boolean loadChange = !source.hasChange(); return FluentIterable .from(Iterables.partition(scanner, 50)) .transformAndConcat(new Function<List<ChangeData>, List<ChangeData>>() { @Override public List<ChangeData> apply(List<ChangeData> buffer) { if (loadChange) { try { ChangeData.ensureChangeLoaded(buffer); } catch (OrmException e) { throw new OrmRuntimeException(e); } } return buffer; } }); } private ChangeDataSource source() { int minCost = Integer.MAX_VALUE; Predicate<ChangeData> s = null; for (Predicate<ChangeData> p : getChildren()) { if (p instanceof ChangeDataSource && p.getCost() < minCost) { s = p; minCost = p.getCost(); } } return (ChangeDataSource) s; } @Override public int getCardinality() { if (cardinality < 0) { cardinality = Integer.MAX_VALUE; for (Predicate<ChangeData> p : getChildren()) { if (p instanceof ChangeDataSource) { int c = ((ChangeDataSource) p).getCardinality(); cardinality = Math.min(cardinality, c); } } } return cardinality; } }