blob: 03128caa1efd2933e7943b5bf4fb67350b92869d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.sis.measure;
import java.util.Objects;
import java.util.Formatter;
import java.util.Formattable;
import java.util.FormattableFlags;
import java.io.Serializable;
import javax.measure.Unit;
import org.apache.sis.internal.util.Strings;
import org.apache.sis.util.collection.CheckedContainer;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Emptiable;
import org.apache.sis.util.Numbers;
/**
* A set of minimum and maximum values of a certain class, allowing
* a user to determine if a value of the same class is contained inside the range.
* The minimum and maximum values do not have to be included in the range, and
* can be null. If the minimum or maximum values are null, the range is said to
* be unbounded on that endpoint. If both the minimum and maximum are null,
* the range is completely unbounded and all values of that class are contained
* within the range. Null values are always considered <em>exclusive</em>,
* since iterations over the values will never reach the infinite endpoint.
*
* <p>The minimal and maximal values (the <cite>endpoints</cite>) may be inclusive or exclusive.
* Numeric ranges where both endpoints are inclusive are called <cite>closed intervals</cite>
* and are represented by square brackets, for example "{@code [0 … 255]}".
* Numeric ranges where both endpoints are exclusive are called <cite>open intervals</cite>
* and are represented by parenthesis, for example "{@code (0 … 256)}".</p>
*
* <h2>Type and value of range elements</h2>
* To be a member of a {@code Range}, the {@code <E>} type defining the range must implement the
* {@link Comparable} interface. All argument values given to the methods of this class shall be
* or contain instances of that {@code <E>} type. The type is enforced by parameterized type,
* but some subclasses may put additional constraints. For example {@link MeasurementRange} will
* additionally checks the units of measurement. Consequently every methods defined in this class
* may throw an {@link IllegalArgumentException} if a given argument does not met some constraint
* beyond the type.
*
* <h2>Relationship with ISO 19123 definition of range</h2>
* The ISO 19123 standard (<cite>Coverage geometry and functions</cite>) defines the range as the set
* (either finite or transfinite) of feature attribute
* values associated by a function (the coverage) with the
* elements of the coverage domain. In other words, if we see a coverage as a function, then a range
* is the set of possible return values.
*
* <p>The characteristics of the spatial domain are defined by the ISO 19123 standard whereas the
* characteristics of the attribute range are not part of that standard. In Apache SIS, those
* characteristics are described by the {@code SampleDimension} class,
* which may contain one or many {@code Range} instances. Consequently this {@code Range} class
* is closely related, but not identical, to the ISO 19123 definition or range.</p>
*
* <p>Ranges are not necessarily numeric. Numeric and non-numeric ranges can be associated to
* discrete coverages, while typically only
* numeric ranges can be associated to continuous coverages.</p>
*
* <h2>Immutability and thread safety</h2>
* This class and the {@link NumberRange} / {@link MeasurementRange} subclasses are immutable,
* and thus inherently thread-safe. Other subclasses may or may not be immutable, at implementation choice.
* But implementers are encouraged to make sure that all subclasses remain immutable for more predictable behavior.
*
* @author Joe White
* @author Martin Desruisseaux (Geomatys)
* @author Jody Garnett (for parameterized type inspiration)
* @version 1.0
*
* @param <E> the type of range elements, typically a {@link Number} subclass or {@link java.util.Date}.
*
* @see RangeFormat
* @see org.apache.sis.util.collection.RangeSet
*
* @since 0.3
* @module
*/
public class Range<E extends Comparable<? super E>> implements CheckedContainer<E>, Formattable, Emptiable, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 603508245068333284L;
/**
* The base type of elements in this range.
*
* @see #getElementType()
*/
final Class<E> elementType;
/**
* The minimal and maximal values.
*/
final E minValue, maxValue;
/**
* Whether the minimal or maximum value is included.
*/
final boolean isMinIncluded, isMaxIncluded;
/**
* Constructs a range with the same type and the same values than the specified range.
* This is a copy constructor.
*
* @param range the range to copy.
*/
public Range(final Range<E> range) {
elementType = range.elementType;
minValue = range.minValue;
isMinIncluded = range.isMinIncluded;
maxValue = range.maxValue;
isMaxIncluded = range.isMaxIncluded;
assert validate() : elementType;
}
/**
* Creates a new range bounded by the given endpoint values.
* If the given minimum value is greater than the maximum value, then the range {@linkplain #isEmpty() is empty}.
*
* <div class="note"><b>Assertion:</b>
* This constructor verifies the {@code minValue} and {@code maxValue} arguments type if Java assertions
* are enabled. This verification is not performed in normal execution because theoretically unnecessary
* unless Java generic types have been tricked.</div>
*
* @param elementType the base type of the range elements.
* @param minValue the minimal value, or {@code null} if none.
* @param isMinIncluded {@code true} if the minimal value is inclusive, or {@code false} if exclusive.
* @param maxValue the maximal value, or {@code null} if none.
* @param isMaxIncluded {@code true} if the maximal value is inclusive, or {@code false} if exclusive.
*/
public Range(final Class<E> elementType,
final E minValue, final boolean isMinIncluded,
final E maxValue, final boolean isMaxIncluded)
{
ArgumentChecks.ensureNonNull("elementType", elementType);
/*
* The 'isMin/Maxincluded' flags must be forced to 'false' if 'minValue' or 'maxValue'
* are null. This is required for proper working of algorithms implemented in this class.
*/
this.elementType = elementType;
this.minValue = minValue;
this.isMinIncluded = isMinIncluded && (minValue != null);
this.maxValue = maxValue;
this.isMaxIncluded = isMaxIncluded && (maxValue != null);
assert validate() : elementType;
}
/**
* Creates a new range using the same element type than this range. This method will
* be overridden by subclasses in order to create a range of a more specific type.
*
* <div class="note"><b>API note:</b>
* This method is invoked by all operations (union, intersection, <i>etc.</i>) that may create a new range.
* But despite this fact, the return type of those methods are nailed down to {@code Range} (i.e. subclasses
* shall not override the above-cited operations with covariant return type) because those operations may return
* the given argument directly, and we have no guarantees on the type of those arguments.</div>
*/
Range<E> create(final E minValue, final boolean isMinIncluded,
final E maxValue, final boolean isMaxIncluded)
{
return new Range<>(elementType, minValue, isMinIncluded, maxValue, isMaxIncluded);
}
/**
* Returns an initially empty array of {@code getClass()} type and of the given length.
* This method is overridden by subclasses in order to create arrays of more specific type.
* This method is invoked by the {@link #subtract(Range)} method. It is okay to use the new
* array only if the ranges to store in that array are only {@code this} or new ranges created
* by the {@link #create(Comparable, boolean, Comparable, boolean)} method - otherwise we may
* get an {@link ArrayStoreException}.
*/
@SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation.
Range<E>[] newArray(final int length) {
return new Range[length];
}
/**
* To be overridden by {@link MeasurementRange} only.
*
* @return the unit of measurement, or {@code null}.
*/
Unit<?> unit() {
return null;
}
/**
* Invoked by the constructors in order to ensure that the argument are of valid types.
* This check is performed only when assertions are enabled. This test is not needed in
* normal execution if the users do not bypass the checks performed by generic types.
*/
private boolean validate() {
ArgumentChecks.ensureCanCast("minValue", elementType, minValue);
ArgumentChecks.ensureCanCast("maxValue", elementType, maxValue);
return Comparable.class.isAssignableFrom(elementType);
}
/**
* Returns the base type of elements in this range.
* This is the type specified at construction time.
*/
@Override
public Class<E> getElementType() {
return elementType;
}
/**
* Returns the minimal value, or {@code null} if this range has no lower limit.
* If non-null, the returned value is either inclusive or exclusive depending on
* the boolean returned by {@link #isMinIncluded()}.
*
* @return the minimal value, or {@code null} if this range is unbounded on the lower side.
*/
public E getMinValue() {
return minValue;
}
/**
* Returns {@code true} if the {@linkplain #getMinValue() minimal value} is inclusive,
* or {@code false} if exclusive. Note that {@code null} values are always considered
* exclusive.
*
* @return {@code true} if the minimal value is inclusive, or {@code false} if exclusive.
*/
public boolean isMinIncluded() {
return isMinIncluded;
}
/**
* Returns the maximal value, or {@code null} if this range has no upper limit.
* If non-null, the returned value is either inclusive or exclusive depending on
* the boolean returned by {@link #isMaxIncluded()}.
*
* @return the maximal value, or {@code null} if this range is unbounded on the upper side.
*/
public E getMaxValue() {
return maxValue;
}
/**
* Returns {@code true} if the {@linkplain #getMaxValue() maximal value} is inclusive,
* or {@code false} if exclusive. Note that {@code null} values are always considered
* exclusive.
*
* @return {@code true} if the maximal value is inclusive, or {@code false} if exclusive.
*/
public boolean isMaxIncluded() {
return isMaxIncluded;
}
/**
* Returns {@code true} if this range is empty. A range is empty if the
* {@linkplain #getMinValue() minimum value} is greater than the
* {@linkplain #getMaxValue() maximum value}, or if they are equal while
* at least one of them is exclusive.
*
* <div class="note"><b>API note:</b>
* This method is final because often used by the internal implementation.
* Making the method final ensures that the other methods behave consistently.</div>
*
* @return {@code true} if this range is empty.
*/
@Override
public final boolean isEmpty() {
if (minValue == null || maxValue == null) {
return false; // Unbounded: can not be empty.
}
final int c = minValue.compareTo(maxValue);
if (c < 0) {
return false; // Minimum is smaller than maximum.
}
// If min and max are equal, then the range is empty if at least one of them is exclusive.
return (c != 0) || !isMinIncluded || !isMaxIncluded;
}
/**
* Returns {@code true} if this range is both left-bounded and right-bounded.
* A {@code true} return value guarantees that:
*
* <ol>
* <li>both {@link #getMinValue()} and {@link #getMaxValue()} will return non-null values;</li>
* <li>if minimum and maximum values are numbers, then those numbers are finite.</li>
* </ol>
*
* @return whether this range is left- and right-bounded.
*
* @since 1.0
*/
public boolean isBounded() {
return minValue != null && maxValue != null;
}
/**
* Returns {@code true} if this range contains the given value. A range never contains the
* {@code null} value. This is consistent with the <a href="#skip-navbar_top">class javadoc</a>
* stating that null {@linkplain #getMinValue() minimum} or {@linkplain #getMaxValue() maximum}
* values are exclusive.
*
* @param value the value to check for inclusion in this range.
* @return {@code true} if the given value is included in this range.
*/
public boolean contains(final E value) {
if (value == null) {
return false;
}
/*
* Implementation note: when testing for inclusion or intersection in a range
* (or in a rectangle, cube, etc.), it is often easier to test when we do not
* have inclusion than to test for inclusion. So we test when to return false
* and if no such test pass, we can return true.
*
* We consistently use min/maxValue.compareTo(value) in this class rather than
* the opposite argument order (namely value.compareTo(min/maxValue)) in the
* hope to reduce the risk of inconsistent behavior if usera pass different
* sub-classes for the 'value' argument with different implementations of the
* 'compareTo' method. Intead than using those user implementations, we always
* use the implementations provided by min/maxValue.
*/
if (minValue != null) {
final int c = minValue.compareTo(value);
if (isMinIncluded ? (c > 0) : (c >= 0)) {
return false;
}
}
if (maxValue != null) {
final int c = maxValue.compareTo(value);
if (isMaxIncluded ? (c < 0) : (c <= 0)) {
return false;
}
}
return true;
}
/**
* Returns {@code true} if the supplied range is fully contained within this range.
*
* @param range the range to check for inclusion in this range.
* @return {@code true} if the given range is included in this range.
* @throws IllegalArgumentException if the given range is incompatible,
* for example because of incommensurable units of measurement.
*/
public boolean contains(final Range<? extends E> range) {
/*
* We could implement this method as below:
*
* return contains(range.minValue) && contains(range.maxValue);
*
* However the above code performs more comparisons than necessary,
* since it implicitly performs the following redundant checks:
*
* (range.minValue < maxValue) redundant with (range.maxValue < maxValue)
* (range.maxValue > minValue) redundant with (range.minValue > minValue)
*
* We can implement this method with less comparisons as below:
*
* return minValue.compareTo(range.minValue) <= 0 &&
* maxValue.compareTo(range.maxValue) >= 0;
*
* However we still have a little bit of additional checks to perform for the
* inclusion status of both ranges. Since the same checks will be needed for
* intersection methods, we factor out the comparisons in 'compareMinTo' and
* 'compareMaxTo' methods.
*/
return (compareMinTo(range.minValue, range.isMinIncluded ? 0 : -1) <= 0) &&
(compareMaxTo(range.maxValue, range.isMaxIncluded ? 0 : +1) >= 0);
}
/**
* Returns {@code true} if this range intersects the given range.
*
* @param range the range to check for intersection with this range.
* @return {@code true} if the given range intersects this range.
* @throws IllegalArgumentException if the given range is incompatible,
* for example because of incommensurable units of measurement.
*/
public boolean intersects(final Range<? extends E> range) {
return (compareMinTo(range.maxValue, range.isMaxIncluded ? 0 : +1) <= 0) &&
(compareMaxTo(range.minValue, range.isMinIncluded ? 0 : -1) >= 0);
}
/**
* Returns the intersection between this range and the given range.
*
* @param range the range to intersect.
* @return the intersection of this range with the given range.
* @throws IllegalArgumentException if the given range is incompatible,
* for example because of incommensurable units of measurement.
*/
public Range<E> intersect(final Range<E> range) {
if (range.isEmpty()) return range;
if (this .isEmpty()) return this;
/*
* For two ranges [L₁ … H₁] and [L₂ … H₂], the intersection is given by
* ([max(L₁, L₂) … min(H₁, H₂)]). Only two comparisons is needed.
*
* There is a small complication since we shall also handle the inclusive states.
* so instead of extracting the minimal and maximal values directly, we will
* find which range contains the highest minimal value, and which range contains
* the smallest maximal value. If we find the same range in both case (which can
* be either 'this' or 'range), return that range. Otherwise we need to create a
* new one.
*/
final Range<E> intersect, min, max;
min = compareMinTo(range.minValue, range.isMinIncluded ? 0 : -1) < 0 ? range : this;
max = compareMaxTo(range.maxValue, range.isMaxIncluded ? 0 : +1) > 0 ? range : this;
if (min == max) {
intersect = min;
} else {
intersect = create(min.minValue, min.isMinIncluded, max.maxValue, max.isMaxIncluded);
}
assert intersect.isEmpty() == !intersects(range) : intersect;
return intersect;
}
/**
* Returns the union of this range with the given range.
*
* @param range the range to add to this range.
* @return the union of this range with the given range.
* @throws IllegalArgumentException if the given range is incompatible,
* for example because of incommensurable units of measurement.
*/
public Range<E> union(final Range<E> range) {
if (range.isEmpty()) return this;
if (this .isEmpty()) return range;
final Range<E> union, min, max;
min = compareMinTo(range.minValue, range.isMinIncluded ? 0 : -1) > 0 ? range : this;
max = compareMaxTo(range.maxValue, range.isMaxIncluded ? 0 : +1) < 0 ? range : this;
if (min == max) {
union = min;
} else {
union = create(min.minValue, min.isMinIncluded, max.maxValue, max.isMaxIncluded);
}
assert union.contains(min) : min;
assert union.contains(max) : max;
return union;
}
/**
* Returns the range of values that are in this range but not in the given range.
* This method returns an array of length 0, 1 or 2:
*
* <ul>
* <li>If the given range contains fully this range, returns an array of length 0.</li>
* <li>If the given range is in the middle of this range, then the subtraction results in
* two disjoint ranges which will be returned as two elements in the array.</li>
* <li>Otherwise returns an array of length 1.</li>
* </ul>
*
* @param range the range to subtract.
* @return this range without the given range, as an array of length 0, 1 or 2.
* @throws IllegalArgumentException if the given range is incompatible,
* for example because of incommensurable units of measurement.
*/
public Range<E>[] subtract(final Range<E> range) {
/*
* Implementation note: never store the 'range' argument value in the array
* returned by 'newArray(int)', otherwise we may get an ArrayStoreException.
*/
final Range<E> subtract;
if (!intersects(range)) {
subtract = this;
} else {
final boolean clipMin = compareMinTo(range.minValue, range.isMinIncluded ? 0 : -1) >= 0;
final boolean clipMax = compareMaxTo(range.maxValue, range.isMaxIncluded ? 0 : +1) <= 0;
if (clipMin) {
if (clipMax) {
// The given range contains fully this range.
assert range.contains(this) : range;
return newArray(0);
}
subtract = create(range.maxValue, !range.isMaxIncluded, maxValue, isMaxIncluded);
} else {
if (!clipMax) {
final Range<E>[] array = newArray(2);
array[0] = create(minValue, isMinIncluded, range.minValue, !range.isMinIncluded);
array[1] = create(range.maxValue, !range.isMaxIncluded, maxValue, isMaxIncluded);
return array;
}
subtract = create(minValue, isMinIncluded, range.minValue, !range.isMinIncluded);
}
}
assert contains(subtract) : subtract;
assert !subtract.intersects(range) : subtract;
final Range<E>[] array = newArray(1);
array[0] = subtract;
return array;
}
/**
* Compares the {@linkplain #getMinValue() minimum value} of this range with the given endpoint of
* another range. Since the given value is either the minimal or maximal value of another range,
* it may be inclusive or exclusive. The latter is specified by {@code position} as below:
*
* <ul>
* <li> 0 if {@code value} is inclusive.</li>
* <li>-1 if {@code value} is exclusive and lower than the inclusive values of the other range.</li>
* <li>+1 if {@code value} is exclusive and higher than the inclusive values of the other range.</li>
* </ul>
*
* Note that the non-zero position shall be exactly -1 or +1, not arbitrary negative or positive.
*
* @param value an endpoint value of the other range to be compared to the minimal value of this range.
* @param position the position of {@code value} relative to the inclusive values of the other range.
* @return position (-, + or 0) of the inclusive values of this range compared to the other range.
*
* @see #contains(Range)
*/
private int compareMinTo(final E value, int position) {
/*
* Check for infinite values. If the given value is infinite, it can be either positive or
* negative infinity, which we can infer from the 'position' argument. Note that 'position'
* can not be 0 in such case, since infinities are always exclusive in this class.
*/
if (minValue == null) {
return (value == null) ? 0 : -1;
}
if (value == null) {
return -position;
}
/*
* Compare the two finite values. If they are not equal, we are done regardless the
* inclusion states, because the difference between included and excluded values is
* considered smaller than any quantity we can represent.
*/
final int c = minValue.compareTo(value);
if (c != 0) {
return c;
}
/*
* The two values are equal. If the 'minValue' of this range is inclusive, then the given
* 'value' is directly at the "right" place (the beginning of the interior of this range),
* so the 'position' argument gives directly the position of the "true minValue" relative
* to the interior of the other range.
*
* But if 'minValue' is exclusive, then the "true minValue" of this range is one position
* to the right (where "position" is a counter for an infinitely small quantity, similar
* to 'dx' in calculus). The effect is to return 0 if the given 'value' is also exclusive
* and lower than the interior of the other range (position == -1), and a positive value
* in all other cases.
*/
if (!isMinIncluded) {
position++;
}
return position;
}
/**
* Compares the {@linkplain #getMaxValue() maximum value} of this range with the given endpoint
* of another range. See the comment in {@link #compareMinTo(Comparable, int)} for more details.
*/
private int compareMaxTo(final E value, int position) {
if (maxValue == null) {
return (value == null) ? 0 : +1;
}
if (value == null) {
return -position;
}
final int c = maxValue.compareTo(value);
if (c != 0) {
return c;
}
if (!isMaxIncluded) {
position--;
}
return position;
}
/**
* Compares this range with the given object for equality.
* Two ranges are considered equal if they met the following conditions:
*
* <ul>
* <li>They are of the same {@linkplain #getClass() class}.</li>
* <li>They have the same {@linkplain #getElementType() element type}.</li>
* <li>Both ranges {@linkplain #isEmpty() are empty}, <strong>or</strong> (if at least one range is non-empty):
* <ul>
* <li>They have equal {@linkplain #getMinValue() minimum} value in the sense of {@link Object#equals(Object)}.</li>
* <li>They have equal {@linkplain #getMaxValue() maximum} value in the sense of {@link Object#equals(Object)}.</li>
* <li>They have equal {@linkplain #isMinIncluded() inclusive minimum} flag.</li>
* <li>They have equal {@linkplain #isMaxIncluded() inclusive maximum} flag.</li>
* </ul>
* <li>Any other requirement added by subclasses.
* In particular {@link MeasurementRange} compares also the units of measurement.</li>
* </ul>
*
* Note that this method may return {@code true} even if the bounds are not strictly identical.
* In particular this method returns {@code true} if the ranges are empty regardless their minimum and maximum values,
* and also returns {@code true} if the bounds are wrappers for some {@link Float#NaN} or {@link Double#NaN} values
* even if their {@linkplain Double#doubleToRawLongBits(double) raw bits pattern} are not the same.
* The latter is because {@link Float#equals(Object)} and {@link Double#equals(Object)} consider all NaN values as equal.
*
* @param object the object to compare with this range for equality.
* @return {@code true} if the given object is equal to this range.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (object != null && object.getClass() == getClass()) {
final Range<?> other = (Range<?>) object;
if (Objects.equals(elementType, other.elementType)) {
if (isEmpty()) {
return other.isEmpty();
}
return Objects.equals(minValue, other.minValue) &&
Objects.equals(maxValue, other.maxValue) &&
isMinIncluded == other.isMinIncluded &&
isMaxIncluded == other.isMaxIncluded;
}
}
return false;
}
/**
* Returns a hash code value for this range.
*/
@Override
public int hashCode() {
int hash = elementType.hashCode();
if (!isEmpty()) {
hash = 13 * hash + Objects.hashCode(minValue);
hash = 13 * hash + Objects.hashCode(maxValue);
hash += isMinIncluded ? 17 : 37;
hash += isMaxIncluded ? 1231 : 1237;
}
return hash ^ (int) serialVersionUID;
}
/**
* Returns {@code true} if the given number is formatted with only one character.
* We will use less space if the minimum and maximum values are formatted using
* only one digit. This method assumes that we have verified that the element type
* is an integer type before to invoke this method.
*/
private static boolean isCompact(final Comparable<?> value, final boolean ifNull) {
if (value == null) {
return ifNull;
}
final long n = ((Number) value).longValue();
return n >= 0 && n < 10;
}
/**
* Returns a unlocalized string representation of this range. This method complies to the format
* described in the <a href="https://en.wikipedia.org/wiki/ISO_31-11">ISO 31-11</a> standard,
* except that the minimal and maximal values are separated by the "{@code …}" character
* instead of coma. More specifically, the string representation is defined as below:
*
* <ul>
* <li>If the range {@linkplain #isEmpty() is empty}, then this method returns "{@code {}}".</li>
* <li>Otherwise if the minimal value is equal to the maximal value, then the string
* representation of that value is returned inside braces as in "{@code {value}}".</li>
* <li>Otherwise the string representation of the minimal and maximal values are formatted
* like "{@code [min … max]}" for inclusive endpoints or "{@code (min … max)}" for exclusive
* endpoints, or a mix of both styles. The "{@code ∞}" symbol is used in place of
* {@code min} or {@code max} for unbounded ranges.</li>
* </ul>
*
* If this range is a {@link MeasurementRange}, then the {@linkplain Unit unit of measurement}
* is appended to the above string representation except for empty ranges.
*
* @see RangeFormat
* @see <a href="https://en.wikipedia.org/wiki/ISO_31-11">Wikipedia: ISO 31-11</a>
*/
@Override
public String toString() {
if (isEmpty()) {
return "{}";
}
final StringBuilder buffer = new StringBuilder(20);
if (minValue != null && minValue.equals(maxValue)) {
buffer.append('{').append(minValue).append('}');
} else {
buffer.append(isMinIncluded ? '[' : '(');
if (minValue == null) {
buffer.append("−∞");
} else {
buffer.append(minValue);
}
// Compact representation for integers, more space for real numbers.
if (Numbers.isInteger(elementType) && isCompact(minValue, false) && isCompact(maxValue, true)) {
buffer.append('…');
} else {
buffer.append(" … ");
}
if (maxValue == null) {
buffer.append('∞');
} else {
buffer.append(maxValue);
}
buffer.append(isMaxIncluded ? ']' : ')');
}
final Unit<?> unit = unit();
if (unit != null) {
final String symbol = unit.toString();
if (symbol != null && !symbol.isEmpty()) {
if (Character.isLetterOrDigit(symbol.codePointAt(0))) {
buffer.append(' ');
}
buffer.append(symbol);
}
}
return buffer.toString();
}
/**
* Formats this range using the provided formatter. This method is invoked when a
* {@code Range} object is formatted using the {@code "%s"} conversion specifier of
* {@link Formatter}. Users don't need to invoke this method explicitly.
*
* <p>If the alternate flags is present (as in {@code "%#s"}), then the range will
* be formatted using the {@linkplain RangeFormat#isAlternateForm() alternate form}
* for exclusive bounds.</p>
*
* @param formatter the formatter in which to format this range.
* @param flags {@link FormattableFlags#LEFT_JUSTIFY} for left alignment, or 0 for right alignment.
* @param width minimal number of characters to write, padding with {@code ' '} if necessary.
* @param precision maximal number of characters to write, or -1 if no limit.
*/
@Override
public void formatTo(final Formatter formatter, final int flags, final int width, int precision) {
final String value;
if (precision == 0) {
value = "";
} else {
final RangeFormat format = new RangeFormat(formatter.locale(), elementType);
format.setAlternateForm((flags & FormattableFlags.ALTERNATE) != 0);
value = format.format(this, new StringBuffer(), null).toString();
}
Strings.formatTo(formatter, flags, width, precision, value);
}
}