// Copyright 2017 Google Inc. 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.api.ads.adwords.axis.utils.v201702.shopping; import com.google.api.ads.adwords.axis.v201702.cm.ProductBiddingCategory; import com.google.api.ads.adwords.axis.v201702.cm.ProductBrand; import com.google.api.ads.adwords.axis.v201702.cm.ProductCanonicalCondition; import com.google.api.ads.adwords.axis.v201702.cm.ProductCanonicalConditionCondition; import com.google.api.ads.adwords.axis.v201702.cm.ProductChannel; import com.google.api.ads.adwords.axis.v201702.cm.ProductChannelExclusivity; import com.google.api.ads.adwords.axis.v201702.cm.ProductCustomAttribute; import com.google.api.ads.adwords.axis.v201702.cm.ProductDimension; import com.google.api.ads.adwords.axis.v201702.cm.ProductDimensionType; import com.google.api.ads.adwords.axis.v201702.cm.ProductOfferId; import com.google.api.ads.adwords.axis.v201702.cm.ProductType; import com.google.api.ads.adwords.axis.v201702.cm.ShoppingProductChannel; import com.google.api.ads.adwords.axis.v201702.cm.ShoppingProductChannelExclusivity; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.Ordering; import java.util.Comparator; import javax.annotation.Nullable; /** * Subclass-aware comparator for {@link ProductDimension} objects. Performs a <em>logical</em> * comparison between instances. This comparator is <em>not</em> consistent with * {@link #equals(Object)}. * * <p>The {@link #compare(ProductDimension, ProductDimension)} method handles nulls, ordering nulls * last. */ class ProductDimensionComparator implements Comparator<ProductDimension> { private final ImmutableMap<Class<? extends ProductDimension>, Comparator<? extends ProductDimension>> comparatorMap; /** * Ordering for Comparable objects that places nulls last, then defers to the Comparable's * natural ordering. */ private static final Ordering<Comparable<?>> NULLS_LAST_NATURAL_ORDERING = Ordering.natural().nullsLast(); /** * Ordering for any type that simply compares null-ness, placing nulls last. */ private static final Ordering<Object> NULLS_LAST_OBJECT_ORDERING = Ordering.allEqual().nullsLast(); /** * Ordering for ProductDimensionType that compares by {@link ProductDimensionType#getValue()}, * placing nulls last. */ private static final Ordering<ProductDimensionType> NULLS_LAST_DIMENSION_TYPE_ORDERING = new Ordering<ProductDimensionType>() { @Override public int compare(ProductDimensionType left, ProductDimensionType right) { return NULLS_LAST_CASE_INSENSITIVE_ORDERING.compare(left.getValue(), right.getValue()); } }.nullsLast(); /** * Ordering for String that places nulls last, then defers to * {@link String#CASE_INSENSITIVE_ORDER}. */ private static final Ordering<String> NULLS_LAST_CASE_INSENSITIVE_ORDERING = Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); ProductDimensionComparator() { Builder<Class<? extends ProductDimension>, Comparator<? extends ProductDimension>> mapBuilder = ImmutableMap.builder(); mapBuilder.put(ProductBiddingCategory.class, new ProductBiddingCategoryComparator()); mapBuilder.put(ProductBrand.class, new ProductBrandComparator()); mapBuilder.put(ProductCanonicalCondition.class, new ProductCanonicalConditionComparator()); mapBuilder.put(ProductCustomAttribute.class, new ProductCustomAttributeComparator()); mapBuilder.put(ProductOfferId.class, new ProductOfferIdComparator()); mapBuilder.put(ProductType.class, new ProductTypeComparator()); mapBuilder.put(ProductChannel.class, new ProductChannelComparator()); mapBuilder.put(ProductChannelExclusivity.class, new ProductChannelExclusivityComparator()); comparatorMap = mapBuilder.build(); } @Override public int compare(@Nullable ProductDimension d1, @Nullable ProductDimension d2) { // Confirm that each of d1 and d2 is either null or is a supported subclass of ProductDimension. Preconditions.checkArgument(d1 == null || comparatorMap.containsKey(d1.getClass()), "Unsupported dimension type %s", d1); Preconditions.checkArgument(d2 == null || comparatorMap.containsKey(d2.getClass()), "Unsupported dimension type %s", d2); if (d1 == d2) { return 0; } // Order nulls last. int result = NULLS_LAST_OBJECT_ORDERING.compare(d1, d2); if (result != 0) { return result; } // java.lang.Class does not implement Comparable, so compare by fully qualified name instead. result = d1.getClass().getName().compareTo(d2.getClass().getName()); if (result != 0) { return result; } Preconditions.checkArgument(d1.getClass().equals(d2.getClass()), "Assumption failed - class of %s and %s are not equal", d1, d2); // Both dimensions are of the same type, so now compare by the appropriate values // for the type. return getDeepComparator(d1).compare(d1, d2); } /** * Returns the deep comparator that will compare the appropriate values between two instances of * type D. Throws an IllegalArgumentException if {@code D} is a subclass of ProductDimension not * supported by Shopping campaigns. As of v201409, the types not supported by Shopping campaigns * are: * <ul> * <li>ProductAdWordsGrouping</li> * <li>ProductAdWordsLabels</li> * <li>ProductLegacyCondition</li> * <li>ProductTypeFull</li> * </ul> * * @throws IllegalArgumentException if {@code D} is not a dimension type supported by this * comparator */ private <D extends ProductDimension> Comparator<D> getDeepComparator(D dimension) { @SuppressWarnings("unchecked") Comparator<D> comparator = (Comparator<D>) comparatorMap.get(dimension.getClass()); Preconditions.checkArgument(comparator != null, "No comparator exists for %s. This comparator only supports comparisons of " + "ProductDimension subclasses supported by Shopping campaigns.", dimension); return comparator; } // Below are the ProductDimension subclass-specific implementations of Comparator. Each of these // assumes that it will only be passed non-null arguments since ProductDimensionComparator.compare // will only defer to one of these Comparators if neither object is null. /** * Comparator for {@link ProductBiddingCategory} objects that orders by type and then value. * Assumes that neither object is null. */ private static class ProductBiddingCategoryComparator implements Comparator<ProductBiddingCategory> { @Override public int compare(ProductBiddingCategory o1, ProductBiddingCategory o2) { int result = NULLS_LAST_DIMENSION_TYPE_ORDERING.compare(o1.getType(), o2.getType()); if (result != 0) { return result; } // Value is optional, so handle nulls. return NULLS_LAST_NATURAL_ORDERING.compare(o1.getValue(), o2.getValue()); } } /** * Comparator for {@link ProductBrand} objects that orders by value. Assumes that neither object * is null. */ private static class ProductBrandComparator implements Comparator<ProductBrand> { @Override public int compare(ProductBrand o1, ProductBrand o2) { return NULLS_LAST_CASE_INSENSITIVE_ORDERING.compare(o1.getValue(), o2.getValue()); } } /** * Comparator for {@link ProductCanonicalCondition} objects that orders by condition. Assumes that * neither object is null. */ private static class ProductCanonicalConditionComparator implements Comparator<ProductCanonicalCondition> { @Override public int compare(ProductCanonicalCondition o1, ProductCanonicalCondition o2) { ProductCanonicalConditionCondition condition1 = o1.getCondition(); ProductCanonicalConditionCondition condition2 = o2.getCondition(); if (condition1 == condition2) { return 0; } // If one condition is null but not the other, order so that the null object will be last. int result = NULLS_LAST_OBJECT_ORDERING.compare(condition1, condition2); if (result != 0) { return result; } return NULLS_LAST_NATURAL_ORDERING.compare(condition1.getValue(), condition2.getValue()); } } /** * Comparator for {@link ProductCustomAttribute} objects that orders by type then value. Assumes * that neither object is null. */ private static class ProductCustomAttributeComparator implements Comparator<ProductCustomAttribute> { @Override public int compare(ProductCustomAttribute o1, ProductCustomAttribute o2) { int result = NULLS_LAST_DIMENSION_TYPE_ORDERING.compare(o1.getType(), o2.getType()); if (result != 0) { return result; } return NULLS_LAST_CASE_INSENSITIVE_ORDERING.compare(o1.getValue(), o2.getValue()); } } /** * Comparator for {@link ProductOfferId} objects that orders by value. Assumes that neither object * is null. */ private static class ProductOfferIdComparator implements Comparator<ProductOfferId> { @Override public int compare(ProductOfferId o1, ProductOfferId o2) { return NULLS_LAST_CASE_INSENSITIVE_ORDERING.compare(o1.getValue(), o2.getValue()); } } /** * Comparator for {@link ProductType} objects that orders by type then value. Assumes that neither * object is null. */ private static class ProductTypeComparator implements Comparator<ProductType> { @Override public int compare(ProductType o1, ProductType o2) { int result = NULLS_LAST_DIMENSION_TYPE_ORDERING.compare(o1.getType(), o2.getType()); if (result != 0) { return result; } return NULLS_LAST_CASE_INSENSITIVE_ORDERING.compare(o1.getValue(), o2.getValue()); } } /** * Comparator for {@link ProductChannel} objects that orders by channel. Assumes that neither * object is null. */ private static class ProductChannelComparator implements Comparator<ProductChannel> { @Override public int compare(ProductChannel o1, ProductChannel o2) { ShoppingProductChannel channel1 = o1.getChannel(); ShoppingProductChannel channel2 = o2.getChannel(); if (channel1 == channel2) { return 0; } // If one channel is null but not the other, order so that the null object will be last. int result = NULLS_LAST_OBJECT_ORDERING.compare(channel1, channel2); if (result != 0) { return result; } return NULLS_LAST_NATURAL_ORDERING.compare(channel1.getValue(), channel2.getValue()); } } /** * Comparator for {@link ProductChannelExclusivity} objects that orders by channel exclusivity. * Assumes that neither object is null. */ private static class ProductChannelExclusivityComparator implements Comparator<ProductChannelExclusivity> { @Override public int compare(ProductChannelExclusivity o1, ProductChannelExclusivity o2) { ShoppingProductChannelExclusivity exclusivity1 = o1.getChannelExclusivity(); ShoppingProductChannelExclusivity exclusivity2 = o2.getChannelExclusivity(); if (exclusivity1 == exclusivity2) { return 0; } // If one exclusivity is null but not the other, order so that the null object will be last. int result = NULLS_LAST_OBJECT_ORDERING.compare(exclusivity1, exclusivity2); if (result != 0) { return result; } return NULLS_LAST_NATURAL_ORDERING.compare(exclusivity1.getValue(), exclusivity2.getValue()); } } }