1 /* 2 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 package javax.print.attribute; 28 29 import java.io.Serializable; 30 import java.util.Vector; 31 32 /** 33 * Class SetOfIntegerSyntax is an abstract base class providing the common 34 * implementation of all attributes whose value is a set of nonnegative 35 * integers. This includes attributes whose value is a single range of integers 36 * and attributes whose value is a set of ranges of integers. 37 * <P> 38 * You can construct an instance of SetOfIntegerSyntax by giving it in "string 39 * form." The string consists of zero or more comma-separated integer groups. 40 * Each integer group consists of either one integer, two integers separated by 41 * a hyphen ({@code -}), or two integers separated by a colon 42 * ({@code :}). Each integer consists of one or more decimal digits 43 * ({@code 0} through {@code 9}). Whitespace characters cannot 44 * appear within an integer but are otherwise ignored. For example: 45 * {@code ""}, {@code "1"}, {@code "5-10"}, {@code "1:2, 4"}. 46 * <P> 47 * You can also construct an instance of SetOfIntegerSyntax by giving it in 48 * "array form." Array form consists of an array of zero or more integer groups 49 * where each integer group is a length-1 or length-2 array of 50 * {@code int}s; for example, {@code int[0][]}, 51 * {@code int[][]{{1}}}, {@code int[][]{{5,10}}}, 52 * {@code int[][]{{1,2},{4}}}. 53 * <P> 54 * In both string form and array form, each successive integer group gives a 55 * range of integers to be included in the set. The first integer in each group 56 * gives the lower bound of the range; the second integer in each group gives 57 * the upper bound of the range; if there is only one integer in the group, the 58 * upper bound is the same as the lower bound. If the upper bound is less than 59 * the lower bound, it denotes a null range (no values). If the upper bound is 60 * equal to the lower bound, it denotes a range consisting of a single value. If 61 * the upper bound is greater than the lower bound, it denotes a range 62 * consisting of more than one value. The ranges may appear in any order and are 63 * allowed to overlap. The union of all the ranges gives the set's contents. 64 * Once a SetOfIntegerSyntax instance is constructed, its value is immutable. 65 * <P> 66 * The SetOfIntegerSyntax object's value is actually stored in "<I>canonical</I> 67 * array form." This is the same as array form, except there are no null ranges; 68 * the members of the set are represented in as few ranges as possible (i.e., 69 * overlapping ranges are coalesced); the ranges appear in ascending order; and 70 * each range is always represented as a length-two array of {@code int}s 71 * in the form {lower bound, upper bound}. An empty set is represented as a 72 * zero-length array. 73 * <P> 74 * Class SetOfIntegerSyntax has operations to return the set's members in 75 * canonical array form, to test whether a given integer is a member of the 76 * set, and to iterate through the members of the set. 77 * 78 * @author David Mendenhall 79 * @author Alan Kaminsky 80 */ 81 public abstract class SetOfIntegerSyntax implements Serializable, Cloneable { 82 83 private static final long serialVersionUID = 3666874174847632203L; 84 85 /** 86 * This set's members in canonical array form. 87 * @serial 88 */ 89 private int[][] members; 90 91 92 /** 93 * Construct a new set-of-integer attribute with the given members in 94 * string form. 95 * 96 * @param members Set members in string form. If null, an empty set is 97 * constructed. 98 * 99 * @exception IllegalArgumentException 100 * (Unchecked exception) Thrown if {@code members} does not 101 * obey the proper syntax. 102 */ 103 protected SetOfIntegerSyntax(String members) { 104 this.members = parse (members); 105 } 106 107 /** 108 * Parse the given string, returning canonical array form. 109 */ 110 private static int[][] parse(String members) { 111 // Create vector to hold int[] elements, each element being one range 112 // parsed out of members. 113 Vector<int[]> theRanges = new Vector<>(); 114 115 // Run state machine over members. 116 int n = (members == null ? 0 : members.length()); 117 int i = 0; 118 int state = 0; 119 int lb = 0; 120 int ub = 0; 121 char c; 122 int digit; 123 while (i < n) { 124 c = members.charAt(i ++); 125 switch (state) { 126 127 case 0: // Before first integer in first group 128 if (Character.isWhitespace(c)) { 129 state = 0; 130 } 131 else if ((digit = Character.digit(c, 10)) != -1) { 132 lb = digit; 133 state = 1; 134 } else { 135 throw new IllegalArgumentException(); 136 } 137 break; 138 139 case 1: // In first integer in a group 140 if (Character.isWhitespace(c)){ 141 state = 2; 142 } else if ((digit = Character.digit(c, 10)) != -1) { 143 lb = 10 * lb + digit; 144 state = 1; 145 } else if (c == '-' || c == ':') { 146 state = 3; 147 } else if (c == ',') { 148 accumulate (theRanges, lb, lb); 149 state = 6; 150 } else { 151 throw new IllegalArgumentException(); 152 } 153 break; 154 155 case 2: // After first integer in a group 156 if (Character.isWhitespace(c)) { 157 state = 2; 158 } 159 else if (c == '-' || c == ':') { 160 state = 3; 161 } 162 else if (c == ',') { 163 accumulate(theRanges, lb, lb); 164 state = 6; 165 } else { 166 throw new IllegalArgumentException(); 167 } 168 break; 169 170 case 3: // Before second integer in a group 171 if (Character.isWhitespace(c)) { 172 state = 3; 173 } else if ((digit = Character.digit(c, 10)) != -1) { 174 ub = digit; 175 state = 4; 176 } else { 177 throw new IllegalArgumentException(); 178 } 179 break; 180 181 case 4: // In second integer in a group 182 if (Character.isWhitespace(c)) { 183 state = 5; 184 } else if ((digit = Character.digit(c, 10)) != -1) { 185 ub = 10 * ub + digit; 186 state = 4; 187 } else if (c == ',') { 188 accumulate(theRanges, lb, ub); 189 state = 6; 190 } else { 191 throw new IllegalArgumentException(); 192 } 193 break; 194 195 case 5: // After second integer in a group 196 if (Character.isWhitespace(c)) { 197 state = 5; 198 } else if (c == ',') { 199 accumulate(theRanges, lb, ub); 200 state = 6; 201 } else { 202 throw new IllegalArgumentException(); 203 } 204 break; 205 206 case 6: // Before first integer in second or later group 207 if (Character.isWhitespace(c)) { 208 state = 6; 209 } else if ((digit = Character.digit(c, 10)) != -1) { 210 lb = digit; 211 state = 1; 212 } else { 213 throw new IllegalArgumentException(); 214 } 215 break; 216 } 217 } 218 219 // Finish off the state machine. 220 switch (state) { 221 case 0: // Before first integer in first group 222 break; 223 case 1: // In first integer in a group 224 case 2: // After first integer in a group 225 accumulate(theRanges, lb, lb); 226 break; 227 case 4: // In second integer in a group 228 case 5: // After second integer in a group 229 accumulate(theRanges, lb, ub); 230 break; 231 case 3: // Before second integer in a group 232 case 6: // Before first integer in second or later group 233 throw new IllegalArgumentException(); 234 } 235 236 // Return canonical array form. 237 return canonicalArrayForm (theRanges); 238 } 239 240 /** 241 * Accumulate the given range (lb .. ub) into the canonical array form 242 * into the given vector of int[] objects. 243 */ 244 private static void accumulate(Vector<int[]> ranges, int lb,int ub) { 245 // Make sure range is non-null. 246 if (lb <= ub) { 247 // Stick range at the back of the vector. 248 ranges.add(new int[] {lb, ub}); 249 250 // Work towards the front of the vector to integrate the new range 251 // with the existing ranges. 252 for (int j = ranges.size()-2; j >= 0; -- j) { 253 // Get lower and upper bounds of the two ranges being compared. 254 int[] rangea = ranges.elementAt (j); 255 int lba = rangea[0]; 256 int uba = rangea[1]; 257 int[] rangeb = ranges.elementAt (j+1); 258 int lbb = rangeb[0]; 259 int ubb = rangeb[1]; 260 261 /* If the two ranges overlap or are adjacent, coalesce them. 262 * The two ranges overlap if the larger lower bound is less 263 * than or equal to the smaller upper bound. The two ranges 264 * are adjacent if the larger lower bound is one greater 265 * than the smaller upper bound. 266 */ 267 if (Math.max(lba, lbb) - Math.min(uba, ubb) <= 1) { 268 // The coalesced range is from the smaller lower bound to 269 // the larger upper bound. 270 ranges.setElementAt(new int[] 271 {Math.min(lba, lbb), 272 Math.max(uba, ubb)}, j); 273 ranges.remove (j+1); 274 } else if (lba > lbb) { 275 276 /* If the two ranges don't overlap and aren't adjacent but 277 * are out of order, swap them. 278 */ 279 ranges.setElementAt (rangeb, j); 280 ranges.setElementAt (rangea, j+1); 281 } else { 282 /* If the two ranges don't overlap and aren't adjacent and 283 * aren't out of order, we're done early. 284 */ 285 break; 286 } 287 } 288 } 289 } 290 291 /** 292 * Convert the given vector of int[] objects to canonical array form. 293 */ 294 private static int[][] canonicalArrayForm(Vector<int[]> ranges) { 295 return ranges.toArray (new int[ranges.size()][]); 296 } 297 298 /** 299 * Construct a new set-of-integer attribute with the given members in 300 * array form. 301 * 302 * @param members Set members in array form. If null, an empty set is 303 * constructed. 304 * 305 * @exception NullPointerException 306 * (Unchecked exception) Thrown if any element of 307 * {@code members} is null. 308 * @exception IllegalArgumentException 309 * (Unchecked exception) Thrown if any element of 310 * {@code members} is not a length-one or length-two array or if 311 * any non-null range in {@code members} has a lower bound less 312 * than zero. 313 */ 314 protected SetOfIntegerSyntax(int[][] members) { 315 this.members = parse (members); 316 } 317 318 /** 319 * Parse the given array form, returning canonical array form. 320 */ 321 private static int[][] parse(int[][] members) { 322 // Create vector to hold int[] elements, each element being one range 323 // parsed out of members. 324 Vector<int[]> ranges = new Vector<>(); 325 326 // Process all integer groups in members. 327 int n = (members == null ? 0 : members.length); 328 for (int i = 0; i < n; ++ i) { 329 // Get lower and upper bounds of the range. 330 int lb, ub; 331 if (members[i].length == 1) { 332 lb = ub = members[i][0]; 333 } else if (members[i].length == 2) { 334 lb = members[i][0]; 335 ub = members[i][1]; 336 } else { 337 throw new IllegalArgumentException(); 338 } 339 340 // Verify valid bounds. 341 if (lb <= ub && lb < 0) { 342 throw new IllegalArgumentException(); 343 } 344 345 // Accumulate the range. 346 accumulate(ranges, lb, ub); 347 } 348 349 // Return canonical array form. 350 return canonicalArrayForm (ranges); 351 } 352 353 /** 354 * Construct a new set-of-integer attribute containing a single integer. 355 * 356 * @param member Set member. 357 * 358 * @exception IllegalArgumentException 359 * (Unchecked exception) Thrown if {@code member} is less than 360 * zero. 361 */ 362 protected SetOfIntegerSyntax(int member) { 363 if (member < 0) { 364 throw new IllegalArgumentException(); 365 } 366 members = new int[][] {{member, member}}; 367 } 368 369 /** 370 * Construct a new set-of-integer attribute containing a single range of 371 * integers. If the lower bound is greater than the upper bound (a null 372 * range), an empty set is constructed. 373 * 374 * @param lowerBound Lower bound of the range. 375 * @param upperBound Upper bound of the range. 376 * 377 * @exception IllegalArgumentException 378 * (Unchecked exception) Thrown if the range is non-null and 379 * {@code lowerBound} is less than zero. 380 */ 381 protected SetOfIntegerSyntax(int lowerBound, int upperBound) { 382 if (lowerBound <= upperBound && lowerBound < 0) { 383 throw new IllegalArgumentException(); 384 } 385 members = lowerBound <=upperBound ? 386 new int[][] {{lowerBound, upperBound}} : 387 new int[0][]; 388 } 389 390 391 /** 392 * Obtain this set-of-integer attribute's members in canonical array form. 393 * The returned array is "safe;" the client may alter it without affecting 394 * this set-of-integer attribute. 395 * 396 * @return This set-of-integer attribute's members in canonical array form. 397 */ 398 public int[][] getMembers() { 399 int n = members.length; 400 int[][] result = new int[n][]; 401 for (int i = 0; i < n; ++ i) { 402 result[i] = new int[] {members[i][0], members[i][1]}; 403 } 404 return result; 405 } 406 407 /** 408 * Determine if this set-of-integer attribute contains the given value. 409 * 410 * @param x Integer value. 411 * 412 * @return True if this set-of-integer attribute contains the value 413 * {@code x}, false otherwise. 414 */ 415 public boolean contains(int x) { 416 // Do a linear search to find the range that contains x, if any. 417 int n = members.length; 418 for (int i = 0; i < n; ++ i) { 419 if (x < members[i][0]) { 420 return false; 421 } else if (x <= members[i][1]) { 422 return true; 423 } 424 } 425 return false; 426 } 427 428 /** 429 * Determine if this set-of-integer attribute contains the given integer 430 * attribute's value. 431 * 432 * @param attribute Integer attribute. 433 * 434 * @return True if this set-of-integer attribute contains 435 * {@code theAttribute}'s value, false otherwise. 436 */ 437 public boolean contains(IntegerSyntax attribute) { 438 return contains (attribute.getValue()); 439 } 440 441 /** 442 * Determine the smallest integer in this set-of-integer attribute that is 443 * greater than the given value. If there are no integers in this 444 * set-of-integer attribute greater than the given value, {@code -1} is 445 * returned. (Since a set-of-integer attribute can only contain nonnegative 446 * values, {@code -1} will never appear in the set.) You can use the 447 * {@code next()} method to iterate through the integer values in a 448 * set-of-integer attribute in ascending order, like this: 449 * <PRE> 450 * SetOfIntegerSyntax attribute = . . .; 451 * int i = -1; 452 * while ((i = attribute.next (i)) != -1) 453 * { 454 * foo (i); 455 * } 456 * </PRE> 457 * 458 * @param x Integer value. 459 * 460 * @return The smallest integer in this set-of-integer attribute that is 461 * greater than {@code x}, or {@code -1} if no integer in 462 * this set-of-integer attribute is greater than {@code x}. 463 */ 464 public int next(int x) { 465 // Do a linear search to find the range that contains x, if any. 466 int n = members.length; 467 for (int i = 0; i < n; ++ i) { 468 if (x < members[i][0]) { 469 return members[i][0]; 470 } else if (x < members[i][1]) { 471 return x + 1; 472 } 473 } 474 return -1; 475 } 476 477 /** 478 * Returns whether this set-of-integer attribute is equivalent to the passed 479 * in object. To be equivalent, all of the following conditions must be 480 * true: 481 * <OL TYPE=1> 482 * <LI> 483 * {@code object} is not null. 484 * <LI> 485 * {@code object} is an instance of class SetOfIntegerSyntax. 486 * <LI> 487 * This set-of-integer attribute's members and {@code object}'s 488 * members are the same. 489 * </OL> 490 * 491 * @param object Object to compare to. 492 * 493 * @return True if {@code object} is equivalent to this 494 * set-of-integer attribute, false otherwise. 495 */ 496 public boolean equals(Object object) { 497 if (object != null && object instanceof SetOfIntegerSyntax) { 498 int[][] myMembers = this.members; 499 int[][] otherMembers = ((SetOfIntegerSyntax) object).members; 500 int m = myMembers.length; 501 int n = otherMembers.length; 502 if (m == n) { 503 for (int i = 0; i < m; ++ i) { 504 if (myMembers[i][0] != otherMembers[i][0] || 505 myMembers[i][1] != otherMembers[i][1]) { 506 return false; 507 } 508 } 509 return true; 510 } else { 511 return false; 512 } 513 } else { 514 return false; 515 } 516 } 517 518 /** 519 * Returns a hash code value for this set-of-integer attribute. The hash 520 * code is the sum of the lower and upper bounds of the ranges in the 521 * canonical array form, or 0 for an empty set. 522 */ 523 public int hashCode() { 524 int result = 0; 525 int n = members.length; 526 for (int i = 0; i < n; ++ i) { 527 result += members[i][0] + members[i][1]; 528 } 529 return result; 530 } 531 532 /** 533 * Returns a string value corresponding to this set-of-integer attribute. 534 * The string value is a zero-length string if this set is empty. Otherwise, 535 * the string value is a comma-separated list of the ranges in the canonical 536 * array form, where each range is represented as <code>"<I>i</I>"</code> if 537 * the lower bound equals the upper bound or 538 * <code>"<I>i</I>-<I>j</I>"</code> otherwise. 539 */ 540 public String toString() { 541 StringBuilder result = new StringBuilder(); 542 int n = members.length; 543 for (int i = 0; i < n; i++) { 544 if (i > 0) { 545 result.append (','); 546 } 547 result.append (members[i][0]); 548 if (members[i][0] != members[i][1]) { 549 result.append ('-'); 550 result.append (members[i][1]); 551 } 552 } 553 return result.toString(); 554 } 555 556 }