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