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 > 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 > 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 < 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> ≤ 0, the first element will be empty, and 344 * the second element will be this range. If <code>length</code> 345 * ≥ {@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 > 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 }