001 /*
002 * Copyright 2000-2004 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.print.attribute;
027
028 import java.io.Serializable;
029 import java.util.Vector;
030
031 /**
032 * Class SetOfIntegerSyntax is an abstract base class providing the common
033 * implementation of all attributes whose value is a set of nonnegative
034 * integers. This includes attributes whose value is a single range of integers
035 * and attributes whose value is a set of ranges of integers.
036 * <P>
037 * You can construct an instance of SetOfIntegerSyntax by giving it in "string
038 * form." The string consists of zero or more comma-separated integer groups.
039 * Each integer group consists of either one integer, two integers separated by
040 * a hyphen (<CODE>-</CODE>), or two integers separated by a colon
041 * (<CODE>:</CODE>). Each integer consists of one or more decimal digits
042 * (<CODE>0</CODE> through <CODE>9</CODE>). Whitespace characters cannot
043 * appear within an integer but are otherwise ignored. For example:
044 * <CODE>""</CODE>, <CODE>"1"</CODE>, <CODE>"5-10"</CODE>, <CODE>"1:2,
045 * 4"</CODE>.
046 * <P>
047 * You can also construct an instance of SetOfIntegerSyntax by giving it in
048 * "array form." Array form consists of an array of zero or more integer groups
049 * where each integer group is a length-1 or length-2 array of
050 * <CODE>int</CODE>s; for example, <CODE>int[0][]</CODE>,
051 * <CODE>int[][]{{1}}</CODE>, <CODE>int[][]{{5,10}}</CODE>,
052 * <CODE>int[][]{{1,2},{4}}</CODE>.
053 * <P>
054 * In both string form and array form, each successive integer group gives a
055 * range of integers to be included in the set. The first integer in each group
056 * gives the lower bound of the range; the second integer in each group gives
057 * the upper bound of the range; if there is only one integer in the group, the
058 * upper bound is the same as the lower bound. If the upper bound is less than
059 * the lower bound, it denotes a null range (no values). If the upper bound is
060 * equal to the lower bound, it denotes a range consisting of a single value. If
061 * the upper bound is greater than the lower bound, it denotes a range
062 * consisting of more than one value. The ranges may appear in any order and are
063 * allowed to overlap. The union of all the ranges gives the set's contents.
064 * Once a SetOfIntegerSyntax instance is constructed, its value is immutable.
065 * <P>
066 * The SetOfIntegerSyntax object's value is actually stored in "<I>canonical</I>
067 * array form." This is the same as array form, except there are no null ranges;
068 * the members of the set are represented in as few ranges as possible (i.e.,
069 * overlapping ranges are coalesced); the ranges appear in ascending order; and
070 * each range is always represented as a length-two array of <CODE>int</CODE>s
071 * in the form {lower bound, upper bound}. An empty set is represented as a
072 * zero-length array.
073 * <P>
074 * Class SetOfIntegerSyntax has operations to return the set's members in
075 * canonical array form, to test whether a given integer is a member of the
076 * set, and to iterate through the members of the set.
077 * <P>
078 *
079 * @author David Mendenhall
080 * @author Alan Kaminsky
081 */
082 public abstract class SetOfIntegerSyntax implements Serializable,
083 Cloneable {
084
085 private static final long serialVersionUID = 3666874174847632203L;
086
087 /**
088 * This set's members in canonical array form.
089 * @serial
090 */
091 private int[][] members;
092
093 /**
094 * Construct a new set-of-integer attribute with the given members in
095 * string form.
096 *
097 * @param members Set members in string form. If null, an empty set is
098 * constructed.
099 *
100 * @exception IllegalArgumentException
101 * (Unchecked exception) Thrown if <CODE>members</CODE> does not
102 * obey the proper syntax.
103 */
104 protected SetOfIntegerSyntax(String members) {
105 this .members = parse(members);
106 }
107
108 /**
109 * Parse the given string, returning canonical array form.
110 */
111 private static int[][] parse(String members) {
112 // Create vector to hold int[] elements, each element being one range
113 // parsed out of members.
114 Vector theRanges = new Vector();
115
116 // Run state machine over members.
117 int n = (members == null ? 0 : members.length());
118 int i = 0;
119 int state = 0;
120 int lb = 0;
121 int ub = 0;
122 char c;
123 int digit;
124 while (i < n) {
125 c = members.charAt(i++);
126 switch (state) {
127
128 case 0: // Before first integer in first group
129 if (Character.isWhitespace(c)) {
130 state = 0;
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 } else if (c == '-' || c == ':') {
159 state = 3;
160 } else if (c == ',') {
161 accumulate(theRanges, lb, lb);
162 state = 6;
163 } else {
164 throw new IllegalArgumentException();
165 }
166 break;
167
168 case 3: // Before second integer in a group
169 if (Character.isWhitespace(c)) {
170 state = 3;
171 } else if ((digit = Character.digit(c, 10)) != -1) {
172 ub = digit;
173 state = 4;
174 } else {
175 throw new IllegalArgumentException();
176 }
177 break;
178
179 case 4: // In second integer in a group
180 if (Character.isWhitespace(c)) {
181 state = 5;
182 } else if ((digit = Character.digit(c, 10)) != -1) {
183 ub = 10 * ub + digit;
184 state = 4;
185 } else if (c == ',') {
186 accumulate(theRanges, lb, ub);
187 state = 6;
188 } else {
189 throw new IllegalArgumentException();
190 }
191 break;
192
193 case 5: // After second integer in a group
194 if (Character.isWhitespace(c)) {
195 state = 5;
196 } else if (c == ',') {
197 accumulate(theRanges, lb, ub);
198 state = 6;
199 } else {
200 throw new IllegalArgumentException();
201 }
202 break;
203
204 case 6: // Before first integer in second or later group
205 if (Character.isWhitespace(c)) {
206 state = 6;
207 } else if ((digit = Character.digit(c, 10)) != -1) {
208 lb = digit;
209 state = 1;
210 } else {
211 throw new IllegalArgumentException();
212 }
213 break;
214 }
215 }
216
217 // Finish off the state machine.
218 switch (state) {
219 case 0: // Before first integer in first group
220 break;
221 case 1: // In first integer in a group
222 case 2: // After first integer in a group
223 accumulate(theRanges, lb, lb);
224 break;
225 case 4: // In second integer in a group
226 case 5: // After second integer in a group
227 accumulate(theRanges, lb, ub);
228 break;
229 case 3: // Before second integer in a group
230 case 6: // Before first integer in second or later group
231 throw new IllegalArgumentException();
232 }
233
234 // Return canonical array form.
235 return canonicalArrayForm(theRanges);
236 }
237
238 /**
239 * Accumulate the given range (lb .. ub) into the canonical array form
240 * into the given vector of int[] objects.
241 */
242 private static void accumulate(Vector ranges, int lb, int ub) {
243 // Make sure range is non-null.
244 if (lb <= ub) {
245 // Stick range at the back of the vector.
246 ranges.add(new int[] { lb, ub });
247
248 // Work towards the front of the vector to integrate the new range
249 // with the existing ranges.
250 for (int j = ranges.size() - 2; j >= 0; --j) {
251 // Get lower and upper bounds of the two ranges being compared.
252 int[] rangea = (int[]) ranges.elementAt(j);
253 int lba = rangea[0];
254 int uba = rangea[1];
255 int[] rangeb = (int[]) ranges.elementAt(j + 1);
256 int lbb = rangeb[0];
257 int ubb = rangeb[1];
258
259 /* If the two ranges overlap or are adjacent, coalesce them.
260 * The two ranges overlap if the larger lower bound is less
261 * than or equal to the smaller upper bound. The two ranges
262 * are adjacent if the larger lower bound is one greater
263 * than the smaller upper bound.
264 */
265 if (Math.max(lba, lbb) - Math.min(uba, ubb) <= 1) {
266 // The coalesced range is from the smaller lower bound to
267 // the larger upper bound.
268 ranges.setElementAt(new int[] { Math.min(lba, lbb),
269 Math.max(uba, ubb) }, j);
270 ranges.remove(j + 1);
271 } else if (lba > lbb) {
272
273 /* If the two ranges don't overlap and aren't adjacent but
274 * are out of order, swap them.
275 */
276 ranges.setElementAt(rangeb, j);
277 ranges.setElementAt(rangea, j + 1);
278 } else {
279 /* If the two ranges don't overlap and aren't adjacent and
280 * aren't out of order, we're done early.
281 */
282 break;
283 }
284 }
285 }
286 }
287
288 /**
289 * Convert the given vector of int[] objects to canonical array form.
290 */
291 private static int[][] canonicalArrayForm(Vector ranges) {
292 return (int[][]) ranges.toArray(new int[ranges.size()][]);
293 }
294
295 /**
296 * Construct a new set-of-integer attribute with the given members in
297 * array form.
298 *
299 * @param members Set members in array form. If null, an empty set is
300 * constructed.
301 *
302 * @exception NullPointerException
303 * (Unchecked exception) Thrown if any element of
304 * <CODE>members</CODE> is null.
305 * @exception IllegalArgumentException
306 * (Unchecked exception) Thrown if any element of
307 * <CODE>members</CODE> is not a length-one or length-two array or if
308 * any non-null range in <CODE>members</CODE> has a lower bound less
309 * than zero.
310 */
311 protected SetOfIntegerSyntax(int[][] members) {
312 this .members = parse(members);
313 }
314
315 /**
316 * Parse the given array form, returning canonical array form.
317 */
318 private static int[][] parse(int[][] members) {
319 // Create vector to hold int[] elements, each element being one range
320 // parsed out of members.
321 Vector ranges = new Vector();
322
323 // Process all integer groups in members.
324 int n = (members == null ? 0 : members.length);
325 for (int i = 0; i < n; ++i) {
326 // Get lower and upper bounds of the range.
327 int lb, ub;
328 if (members[i].length == 1) {
329 lb = ub = members[i][0];
330 } else if (members[i].length == 2) {
331 lb = members[i][0];
332 ub = members[i][1];
333 } else {
334 throw new IllegalArgumentException();
335 }
336
337 // Verify valid bounds.
338 if (lb <= ub && lb < 0) {
339 throw new IllegalArgumentException();
340 }
341
342 // Accumulate the range.
343 accumulate(ranges, lb, ub);
344 }
345
346 // Return canonical array form.
347 return canonicalArrayForm(ranges);
348 }
349
350 /**
351 * Construct a new set-of-integer attribute containing a single integer.
352 *
353 * @param member Set member.
354 *
355 * @exception IllegalArgumentException
356 * (Unchecked exception) Thrown if <CODE>member</CODE> is less than
357 * zero.
358 */
359 protected SetOfIntegerSyntax(int member) {
360 if (member < 0) {
361 throw new IllegalArgumentException();
362 }
363 members = new int[][] { { member, member } };
364 }
365
366 /**
367 * Construct a new set-of-integer attribute containing a single range of
368 * integers. If the lower bound is greater than the upper bound (a null
369 * range), an empty set is constructed.
370 *
371 * @param lowerBound Lower bound of the range.
372 * @param upperBound Upper bound of the range.
373 *
374 * @exception IllegalArgumentException
375 * (Unchecked exception) Thrown if the range is non-null and
376 * <CODE>lowerBound</CODE> is less than zero.
377 */
378 protected SetOfIntegerSyntax(int lowerBound, int upperBound) {
379 if (lowerBound <= upperBound && lowerBound < 0) {
380 throw new IllegalArgumentException();
381 }
382 members = lowerBound <= upperBound ? new int[][] { {
383 lowerBound, upperBound } } : new int[0][];
384 }
385
386 /**
387 * Obtain this set-of-integer attribute's members in canonical array form.
388 * The returned array is "safe;" the client may alter it without affecting
389 * this set-of-integer attribute.
390 *
391 * @return This set-of-integer attribute's members in canonical array form.
392 */
393 public int[][] getMembers() {
394 int n = members.length;
395 int[][] result = new int[n][];
396 for (int i = 0; i < n; ++i) {
397 result[i] = new int[] { members[i][0], members[i][1] };
398 }
399 return result;
400 }
401
402 /**
403 * Determine if this set-of-integer attribute contains the given value.
404 *
405 * @param x Integer value.
406 *
407 * @return True if this set-of-integer attribute contains the value
408 * <CODE>x</CODE>, false otherwise.
409 */
410 public boolean contains(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 false;
416 } else if (x <= members[i][1]) {
417 return true;
418 }
419 }
420 return false;
421 }
422
423 /**
424 * Determine if this set-of-integer attribute contains the given integer
425 * attribute's value.
426 *
427 * @param attribute Integer attribute.
428 *
429 * @return True if this set-of-integer attribute contains
430 * <CODE>theAttribute</CODE>'s value, false otherwise.
431 */
432 public boolean contains(IntegerSyntax attribute) {
433 return contains(attribute.getValue());
434 }
435
436 /**
437 * Determine the smallest integer in this set-of-integer attribute that is
438 * greater than the given value. If there are no integers in this
439 * set-of-integer attribute greater than the given value, <CODE>-1</CODE> is
440 * returned. (Since a set-of-integer attribute can only contain nonnegative
441 * values, <CODE>-1</CODE> will never appear in the set.) You can use the
442 * <CODE>next()</CODE> method to iterate through the integer values in a
443 * set-of-integer attribute in ascending order, like this:
444 * <PRE>
445 * SetOfIntegerSyntax attribute = . . .;
446 * int i = -1;
447 * while ((i = attribute.next (i)) != -1)
448 * {
449 * foo (i);
450 * }
451 * </PRE>
452 *
453 * @param x Integer value.
454 *
455 * @return The smallest integer in this set-of-integer attribute that is
456 * greater than <CODE>x</CODE>, or <CODE>-1</CODE> if no integer in
457 * this set-of-integer attribute is greater than <CODE>x</CODE>.
458 */
459 public int next(int x) {
460 // Do a linear search to find the range that contains x, if any.
461 int n = members.length;
462 for (int i = 0; i < n; ++i) {
463 if (x < members[i][0]) {
464 return members[i][0];
465 } else if (x < members[i][1]) {
466 return x + 1;
467 }
468 }
469 return -1;
470 }
471
472 /**
473 * Returns whether this set-of-integer attribute is equivalent to the passed
474 * in object. To be equivalent, all of the following conditions must be
475 * true:
476 * <OL TYPE=1>
477 * <LI>
478 * <CODE>object</CODE> is not null.
479 * <LI>
480 * <CODE>object</CODE> is an instance of class SetOfIntegerSyntax.
481 * <LI>
482 * This set-of-integer attribute's members and <CODE>object</CODE>'s
483 * members are the same.
484 * </OL>
485 *
486 * @param object Object to compare to.
487 *
488 * @return True if <CODE>object</CODE> is equivalent to this
489 * set-of-integer attribute, false otherwise.
490 */
491 public boolean equals(Object object) {
492 if (object != null && object instanceof SetOfIntegerSyntax) {
493 int[][] myMembers = this .members;
494 int[][] otherMembers = ((SetOfIntegerSyntax) object).members;
495 int m = myMembers.length;
496 int n = otherMembers.length;
497 if (m == n) {
498 for (int i = 0; i < m; ++i) {
499 if (myMembers[i][0] != otherMembers[i][0]
500 || myMembers[i][1] != otherMembers[i][1]) {
501 return false;
502 }
503 }
504 return true;
505 } else {
506 return false;
507 }
508 } else {
509 return false;
510 }
511 }
512
513 /**
514 * Returns a hash code value for this set-of-integer attribute. The hash
515 * code is the sum of the lower and upper bounds of the ranges in the
516 * canonical array form, or 0 for an empty set.
517 */
518 public int hashCode() {
519 int result = 0;
520 int n = members.length;
521 for (int i = 0; i < n; ++i) {
522 result += members[i][0] + members[i][1];
523 }
524 return result;
525 }
526
527 /**
528 * Returns a string value corresponding to this set-of-integer attribute.
529 * The string value is a zero-length string if this set is empty. Otherwise,
530 * the string value is a comma-separated list of the ranges in the canonical
531 * array form, where each range is represented as <CODE>"<I>i</I>"</CODE> if
532 * the lower bound equals the upper bound or
533 * <CODE>"<I>i</I>-<I>j</I>"</CODE> otherwise.
534 */
535 public String toString() {
536 StringBuffer result = new StringBuffer();
537 int n = members.length;
538 for (int i = 0; i < n; i++) {
539 if (i > 0) {
540 result.append(',');
541 }
542 result.append(members[i][0]);
543 if (members[i][0] != members[i][1]) {
544 result.append('-');
545 result.append(members[i][1]);
546 }
547 }
548 return result.toString();
549 }
550
551 }
|