View Javadoc
1   /**
2    * This file Copyright (c) 2015-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.vaadin.gwt.shared;
35  
36  import java.io.Serializable;
37  
38  /**
39   * Represents a span of integer numbers of the type [start, end), provides convenience factory methods and logical operations like intersection,
40   * inclusion check etc.
41   *
42   * This is almost verbatim copy of Vaadin's Range class used for the implementation of the Grid component
43   * (first introduced in Vaadin 7.4).
44   */
45  public final class Range implements Serializable {
46      private final int start;
47      private final int end;
48  
49      /**
50       * Creates a new range between two numbers: <code>[start..end[</code>.
51       *
52       * @param start the start integer, inclusive
53       * @param end the end integer, exclusive
54       * @throws IllegalArgumentException if <code>start &gt; end</code>
55       */
56      private Range(final int start, final int end) throws IllegalArgumentException {
57          if (start > end) {
58              throw new IllegalArgumentException("start must not be greater than end");
59          }
60          this.start = start;
61          this.end = end;
62      }
63  
64      /**
65       * Creates a range object representing a single integer.
66       *
67       * @param integer the number to represent as a range
68       * @return the range represented by <code>integer</code>
69       */
70      public static Range withOnly(final int integer) {
71          return new Range(integer, integer + 1);
72      }
73  
74      /**
75       * Creates a range between two integers.
76       * <p>
77       * The range start is <em>inclusive</em> and the end is <em>exclusive</em>.
78       * So, a range "between" 0 and 5 represents the numbers 0, 1, 2, 3 and 4,
79       * but not 5.
80       *
81       * @param start the start of the the range, inclusive
82       * @param end the end of the range, exclusive
83       * @return a range representing <code>[start..end[</code>
84       * @throws IllegalArgumentException if <code>start &gt; end</code>
85       */
86      public static Range between(final int start, final int end)
87              throws IllegalArgumentException {
88          return new Range(start, end);
89      }
90  
91      /**
92       * Creates a range from a start point, with a given length.
93       *
94       * @param start the first integer to include in the range
95       * @param length the length of the resulting range
96       * @return a range starting from <code>start</code>, with
97       *         <code>length</code> number of integers following
98       * @throws IllegalArgumentException if length &lt; 0
99       */
100     public static Range withLength(final int start, final int length)
101             throws IllegalArgumentException {
102         if (length < 0) {
103             /*
104              * The constructor of Range will throw an exception if start >
105              * start+length (i.e. if length is negative). We're throwing the
106              * same exception type, just with a more descriptive message.
107              */
108             throw new IllegalArgumentException("length must not be negative");
109         }
110         return new Range(start, start + length);
111     }
112 
113     /**
114      * Returns the <em>inclusive</em> start point of this range.
115      *
116      * @return the start point of this range
117      */
118     public int getStart() {
119         return start;
120     }
121 
122     /**
123      * Returns the <em>exclusive</em> end point of this range.
124      *
125      * @return the end point of this range
126      */
127     public int getEnd() {
128         return end;
129     }
130 
131     /**
132      * The number of integers contained in the range.
133      *
134      * @return the number of integers contained in the range
135      */
136     public int length() {
137         return getEnd() - getStart();
138     }
139 
140     /**
141      * Checks whether the range has no elements between the start and end.
142      *
143      * @return <code>true</code> iff the range contains no elements.
144      */
145     public boolean isEmpty() {
146         return getStart() >= getEnd();
147     }
148 
149     /**
150      * Checks whether this range and another range are at least partially
151      * covering the same values.
152      *
153      * @param other the other range to check against
154      * @return <code>true</code> if this and <code>other</code> intersect
155      */
156     public boolean intersects(final Range other) {
157         return getStart() < other.getEnd() && other.getStart() < getEnd();
158     }
159 
160     /**
161      * Checks whether an integer is found within this range.
162      *
163      * @param integer an integer to test for presence in this range
164      * @return <code>true</code> iff <code>integer</code> is in this range
165      */
166     public boolean contains(final int integer) {
167         return getStart() <= integer && integer < getEnd();
168     }
169 
170     /**
171      * Checks whether this range is a subset of another range.
172      *
173      * @return <code>true</code> iff <code>other</code> completely wraps this
174      *         range
175      */
176     public boolean isSubsetOf(final Range other) {
177         if (isEmpty() && other.isEmpty()) {
178             return true;
179         }
180 
181         return other.getStart() <= getStart() && getEnd() <= other.getEnd();
182     }
183 
184     /**
185      * Overlay this range with another one, and partition the ranges according
186      * to how they position relative to each other.
187      * <p>
188      * The three partitions are returned as a three-element Range array:
189      * <ul>
190      * <li>Elements in this range that occur before elements in
191      * <code>other</code>.
192      * <li>Elements that are shared between the two ranges.
193      * <li>Elements in this range that occur after elements in
194      * <code>other</code>.
195      * </ul>
196      *
197      * @param other the other range to act as delimiters.
198      * @return a three-element Range array of partitions depicting the elements
199      *         before (index 0), shared/inside (index 1) and after (index 2).
200      */
201     public Rangeared/Range.html#Range">Range[] partitionWith(final Range other) {
202         final Range[] splitBefore = splitAt(other.getStart());
203         final Range rangeBefore = splitBefore[0];
204         final Range[] splitAfter = splitBefore[1].splitAt(other.getEnd());
205         final Range rangeInside = splitAfter[0];
206         final Range rangeAfter = splitAfter[1];
207         return new Range[]{rangeBefore, rangeInside, rangeAfter};
208     }
209 
210     /**
211      * Get a range that is based on this one, but offset by a number.
212      *
213      * @param offset the number to offset by
214      * @return a copy of this range, offset by <code>offset</code>
215      */
216     public Range offsetBy(final int offset) {
217         if (offset == 0) {
218             return this;
219         } else {
220             return new Range(start + offset, end + offset);
221         }
222     }
223 
224     @Override
225     public String toString() {
226         return " [" + getStart() + ".." + getEnd()
227                 + ")" + (isEmpty() ? " (e)" : "");
228     }
229 
230     @Override
231     public int hashCode() {
232         final int prime = 31;
233         int result = 1;
234         result = prime * result + end;
235         result = prime * result + start;
236         return result;
237     }
238 
239     @Override
240     public boolean equals(final Object obj) {
241         if (this == obj) {
242             return true;
243         }
244         if (obj == null) {
245             return false;
246         }
247         if (getClass() != obj.getClass()) {
248             return false;
249         }
250         final Range="../../../../../../info/magnolia/ui/vaadin/gwt/shared/Range.html#Range">Range other = (Range) obj;
251         if (end != other.end) {
252             return false;
253         }
254         if (start != other.start) {
255             return false;
256         }
257         return true;
258     }
259 
260     /**
261      * Checks whether this range starts before the start of another range.
262      *
263      * @param other the other range to compare against
264      * @return <code>true</code> iff this range starts before the
265      *         <code>other</code>
266      */
267     public boolean startsBefore(final Range other) {
268         return getStart() < other.getStart();
269     }
270 
271     /**
272      * Checks whether this range ends before the start of another range.
273      *
274      * @param other the other range to compare against
275      * @return <code>true</code> iff this range ends before the
276      *         <code>other</code>
277      */
278     public boolean endsBefore(final Range other) {
279         return getEnd() <= other.getStart();
280     }
281 
282     /**
283      * Checks whether this range ends after the end of another range.
284      *
285      * @param other the other range to compare against
286      * @return <code>true</code> iff this range ends after the
287      *         <code>other</code>
288      */
289     public boolean endsAfter(final Range other) {
290         return getEnd() > other.getEnd();
291     }
292 
293     /**
294      * Checks whether this range starts after the end of another range.
295      *
296      * @param other the other range to compare against
297      * @return <code>true</code> iff this range starts after the
298      *         <code>other</code>
299      */
300     public boolean startsAfter(final Range other) {
301         return getStart() >= other.getEnd();
302     }
303 
304     /**
305      * Split the range into two at a certain integer.
306      * <p>
307      * <em>Example:</em> <code>[5..10[.splitAt(7) == [5..7[, [7..10[</code>
308      *
309      * @param integer the integer at which to split the range into two
310      * @return an array of two ranges, with <code>[start..integer[</code> in the
311      *         first element, and <code>[integer..end[</code> in the second
312      *         element.
313      *         <p>
314      *         If {@code integer} is less than {@code start}, [empty,
315      *         {@code this} ] is returned. if <code>integer</code> is equal to
316      *         or greater than {@code end}, [{@code this}, empty] is returned
317      *         instead.
318      */
319     public Range[] splitAt(final int integer) {
320         if (integer < start) {
321             return new Range[]{Range.withLength(start, 0), this};
322         } else if (integer >= end) {
323             return new Range[]{this, Range.withLength(end, 0)};
324         } else {
325             return new Rangeui/vaadin/gwt/shared/Range.html#Range">Range[]{new Range(start, integer),
326                     new Range(integer, end)};
327         }
328     }
329 
330     /**
331      * Split the range into two after a certain number of integers into the
332      * range.
333      * <p>
334      * Calling this method is equivalent to calling
335      * <code>{@link #splitAt(int) splitAt}({@link #getStart()}+length);</code>
336      * <p>
337      * <em>Example:</em>
338      * <code>[5..10[.splitAtFromStart(2) == [5..7[, [7..10[</code>
339      *
340      * @param length the length at which to split this range into two
341      * @return an array of two ranges, having the <code>length</code>-first
342      *         elements of this range, and the second range having the rest. If
343      *         <code>length</code> &leq; 0, the first element will be empty, and
344      *         the second element will be this range. If <code>length</code>
345      *         &geq; {@link #length()}, the first element will be this range,
346      *         and the second element will be empty.
347      */
348     public Range[] splitAtFromStart(final int length) {
349         return splitAt(getStart() + length);
350     }
351 
352     /**
353      * Combines two ranges to create a range containing all values in both
354      * ranges, provided there are no gaps between the ranges.
355      *
356      * @param other the range to combine with this range
357      * @return the combined range
358      * @throws IllegalArgumentException if the two ranges aren't connected
359      */
360     public Range./../../../../../info/magnolia/ui/vaadin/gwt/shared/Range.html#Range">Range combineWith(Range other) throws IllegalArgumentException {
361         if (getStart() > other.getEnd() || other.getStart() > getEnd()) {
362             throw new IllegalArgumentException("There is a gap between " + this
363                     + " and " + other);
364         }
365 
366         return Range.between(Math.min(getStart(), other.getStart()),
367                 Math.max(getEnd(), other.getEnd()));
368     }
369 
370     /**
371      * Creates a range that is expanded the given amounts in both ends.
372      *
373      * @param startDelta the amount to expand by in the beginning of the range
374      * @param endDelta the amount to expand by in the end of the range
375      * @return an expanded range
376      * @throws IllegalArgumentException if the new range would have <code>start &gt; end</code>
377      */
378     public Range expand(int startDelta, int endDelta) throws IllegalArgumentException {
379         return Range.between(getStart() - startDelta, getEnd() + endDelta);
380     }
381 
382     /**
383      * Limits this range to be within the bounds of the provided range.
384      * <p>
385      * This is basically an optimized way of calculating
386      * <code>{@link #partitionWith(Range)}[1]</code> without the overhead of
387      * defining the parts that do not overlap.
388      * <p>
389      * If the two ranges do not intersect, an empty range is returned. There are
390      * no guarantees about the position of that range.
391      *
392      * @param bounds the bounds that the returned range should be limited to
393      * @return a bounded range
394      */
395     public Range../../../../../../info/magnolia/ui/vaadin/gwt/shared/Range.html#Range">Range restrictTo(Range bounds) {
396         boolean startWithin = bounds.contains(getStart());
397         boolean endWithin = bounds.contains(getEnd());
398         boolean boundsWithin = getStart() < bounds.getStart()
399                 && getEnd() >= bounds.getEnd();
400 
401         if (startWithin) {
402             if (endWithin) {
403                 return this;
404             } else {
405                 return Range.between(getStart(), bounds.getEnd());
406             }
407         } else {
408             if (endWithin) {
409                 return Range.between(bounds.getStart(), getEnd());
410             } else if (boundsWithin) {
411                 return bounds;
412             } else {
413                 return Range.withLength(getStart(), 0);
414             }
415         }
416     }
417 }