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