/*
* Copyright 2016 the original author or authors.
*
* 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 org.gradle.jvm.internal.resolve;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.TreeMultimap;
import org.gradle.api.Named;
import org.gradle.internal.Cast;
import org.gradle.model.internal.manage.schema.ModelSchemaStore;
import org.gradle.platform.base.BinarySpec;
import org.gradle.platform.base.internal.BinarySpecInternal;
import java.util.*;
public class VariantsMatcher {
private static final Comparator<VariantValue> SPEC_COMPARATOR = new Comparator<VariantValue>() {
@Override
public int compare(VariantValue o1, VariantValue o2) {
return o1.spec.getDisplayName().compareTo(o2.spec.getDisplayName());
}
};
private final List<VariantAxisCompatibilityFactory> factories;
private final Class<? extends BinarySpec> binarySpecType;
private final ModelSchemaStore schemaStore;
public VariantsMatcher(List<VariantAxisCompatibilityFactory> factories, Class<? extends BinarySpec> binarySpecType, ModelSchemaStore schemaStore) {
this.factories = factories;
this.binarySpecType = binarySpecType;
this.schemaStore = schemaStore;
}
private VariantAxisCompatibility<Object> createSelector(Object o) {
for (VariantAxisCompatibilityFactory factory : factories) {
@SuppressWarnings("unchecked")
VariantAxisCompatibility<Object> selector = factory.getVariantAxisCompatibility(o);
if (selector != null) {
return selector;
}
}
return new DefaultVariantAxisCompatibility();
}
public Collection<? extends BinarySpec> filterBinaries(VariantsMetaData variantsMetaData, Collection<BinarySpec> binaries) {
if (binaries.isEmpty()) {
return binaries;
}
Set<String> resolveDimensions = variantsMetaData.getNonNullVariantAxes();
TreeMultimap<String, VariantValue> selectedVariants = TreeMultimap.create(String.CASE_INSENSITIVE_ORDER, SPEC_COMPARATOR);
Set<BinarySpec> removedSpecs = Sets.newHashSet();
for (BinarySpec binarySpec : binaries) {
if (binarySpecType.isAssignableFrom(binarySpec.getClass())) {
VariantsMetaData binaryVariants = DefaultVariantsMetaData.extractFrom(binarySpec, schemaStore.getSchema(((BinarySpecInternal)binarySpec).getPublicType()));
Set<String> commonsDimensions = Sets.intersection(resolveDimensions, binaryVariants.getNonNullVariantAxes());
Set<String> incompatibleDimensionTypes = VariantsMetaDataHelper.determineAxesWithIncompatibleTypes(variantsMetaData, binaryVariants, commonsDimensions);
if (incompatibleDimensionTypes.isEmpty()) {
for (String dimension : commonsDimensions) {
Class<?> dimensionType = variantsMetaData.getVariantAxisType(dimension).getConcreteClass();
boolean isStringType = String.class == dimensionType;
Object requestedValue = isStringType ? variantsMetaData.getValueAsString(dimension) : variantsMetaData.getValueAsType(Cast.<Class<? extends Named>>uncheckedCast(dimensionType), dimension);
Object binaryValue = isStringType ? binaryVariants.getValueAsString(dimension) : binaryVariants.getValueAsType(Cast.<Class<? extends Named>>uncheckedCast(dimensionType), dimension);
VariantAxisCompatibility<Object> selector = createSelector(requestedValue);
if (selector.isCompatibleWithRequirement(requestedValue, binaryValue)) {
VariantValue value = new VariantValue(binaryValue, binarySpec);
SortedSet<VariantValue> variantValues = selectedVariants.get(dimension);
for (VariantValue variantValue : variantValues) {
// all the values are equal, but we store all the binaries that match that value
// and incrementally build a list of binaries which are excluded because of a better match
if (selector.betterFit(requestedValue, variantValue.value, binaryValue)) {
// the new value is a better fit than the old one
removedSpecs.add(variantValue.spec);
} else if (selector.betterFit(requestedValue, binaryValue, variantValue.value)) {
// the old value is a better fit than the new one, let's ignore the new one altogether
removedSpecs.add(value.spec);
}
}
selectedVariants.put(dimension, value);
} else {
removedSpecs.add(binarySpec);
}
}
}
}
}
Set<BinarySpec> union = null;
for (String dimension : selectedVariants.keySet()) {
Set<BinarySpec> variantValues = ImmutableSet.copyOf(Iterables.transform(selectedVariants.get(dimension), VariantValue.SPEC_FUNCTION));
union = union == null ? variantValues : Sets.union(union, variantValues);
}
return union == null ? Collections.<BinarySpec>emptySet() : Sets.difference(union, removedSpecs);
}
private static class VariantValue {
public static final Function<VariantValue, BinarySpec> SPEC_FUNCTION = new Function<VariantValue, BinarySpec>() {
@Override
public BinarySpec apply(VariantValue input) {
return input.spec;
}
};
final Object value;
final BinarySpec spec;
private VariantValue(Object value, BinarySpec spec) {
this.value = value;
this.spec = spec;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("VariantValue{");
sb.append("spec=").append(spec);
sb.append(", value=").append(value);
sb.append('}');
return sb.toString();
}
}
}