// Copyright 2015 The Bazel Authors. All rights reserved. // // 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.devtools.build.lib.query2.engine; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.MutableMap; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryTaskCallable; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryTaskFuture; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.ThreadSafeMutableSet; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; /** Several query utilities to make easier to work with query callbacks and uniquifiers. */ public final class QueryUtil { private QueryUtil() { } /** A {@link Callback} that can aggregate all the partial results into one set. */ public interface AggregateAllCallback> extends Callback { /** Returns a {@link Set} of all the results. */ S getResult(); } /** A {@link OutputFormatterCallback} that is also a {@link AggregateAllCallback}. */ public abstract static class AggregateAllOutputFormatterCallback> extends ThreadSafeOutputFormatterCallback implements AggregateAllCallback { } private static class AggregateAllOutputFormatterCallbackImpl extends AggregateAllOutputFormatterCallback> { private final ThreadSafeMutableSet result; private AggregateAllOutputFormatterCallbackImpl(QueryEnvironment env) { this.result = env.createThreadSafeMutableSet(); } @Override public final void processOutput(Iterable partialResult) { Iterables.addAll(result, partialResult); } @Override public ThreadSafeMutableSet getResult() { return result; } } private static class OrderedAggregateAllOutputFormatterCallbackImpl extends AggregateAllOutputFormatterCallback> { private final Set resultSet; private final List resultList; private OrderedAggregateAllOutputFormatterCallbackImpl(QueryEnvironment env) { this.resultSet = env.createThreadSafeMutableSet(); this.resultList = new ArrayList<>(); } @Override public final synchronized void processOutput(Iterable partialResult) { for (T element : partialResult) { if (resultSet.add(element)) { resultList.add(element); } } } @Override public synchronized Set getResult() { // A CompactHashSet's iteration order is the same as its insertion order. CompactHashSet result = CompactHashSet.createWithExpectedSize(resultList.size()); result.addAll(resultList); return result; } } /** * Returns a fresh {@link AggregateAllOutputFormatterCallback} instance whose * {@link AggregateAllCallback#getResult} returns all the elements of the result in the order they * were processed. */ public static AggregateAllOutputFormatterCallback> newOrderedAggregateAllOutputFormatterCallback(QueryEnvironment env) { return new OrderedAggregateAllOutputFormatterCallbackImpl<>(env); } /** * Returns a fresh {@link AggregateAllCallback} instance that aggregates all of the values into an * {@link ThreadSafeMutableSet}. */ public static AggregateAllCallback> newAggregateAllCallback( QueryEnvironment env) { return new AggregateAllOutputFormatterCallbackImpl<>(env); } /** * Returns a {@link QueryTaskFuture} representing the evaluation of {@code expr} as a mutable, * thread safe {@link Set} comprised of all the results. * *

Should only be used by QueryExpressions when it is the only way of achieving correctness. */ public static QueryTaskFuture> evalAll( QueryEnvironment env, QueryExpressionContext context, QueryExpression expr) { final AggregateAllCallback> callback = newAggregateAllCallback(env); return env.whenSucceedsCall( env.eval(expr, context, callback), new QueryTaskCallable>() { @Override public ThreadSafeMutableSet call() { return callback.getResult(); } }); } /** * A mutable thread safe {@link Set} that uses a {@link KeyExtractor} for determining equality of * its elements. This is useful e.g. when {@code T} isn't guaranteed to have a useful * {@link Object#equals} and {@link Object#hashCode} but {@code K} is. */ public static class ThreadSafeMutableKeyExtractorBackedSetImpl extends AbstractSet implements ThreadSafeMutableSet { private final KeyExtractor extractor; private final Class elementClass; private final ConcurrentMap map; public ThreadSafeMutableKeyExtractorBackedSetImpl( KeyExtractor extractor, Class elementClass) { this(extractor, elementClass, /*concurrencyLevel=*/ 1); } public ThreadSafeMutableKeyExtractorBackedSetImpl( KeyExtractor extractor, Class elementClass, int concurrencyLevel) { this.extractor = extractor; this.elementClass = elementClass; this.map = new ConcurrentHashMap<>(/*initialCapacity=*/ concurrencyLevel, /*loadFactor=*/ 0.75f); } @Override public Iterator iterator() { return map.values().iterator(); } @Override public int size() { return map.size(); } @Override public boolean add(T element) { return map.putIfAbsent(extractor.extractKey(element), element) == null; } @Override public boolean contains(Object obj) { if (!elementClass.isInstance(obj)) { return false; } T element = elementClass.cast(obj); return map.containsKey(extractor.extractKey(element)); } @Override public boolean remove(Object obj) { if (!elementClass.isInstance(obj)) { return false; } T element = elementClass.cast(obj); return map.remove(extractor.extractKey(element)) != null; } } /** * A {@link MutableMap} implementation that uses a {@link KeyExtractor} for determining equality * of its keys. */ public static class MutableKeyExtractorBackedMapImpl implements MutableMap { private final KeyExtractor extractor; private final HashMap map; public MutableKeyExtractorBackedMapImpl(KeyExtractor extractor) { this.extractor = extractor; this.map = new HashMap<>(); } @Override @Nullable public V get(T key) { return map.get(extractor.extractKey(key)); } @Override public V put(T key, V value) { return map.put(extractor.extractKey(key), value); } } /** A trivial {@link Uniquifier} implementation. */ public static class UniquifierImpl implements Uniquifier { private final KeyExtractor extractor; private final Set alreadySeen; public UniquifierImpl(KeyExtractor extractor) { this.extractor = extractor; this.alreadySeen = Collections.newSetFromMap(new ConcurrentHashMap<>()); } @Override public boolean uniquePure(T element) { return !alreadySeen.contains(extractor.extractKey(element)); } @Override public boolean unique(T element) { return alreadySeen.add(extractor.extractKey(element)); } @Override public ImmutableList unique(Iterable newElements) { ImmutableList.Builder result = ImmutableList.builder(); for (T element : newElements) { if (unique(element)) { result.add(element); } } return result.build(); } } /** A trivial {@link MinDepthUniquifier} implementation. */ public static class MinDepthUniquifierImpl implements MinDepthUniquifier { private final KeyExtractor extractor; private final ConcurrentMap alreadySeenAtDepth; public MinDepthUniquifierImpl(KeyExtractor extractor, int concurrencyLevel) { this.extractor = extractor; this.alreadySeenAtDepth = new ConcurrentHashMap<>(/*initialCapacity=*/ concurrencyLevel, /*loadFactor=*/ 0.75f); } @Override public final ImmutableList uniqueAtDepthLessThanOrEqualTo( Iterable newElements, int depth) { ImmutableList.Builder resultBuilder = ImmutableList.builder(); for (T newElement : newElements) { if (uniqueAtDepthLessThanOrEqualTo(newElement, depth)) { resultBuilder.add(newElement); } } return resultBuilder.build(); } @Override public boolean uniqueAtDepthLessThanOrEqualTo(T newElement, int depth) { AtomicInteger newDepth = new AtomicInteger(depth); AtomicInteger previousDepth = alreadySeenAtDepth.putIfAbsent(extractor.extractKey(newElement), newDepth); if (previousDepth == null) { return true; } if (depth < previousDepth.get()) { synchronized (previousDepth) { if (depth < previousDepth.get()) { // We've seen the element before, but never at a depth this shallow. previousDepth.set(depth); return true; } } } return false; } @Override public boolean uniqueAtDepthLessThanOrEqualToPure(T newElement, int depth) { AtomicInteger previousDepth = alreadySeenAtDepth.get(extractor.extractKey(newElement)); return previousDepth != null ? depth < previousDepth.get() : true; } } }