001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.temporal;
033
034import java.io.Serializable;
035
036import org.threeten.bp.DateTimeException;
037
038/**
039 * The range of valid values for a date-time field.
040 * <p>
041 * All {@link TemporalField} instances have a valid range of values.
042 * For example, the ISO day-of-month runs from 1 to somewhere between 28 and 31.
043 * This class captures that valid range.
044 * <p>
045 * It is important to be aware of the limitations of this class.
046 * Only the minimum and maximum values are provided.
047 * It is possible for there to be invalid values within the outer range.
048 * For example, a weird field may have valid values of 1, 2, 4, 6, 7, thus
049 * have a range of '1 - 7', despite that fact that values 3 and 5 are invalid.
050 * <p>
051 * Instances of this class are not tied to a specific field.
052 *
053 * <h3>Specification for implementors</h3>
054 * This class is immutable and thread-safe.
055 */
056public final class ValueRange implements Serializable {
057
058    /**
059     * Serialization version.
060     */
061    private static final long serialVersionUID = -7317881728594519368L;
062
063    /**
064     * The smallest minimum value.
065     */
066    private final long minSmallest;
067    /**
068     * The largest minimum value.
069     */
070    private final long minLargest;
071    /**
072     * The smallest maximum value.
073     */
074    private final long maxSmallest;
075    /**
076     * The largest maximum value.
077     */
078    private final long maxLargest;
079
080    /**
081     * Obtains a fixed value range.
082     * <p>
083     * This factory obtains a range where the minimum and maximum values are fixed.
084     * For example, the ISO month-of-year always runs from 1 to 12.
085     *
086     * @param min  the minimum value
087     * @param max  the maximum value
088     * @return the ValueRange for min, max, not null
089     * @throws IllegalArgumentException if the minimum is greater than the maximum
090     */
091    public static ValueRange of(long min, long max) {
092        if (min > max) {
093            throw new IllegalArgumentException("Minimum value must be less than maximum value");
094        }
095        return new ValueRange(min, min, max, max);
096    }
097
098    /**
099     * Obtains a variable value range.
100     * <p>
101     * This factory obtains a range where the minimum value is fixed and the maximum value may vary.
102     * For example, the ISO day-of-month always starts at 1, but ends between 28 and 31.
103     *
104     * @param min  the minimum value
105     * @param maxSmallest  the smallest maximum value
106     * @param maxLargest  the largest maximum value
107     * @return the ValueRange for min, smallest max, largest max, not null
108     * @throws IllegalArgumentException if
109     *     the minimum is greater than the smallest maximum,
110     *  or the smallest maximum is greater than the largest maximum
111     */
112    public static ValueRange of(long min, long maxSmallest, long maxLargest) {
113        return of(min, min, maxSmallest, maxLargest);
114    }
115
116    /**
117     * Obtains a fully variable value range.
118     * <p>
119     * This factory obtains a range where both the minimum and maximum value may vary.
120     *
121     * @param minSmallest  the smallest minimum value
122     * @param minLargest  the largest minimum value
123     * @param maxSmallest  the smallest maximum value
124     * @param maxLargest  the largest maximum value
125     * @return the ValueRange for smallest min, largest min, smallest max, largest max, not null
126     * @throws IllegalArgumentException if
127     *     the smallest minimum is greater than the smallest maximum,
128     *  or the smallest maximum is greater than the largest maximum
129     *  or the largest minimum is greater than the largest maximum
130     */
131    public static ValueRange of(long minSmallest, long minLargest, long maxSmallest, long maxLargest) {
132        if (minSmallest > minLargest) {
133            throw new IllegalArgumentException("Smallest minimum value must be less than largest minimum value");
134        }
135        if (maxSmallest > maxLargest) {
136            throw new IllegalArgumentException("Smallest maximum value must be less than largest maximum value");
137        }
138        if (minLargest > maxLargest) {
139            throw new IllegalArgumentException("Minimum value must be less than maximum value");
140        }
141        return new ValueRange(minSmallest, minLargest, maxSmallest, maxLargest);
142    }
143
144    /**
145     * Restrictive constructor.
146     *
147     * @param minSmallest  the smallest minimum value
148     * @param minLargest  the largest minimum value
149     * @param maxSmallest  the smallest minimum value
150     * @param maxLargest  the largest minimum value
151     */
152    private ValueRange(long minSmallest, long minLargest, long maxSmallest, long maxLargest) {
153        this.minSmallest = minSmallest;
154        this.minLargest = minLargest;
155        this.maxSmallest = maxSmallest;
156        this.maxLargest = maxLargest;
157    }
158
159    //-----------------------------------------------------------------------
160    /**
161     * Is the value range fixed and fully known.
162     * <p>
163     * For example, the ISO day-of-month runs from 1 to between 28 and 31.
164     * Since there is uncertainty about the maximum value, the range is not fixed.
165     * However, for the month of January, the range is always 1 to 31, thus it is fixed.
166     *
167     * @return true if the set of values is fixed
168     */
169    public boolean isFixed() {
170        return minSmallest == minLargest && maxSmallest == maxLargest;
171    }
172
173    //-----------------------------------------------------------------------
174    /**
175     * Gets the minimum value that the field can take.
176     * <p>
177     * For example, the ISO day-of-month always starts at 1.
178     * The minimum is therefore 1.
179     *
180     * @return the minimum value for this field
181     */
182    public long getMinimum() {
183        return minSmallest;
184    }
185
186    /**
187     * Gets the largest possible minimum value that the field can take.
188     * <p>
189     * For example, the ISO day-of-month always starts at 1.
190     * The largest minimum is therefore 1.
191     *
192     * @return the largest possible minimum value for this field
193     */
194    public long getLargestMinimum() {
195        return minLargest;
196    }
197
198    /**
199     * Gets the smallest possible maximum value that the field can take.
200     * <p>
201     * For example, the ISO day-of-month runs to between 28 and 31 days.
202     * The smallest maximum is therefore 28.
203     *
204     * @return the smallest possible maximum value for this field
205     */
206    public long getSmallestMaximum() {
207        return maxSmallest;
208    }
209
210    /**
211     * Gets the maximum value that the field can take.
212     * <p>
213     * For example, the ISO day-of-month runs to between 28 and 31 days.
214     * The maximum is therefore 31.
215     *
216     * @return the maximum value for this field
217     */
218    public long getMaximum() {
219        return maxLargest;
220    }
221
222    //-----------------------------------------------------------------------
223    /**
224     * Checks if all values in the range fit in an {@code int}.
225     * <p>
226     * This checks that all valid values are within the bounds of an {@code int}.
227     * <p>
228     * For example, the ISO month-of-year has values from 1 to 12, which fits in an {@code int}.
229     * By comparison, ISO nano-of-day runs from 1 to 86,400,000,000,000 which does not fit in an {@code int}.
230     * <p>
231     * This implementation uses {@link #getMinimum()} and {@link #getMaximum()}.
232     *
233     * @return true if a valid value always fits in an {@code int}
234     */
235    public boolean isIntValue() {
236        return getMinimum() >= Integer.MIN_VALUE && getMaximum() <= Integer.MAX_VALUE;
237    }
238
239    /**
240     * Checks if the value is within the valid range.
241     * <p>
242     * This checks that the value is within the stored range of values.
243     *
244     * @param value  the value to check
245     * @return true if the value is valid
246     */
247    public boolean isValidValue(long value) {
248        return (value >= getMinimum() && value <= getMaximum());
249    }
250
251    /**
252     * Checks if the value is within the valid range and that all values
253     * in the range fit in an {@code int}.
254     * <p>
255     * This method combines {@link #isIntValue()} and {@link #isValidValue(long)}.
256     *
257     * @param value  the value to check
258     * @return true if the value is valid and fits in an {@code int}
259     */
260    public boolean isValidIntValue(long value) {
261        return isIntValue() && isValidValue(value);
262    }
263
264    /**
265     * Checks that the specified value is valid.
266     * <p>
267     * This validates that the value is within the valid range of values.
268     * The field is only used to improve the error message.
269     *
270     * @param value  the value to check
271     * @param field  the field being checked, may be null
272     * @return the value that was passed in
273     * @see #isValidValue(long)
274     */
275    public long checkValidValue(long value, TemporalField field) {
276        if (isValidValue(value) == false) {
277            if (field != null) {
278                throw new DateTimeException("Invalid value for " + field.getName() + " (valid values " + this + "): " + value);
279            } else {
280                throw new DateTimeException("Invalid value (valid values " + this + "): " + value);
281            }
282        }
283        return value;
284    }
285
286    /**
287     * Checks that the specified value is valid and fits in an {@code int}.
288     * <p>
289     * This validates that the value is within the valid range of values and that
290     * all valid values are within the bounds of an {@code int}.
291     * The field is only used to improve the error message.
292     *
293     * @param value  the value to check
294     * @param field  the field being checked, may be null
295     * @return the value that was passed in
296     * @see #isValidIntValue(long)
297     */
298    public int checkValidIntValue(long value, TemporalField field) {
299        if (isValidIntValue(value) == false) {
300            throw new DateTimeException("Invalid int value for " + field.getName() + ": " + value);
301        }
302        return (int) value;
303    }
304
305    //-----------------------------------------------------------------------
306    /**
307     * Checks if this range is equal to another range.
308     * <p>
309     * The comparison is based on the four values, minimum, largest minimum,
310     * smallest maximum and maximum.
311     * Only objects of type {@code ValueRange} are compared, other types return false.
312     *
313     * @param obj  the object to check, null returns false
314     * @return true if this is equal to the other range
315     */
316    @Override
317    public boolean equals(Object obj) {
318        if (obj == this) {
319            return true;
320        }
321        if (obj instanceof ValueRange) {
322            ValueRange other = (ValueRange) obj;
323           return minSmallest == other.minSmallest && minLargest == other.minLargest &&
324                   maxSmallest == other.maxSmallest && maxLargest == other.maxLargest;
325        }
326        return false;
327    }
328
329    /**
330     * A hash code for this range.
331     *
332     * @return a suitable hash code
333     */
334    @Override
335    public int hashCode() {
336        long hash = minSmallest + minLargest << 16 + minLargest >> 48 + maxSmallest << 32 +
337            maxSmallest >> 32 + maxLargest << 48 + maxLargest >> 16;
338        return (int) (hash ^ (hash >>> 32));
339    }
340
341    //-----------------------------------------------------------------------
342    /**
343     * Outputs this range as a {@code String}.
344     * <p>
345     * The format will be '{min}/{largestMin} - {smallestMax}/{max}',
346     * where the largestMin or smallestMax sections may be omitted, together
347     * with associated slash, if they are the same as the min or max.
348     *
349     * @return a string representation of this range, not null
350     */
351    @Override
352    public String toString() {
353        StringBuilder buf = new StringBuilder();
354        buf.append(minSmallest);
355        if (minSmallest != minLargest) {
356            buf.append('/').append(minLargest);
357        }
358        buf.append(" - ").append(maxSmallest);
359        if (maxSmallest != maxLargest) {
360            buf.append('/').append(maxLargest);
361        }
362        return buf.toString();
363    }
364
365}