Home | History | Annotate | Download | only in collect
      1 /*
      2  * Copyright (C) 2011 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
     11  * express or implied. See the License for the specific language governing permissions and
     12  * limitations under the License.
     13  */
     14 
     15 package com.google.common.collect;
     16 
     17 import static com.google.common.base.Preconditions.checkArgument;
     18 import static com.google.common.base.Preconditions.checkNotNull;
     19 import static com.google.common.collect.BoundType.CLOSED;
     20 import static com.google.common.collect.BoundType.OPEN;
     21 
     22 import com.google.common.annotations.GwtCompatible;
     23 import com.google.common.base.Objects;
     24 
     25 import java.io.Serializable;
     26 import java.util.Comparator;
     27 
     28 import javax.annotation.Nullable;
     29 
     30 /**
     31  * A generalized interval on any ordering, for internal use. Supports {@code null}. Unlike
     32  * {@link Range}, this allows the use of an arbitrary comparator. This is designed for use in the
     33  * implementation of subcollections of sorted collection types.
     34  *
     35  * <p>Whenever possible, use {@code Range} instead, which is better supported.
     36  *
     37  * @author Louis Wasserman
     38  */
     39 @GwtCompatible(serializable = true)
     40 final class GeneralRange<T> implements Serializable {
     41   /**
     42    * Converts a Range to a GeneralRange.
     43    */
     44   static <T extends Comparable> GeneralRange<T> from(Range<T> range) {
     45     @Nullable
     46     T lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() : null;
     47     BoundType lowerBoundType = range.hasLowerBound() ? range.lowerBoundType() : OPEN;
     48 
     49     @Nullable
     50     T upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() : null;
     51     BoundType upperBoundType = range.hasUpperBound() ? range.upperBoundType() : OPEN;
     52     return new GeneralRange<T>(Ordering.natural(), range.hasLowerBound(), lowerEndpoint,
     53         lowerBoundType, range.hasUpperBound(), upperEndpoint, upperBoundType);
     54   }
     55 
     56   /**
     57    * Returns the whole range relative to the specified comparator.
     58    */
     59   static <T> GeneralRange<T> all(Comparator<? super T> comparator) {
     60     return new GeneralRange<T>(comparator, false, null, OPEN, false, null, OPEN);
     61   }
     62 
     63   /**
     64    * Returns everything above the endpoint relative to the specified comparator, with the specified
     65    * endpoint behavior.
     66    */
     67   static <T> GeneralRange<T> downTo(Comparator<? super T> comparator, @Nullable T endpoint,
     68       BoundType boundType) {
     69     return new GeneralRange<T>(comparator, true, endpoint, boundType, false, null, OPEN);
     70   }
     71 
     72   /**
     73    * Returns everything below the endpoint relative to the specified comparator, with the specified
     74    * endpoint behavior.
     75    */
     76   static <T> GeneralRange<T> upTo(Comparator<? super T> comparator, @Nullable T endpoint,
     77       BoundType boundType) {
     78     return new GeneralRange<T>(comparator, false, null, OPEN, true, endpoint, boundType);
     79   }
     80 
     81   /**
     82    * Returns everything between the endpoints relative to the specified comparator, with the
     83    * specified endpoint behavior.
     84    */
     85   static <T> GeneralRange<T> range(Comparator<? super T> comparator, @Nullable T lower,
     86       BoundType lowerType, @Nullable T upper, BoundType upperType) {
     87     return new GeneralRange<T>(comparator, true, lower, lowerType, true, upper, upperType);
     88   }
     89 
     90   private final Comparator<? super T> comparator;
     91   private final boolean hasLowerBound;
     92   @Nullable
     93   private final T lowerEndpoint;
     94   private final BoundType lowerBoundType;
     95   private final boolean hasUpperBound;
     96   @Nullable
     97   private final T upperEndpoint;
     98   private final BoundType upperBoundType;
     99 
    100   private GeneralRange(Comparator<? super T> comparator, boolean hasLowerBound,
    101       @Nullable T lowerEndpoint, BoundType lowerBoundType, boolean hasUpperBound,
    102       @Nullable T upperEndpoint, BoundType upperBoundType) {
    103     this.comparator = checkNotNull(comparator);
    104     this.hasLowerBound = hasLowerBound;
    105     this.hasUpperBound = hasUpperBound;
    106     this.lowerEndpoint = lowerEndpoint;
    107     this.lowerBoundType = checkNotNull(lowerBoundType);
    108     this.upperEndpoint = upperEndpoint;
    109     this.upperBoundType = checkNotNull(upperBoundType);
    110 
    111     if (hasLowerBound) {
    112       comparator.compare(lowerEndpoint, lowerEndpoint);
    113     }
    114     if (hasUpperBound) {
    115       comparator.compare(upperEndpoint, upperEndpoint);
    116     }
    117     if (hasLowerBound && hasUpperBound) {
    118       int cmp = comparator.compare(lowerEndpoint, upperEndpoint);
    119       // be consistent with Range
    120       checkArgument(cmp <= 0, "lowerEndpoint (%s) > upperEndpoint (%s)", lowerEndpoint,
    121           upperEndpoint);
    122       if (cmp == 0) {
    123         checkArgument(lowerBoundType != OPEN | upperBoundType != OPEN);
    124       }
    125     }
    126   }
    127 
    128   Comparator<? super T> comparator() {
    129     return comparator;
    130   }
    131 
    132   boolean hasLowerBound() {
    133     return hasLowerBound;
    134   }
    135 
    136   boolean hasUpperBound() {
    137     return hasUpperBound;
    138   }
    139 
    140   boolean isEmpty() {
    141     return (hasUpperBound() && tooLow(getUpperEndpoint()))
    142         || (hasLowerBound() && tooHigh(getLowerEndpoint()));
    143   }
    144 
    145   boolean tooLow(@Nullable T t) {
    146     if (!hasLowerBound()) {
    147       return false;
    148     }
    149     T lbound = getLowerEndpoint();
    150     int cmp = comparator.compare(t, lbound);
    151     return cmp < 0 | (cmp == 0 & getLowerBoundType() == OPEN);
    152   }
    153 
    154   boolean tooHigh(@Nullable T t) {
    155     if (!hasUpperBound()) {
    156       return false;
    157     }
    158     T ubound = getUpperEndpoint();
    159     int cmp = comparator.compare(t, ubound);
    160     return cmp > 0 | (cmp == 0 & getUpperBoundType() == OPEN);
    161   }
    162 
    163   boolean contains(@Nullable T t) {
    164     return !tooLow(t) && !tooHigh(t);
    165   }
    166 
    167   /**
    168    * Returns the intersection of the two ranges, or an empty range if their intersection is empty.
    169    */
    170   GeneralRange<T> intersect(GeneralRange<T> other) {
    171     checkNotNull(other);
    172     checkArgument(comparator.equals(other.comparator));
    173 
    174     boolean hasLowBound = this.hasLowerBound;
    175     @Nullable
    176     T lowEnd = getLowerEndpoint();
    177     BoundType lowType = getLowerBoundType();
    178     if (!hasLowerBound()) {
    179       hasLowBound = other.hasLowerBound;
    180       lowEnd = other.getLowerEndpoint();
    181       lowType = other.getLowerBoundType();
    182     } else if (other.hasLowerBound()) {
    183       int cmp = comparator.compare(getLowerEndpoint(), other.getLowerEndpoint());
    184       if (cmp < 0 || (cmp == 0 && other.getLowerBoundType() == OPEN)) {
    185         lowEnd = other.getLowerEndpoint();
    186         lowType = other.getLowerBoundType();
    187       }
    188     }
    189 
    190     boolean hasUpBound = this.hasUpperBound;
    191     @Nullable
    192     T upEnd = getUpperEndpoint();
    193     BoundType upType = getUpperBoundType();
    194     if (!hasUpperBound()) {
    195       hasUpBound = other.hasUpperBound;
    196       upEnd = other.getUpperEndpoint();
    197       upType = other.getUpperBoundType();
    198     } else if (other.hasUpperBound()) {
    199       int cmp = comparator.compare(getUpperEndpoint(), other.getUpperEndpoint());
    200       if (cmp > 0 || (cmp == 0 && other.getUpperBoundType() == OPEN)) {
    201         upEnd = other.getUpperEndpoint();
    202         upType = other.getUpperBoundType();
    203       }
    204     }
    205 
    206     if (hasLowBound && hasUpBound) {
    207       int cmp = comparator.compare(lowEnd, upEnd);
    208       if (cmp > 0 || (cmp == 0 && lowType == OPEN && upType == OPEN)) {
    209         // force allowed empty range
    210         lowEnd = upEnd;
    211         lowType = OPEN;
    212         upType = CLOSED;
    213       }
    214     }
    215 
    216     return new GeneralRange<T>(comparator, hasLowBound, lowEnd, lowType, hasUpBound, upEnd, upType);
    217   }
    218 
    219   @Override
    220   public boolean equals(@Nullable Object obj) {
    221     if (obj instanceof GeneralRange) {
    222       GeneralRange<?> r = (GeneralRange<?>) obj;
    223       return comparator.equals(r.comparator) && hasLowerBound == r.hasLowerBound
    224           && hasUpperBound == r.hasUpperBound && getLowerBoundType().equals(r.getLowerBoundType())
    225           && getUpperBoundType().equals(r.getUpperBoundType())
    226           && Objects.equal(getLowerEndpoint(), r.getLowerEndpoint())
    227           && Objects.equal(getUpperEndpoint(), r.getUpperEndpoint());
    228     }
    229     return false;
    230   }
    231 
    232   @Override
    233   public int hashCode() {
    234     return Objects.hashCode(comparator, getLowerEndpoint(), getLowerBoundType(), getUpperEndpoint(),
    235         getUpperBoundType());
    236   }
    237 
    238   private transient GeneralRange<T> reverse;
    239 
    240   /**
    241    * Returns the same range relative to the reversed comparator.
    242    */
    243   GeneralRange<T> reverse() {
    244     GeneralRange<T> result = reverse;
    245     if (result == null) {
    246       result = new GeneralRange<T>(
    247           Ordering.from(comparator).reverse(), hasUpperBound, getUpperEndpoint(),
    248           getUpperBoundType(), hasLowerBound, getLowerEndpoint(), getLowerBoundType());
    249       result.reverse = this;
    250       return this.reverse = result;
    251     }
    252     return result;
    253   }
    254 
    255   @Override
    256   public String toString() {
    257     return new StringBuilder()
    258         .append(comparator)
    259         .append(":")
    260         .append(lowerBoundType == CLOSED ? '[' : '(')
    261         .append(hasLowerBound ? lowerEndpoint : "-\u221e")
    262         .append(',')
    263         .append(hasUpperBound ? upperEndpoint : "\u221e")
    264         .append(upperBoundType == CLOSED ? ']' : ')')
    265         .toString();
    266   }
    267 
    268   T getLowerEndpoint() {
    269     return lowerEndpoint;
    270   }
    271 
    272   BoundType getLowerBoundType() {
    273     return lowerBoundType;
    274   }
    275 
    276   T getUpperEndpoint() {
    277     return upperEndpoint;
    278   }
    279 
    280   BoundType getUpperBoundType() {
    281     return upperBoundType;
    282   }
    283 }
    284