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