/**
   * Returns the smallest range that includes this range and the inclusive range specified by {@code
   * [lower, upper]}.
   *
   * <p>See {@link #extend(Range)} for more details.
   *
   * @param lower a non-{@code null} {@code T} reference
   * @param upper a non-{@code null} {@code T} reference
   * @return the extension of this range and the other range.
   * @throws NullPointerException if {@code lower} or {@code upper} was {@code null}
   */
  public Range<T> extend(T lower, T upper) {
    checkNotNull(lower, "lower must not be null");
    checkNotNull(upper, "upper must not be null");

    int cmpLower = lower.compareTo(mLower);
    int cmpUpper = upper.compareTo(mUpper);

    if (cmpLower >= 0 && cmpUpper <= 0) {
      // this inludes other
      return this;
    } else {
      return Range.create(cmpLower >= 0 ? mLower : lower, cmpUpper <= 0 ? mUpper : upper);
    }
  }
  /**
   * Returns the intersection of this range and the inclusive range specified by {@code [lower,
   * upper]}.
   *
   * <p>See {@link #intersect(Range)} for more details.
   *
   * @param lower a non-{@code null} {@code T} reference
   * @param upper a non-{@code null} {@code T} reference
   * @return the intersection of this range and the other range
   * @throws NullPointerException if {@code lower} or {@code upper} was {@code null}
   * @throws IllegalArgumentException if the ranges are disjoint.
   */
  public Range<T> intersect(T lower, T upper) {
    checkNotNull(lower, "lower must not be null");
    checkNotNull(upper, "upper must not be null");

    int cmpLower = lower.compareTo(mLower);
    int cmpUpper = upper.compareTo(mUpper);

    if (cmpLower <= 0 && cmpUpper >= 0) {
      // [lower, upper] includes this
      return this;
    } else {
      return Range.create(cmpLower <= 0 ? mLower : lower, cmpUpper >= 0 ? mUpper : upper);
    }
  }
  /**
   * Returns the smallest range that includes this range and another {@code range}.
   *
   * <p>E.g. if a {@code <} b {@code <} c {@code <} d, the extension of [a, c] and [b, d] ranges is
   * [a, d]. As the endpoints are object references, there is no guarantee which specific endpoint
   * reference is used from the input ranges:
   *
   * <p>E.g. if a {@code ==} a' {@code <} b {@code <} c, the extension of [a, b] and [a', c] ranges
   * could be either [a, c] or ['a, c], where ['a, c] could be either the exact input range, or a
   * newly created range with the same endpoints.
   *
   * @param range a non-{@code null} {@code Range<T>} reference
   * @return the extension of this range and the other range.
   * @throws NullPointerException if {@code range} was {@code null}
   */
  public Range<T> extend(Range<T> range) {
    checkNotNull(range, "range must not be null");

    int cmpLower = range.mLower.compareTo(mLower);
    int cmpUpper = range.mUpper.compareTo(mUpper);

    if (cmpLower <= 0 && cmpUpper >= 0) {
      // other includes this
      return range;
    } else if (cmpLower >= 0 && cmpUpper <= 0) {
      // this inludes other
      return this;
    } else {
      return Range.create(
          cmpLower >= 0 ? mLower : range.mLower, cmpUpper <= 0 ? mUpper : range.mUpper);
    }
  }