Path: blob/master/src/java.desktop/share/classes/javax/print/attribute/SetOfIntegerSyntax.java
41159 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.print.attribute;2627import java.io.Serial;28import java.io.Serializable;29import java.util.Vector;3031/**32* Class {@code SetOfIntegerSyntax} is an abstract base class providing the33* common implementation of all attributes whose value is a set of nonnegative34* integers. This includes attributes whose value is a single range of integers35* and attributes whose value is a set of ranges of integers.36* <p>37* You can construct an instance of {@code SetOfIntegerSyntax} by giving it in38* "string form." The string consists of zero or more comma-separated integer39* groups. Each integer group consists of either one integer, two integers40* separated by a hyphen ({@code -}), or two integers separated by a colon41* ({@code :}). Each integer consists of one or more decimal digits ({@code 0}42* through {@code 9}). Whitespace characters cannot appear within an integer but43* are otherwise ignored. For example: {@code ""}, {@code "1"}, {@code "5-10"},44* {@code "1:2, 4"}.45* <p>46* You can also construct an instance of {@code SetOfIntegerSyntax} by giving it47* in "array form." Array form consists of an array of zero or more integer48* groups where each integer group is a length-1 or length-2 array of49* {@code int}s; for example, {@code int[0][]}, {@code int[][]{{1}}},50* {@code int[][]{{5,10}}}, {@code int[][]{{1,2},{4}}}.51* <p>52* In both string form and array form, each successive integer group gives a53* range of integers to be included in the set. The first integer in each group54* gives the lower bound of the range; the second integer in each group gives55* the upper bound of the range; if there is only one integer in the group, the56* upper bound is the same as the lower bound. If the upper bound is less than57* the lower bound, it denotes a {@code null} range (no values). If the upper58* bound is equal to the lower bound, it denotes a range consisting of a single59* value. If the upper bound is greater than the lower bound, it denotes a range60* consisting of more than one value. The ranges may appear in any order and are61* allowed to overlap. The union of all the ranges gives the set's contents.62* Once a {@code SetOfIntegerSyntax} instance is constructed, its value is63* immutable.64* <p>65* The {@code SetOfIntegerSyntax} object's value is actually stored in66* "<i>canonical</i> array form." This is the same as array form, except there67* are no {@code null} ranges; the members of the set are represented in as few68* ranges as possible (i.e., overlapping ranges are coalesced); the ranges69* appear in ascending order; and each range is always represented as a70* length-two array of {@code int}s in the form {lower bound, upper bound}. An71* empty set is represented as a zero-length array.72* <p>73* Class {@code SetOfIntegerSyntax} has operations to return the set's members74* in canonical array form, to test whether a given integer is a member of the75* set, and to iterate through the members of the set.76*77* @author David Mendenhall78* @author Alan Kaminsky79*/80public abstract class SetOfIntegerSyntax implements Serializable, Cloneable {8182/**83* Use serialVersionUID from JDK 1.4 for interoperability.84*/85@Serial86private static final long serialVersionUID = 3666874174847632203L;8788/**89* This set's members in canonical array form.90*91* @serial92*/93private int[][] members;9495/**96* Construct a new set-of-integer attribute with the given members in string97* form.98*99* @param members set members in string form. If {@code null}, an empty set100* is constructed.101* @throws IllegalArgumentException if {@code members} does not obey the102* proper syntax103*/104protected SetOfIntegerSyntax(String members) {105this.members = parse (members);106}107108/**109* Parse the given string, returning canonical array form.110*111* @param members the string112* @return the canonical array form113*/114private static int[][] parse(String members) {115// Create vector to hold int[] elements, each element being one range116// parsed out of members.117Vector<int[]> theRanges = new Vector<>();118119// Run state machine over members.120int n = (members == null ? 0 : members.length());121int i = 0;122int state = 0;123int lb = 0;124int ub = 0;125char c;126int digit;127while (i < n) {128c = members.charAt(i ++);129switch (state) {130131case 0: // Before first integer in first group132if (Character.isWhitespace(c)) {133state = 0;134}135else if ((digit = Character.digit(c, 10)) != -1) {136lb = digit;137state = 1;138} else {139throw new IllegalArgumentException();140}141break;142143case 1: // In first integer in a group144if (Character.isWhitespace(c)){145state = 2;146} else if ((digit = Character.digit(c, 10)) != -1) {147lb = 10 * lb + digit;148state = 1;149} else if (c == '-' || c == ':') {150state = 3;151} else if (c == ',') {152accumulate (theRanges, lb, lb);153state = 6;154} else {155throw new IllegalArgumentException();156}157break;158159case 2: // After first integer in a group160if (Character.isWhitespace(c)) {161state = 2;162}163else if (c == '-' || c == ':') {164state = 3;165}166else if (c == ',') {167accumulate(theRanges, lb, lb);168state = 6;169} else {170throw new IllegalArgumentException();171}172break;173174case 3: // Before second integer in a group175if (Character.isWhitespace(c)) {176state = 3;177} else if ((digit = Character.digit(c, 10)) != -1) {178ub = digit;179state = 4;180} else {181throw new IllegalArgumentException();182}183break;184185case 4: // In second integer in a group186if (Character.isWhitespace(c)) {187state = 5;188} else if ((digit = Character.digit(c, 10)) != -1) {189ub = 10 * ub + digit;190state = 4;191} else if (c == ',') {192accumulate(theRanges, lb, ub);193state = 6;194} else {195throw new IllegalArgumentException();196}197break;198199case 5: // After second integer in a group200if (Character.isWhitespace(c)) {201state = 5;202} else if (c == ',') {203accumulate(theRanges, lb, ub);204state = 6;205} else {206throw new IllegalArgumentException();207}208break;209210case 6: // Before first integer in second or later group211if (Character.isWhitespace(c)) {212state = 6;213} else if ((digit = Character.digit(c, 10)) != -1) {214lb = digit;215state = 1;216} else {217throw new IllegalArgumentException();218}219break;220}221}222223// Finish off the state machine.224switch (state) {225case 0: // Before first integer in first group226break;227case 1: // In first integer in a group228case 2: // After first integer in a group229accumulate(theRanges, lb, lb);230break;231case 4: // In second integer in a group232case 5: // After second integer in a group233accumulate(theRanges, lb, ub);234break;235case 3: // Before second integer in a group236case 6: // Before first integer in second or later group237throw new IllegalArgumentException();238}239240// Return canonical array form.241return canonicalArrayForm (theRanges);242}243244/**245* Accumulate the given range (lb .. ub) into the canonical array form into246* the given vector of int[] objects.247*/248private static void accumulate(Vector<int[]> ranges, int lb,int ub) {249// Make sure range is non-null.250if (lb <= ub) {251// Stick range at the back of the vector.252ranges.add(new int[] {lb, ub});253254// Work towards the front of the vector to integrate the new range255// with the existing ranges.256for (int j = ranges.size()-2; j >= 0; -- j) {257// Get lower and upper bounds of the two ranges being compared.258int[] rangea = ranges.elementAt (j);259int lba = rangea[0];260int uba = rangea[1];261int[] rangeb = ranges.elementAt (j+1);262int lbb = rangeb[0];263int ubb = rangeb[1];264/*265* If the two ranges overlap or are adjacent, coalesce them. The266* two ranges overlap if the larger lower bound is less than or267* equal to the smaller upper bound. The two ranges are adjacent268* if the larger lower bound is one greater than the smaller269* upper bound.270*/271if (Math.max(lba, lbb) - Math.min(uba, ubb) <= 1) {272// The coalesced range is from the smaller lower bound to273// the larger upper bound.274ranges.setElementAt(new int[]275{Math.min(lba, lbb),276Math.max(uba, ubb)}, j);277ranges.remove (j+1);278} else if (lba > lbb) {279280/* If the two ranges don't overlap and aren't adjacent but281* are out of order, swap them.282*/283ranges.setElementAt (rangeb, j);284ranges.setElementAt (rangea, j+1);285} else {286/*287* If the two ranges don't overlap and aren't adjacent and288* aren't out of order, we're done early.289*/290break;291}292}293}294}295296/**297* Convert the given vector of int[] objects to canonical array form.298*/299private static int[][] canonicalArrayForm(Vector<int[]> ranges) {300return ranges.toArray (new int[ranges.size()][]);301}302303/**304* Construct a new set-of-integer attribute with the given members in array305* form.306*307* @param members set members in array form. If {@code null}, an empty set308* is constructed.309* @throws NullPointerException if any element of {@code members} is310* {@code null}311* @throws IllegalArgumentException if any element of {@code members} is not312* a length-one or length-two array or if any {@code non-null} range313* in {@code members} has a lower bound less than zero314*/315protected SetOfIntegerSyntax(int[][] members) {316this.members = parse (members);317}318319/**320* Parse the given array form, returning canonical array form.321*/322private static int[][] parse(int[][] members) {323// Create vector to hold int[] elements, each element being one range324// parsed out of members.325Vector<int[]> ranges = new Vector<>();326327// Process all integer groups in members.328int n = (members == null ? 0 : members.length);329for (int i = 0; i < n; ++ i) {330// Get lower and upper bounds of the range.331int lb, ub;332if (members[i].length == 1) {333lb = ub = members[i][0];334} else if (members[i].length == 2) {335lb = members[i][0];336ub = members[i][1];337} else {338throw new IllegalArgumentException();339}340341// Verify valid bounds.342if (lb <= ub && lb < 0) {343throw new IllegalArgumentException();344}345346// Accumulate the range.347accumulate(ranges, lb, ub);348}349350// Return canonical array form.351return canonicalArrayForm (ranges);352}353354/**355* Construct a new set-of-integer attribute containing a single integer.356*357* @param member set member358* @throws IllegalArgumentException if {@code member} is negative359*/360protected SetOfIntegerSyntax(int member) {361if (member < 0) {362throw new IllegalArgumentException();363}364members = new int[][] {{member, member}};365}366367/**368* Construct a new set-of-integer attribute containing a single range of369* integers. If the lower bound is greater than the upper bound (a null370* range), an empty set is constructed.371*372* @param lowerBound Lower bound of the range373* @param upperBound Upper bound of the range374* @throws IllegalArgumentException if the range is {@code non-null} and375* {@code lowerBound} is less than zero376*/377protected SetOfIntegerSyntax(int lowerBound, int upperBound) {378if (lowerBound <= upperBound && lowerBound < 0) {379throw new IllegalArgumentException();380}381members = lowerBound <=upperBound ?382new int[][] {{lowerBound, upperBound}} :383new int[0][];384}385386/**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 affecting389* this set-of-integer attribute.390*391* @return this set-of-integer attribute's members in canonical array form392*/393public int[][] getMembers() {394int n = members.length;395int[][] result = new int[n][];396for (int i = 0; i < n; ++ i) {397result[i] = new int[] {members[i][0], members[i][1]};398}399return result;400}401402/**403* Determine if this set-of-integer attribute contains the given value.404*405* @param x the Integer value406* @return {@code true} if this set-of-integer attribute contains the value407* {@code x}, {@code false} otherwise408*/409public boolean contains(int x) {410// Do a linear search to find the range that contains x, if any.411int n = members.length;412for (int i = 0; i < n; ++ i) {413if (x < members[i][0]) {414return false;415} else if (x <= members[i][1]) {416return true;417}418}419return false;420}421422/**423* Determine if this set-of-integer attribute contains the given integer424* attribute's value.425*426* @param attribute the Integer attribute427* @return {@code true} if this set-of-integer attribute contains428* {@code attribute}'s value, {@code false} otherwise429*/430public boolean contains(IntegerSyntax attribute) {431return contains (attribute.getValue());432}433434/**435* Determine the smallest integer in this set-of-integer attribute that is436* greater than the given value. If there are no integers in this437* set-of-integer attribute greater than the given value, {@code -1} is438* returned. (Since a set-of-integer attribute can only contain nonnegative439* values, {@code -1} will never appear in the set.) You can use the440* {@code next()} method to iterate through the integer values in a441* set-of-integer attribute in ascending order, like this:442* <pre>443* SetOfIntegerSyntax attribute = . . .;444* int i = -1;445* while ((i = attribute.next (i)) != -1)446* {447* foo (i);448* }449* </pre>450*451* @param x the Integer value452* @return the smallest integer in this set-of-integer attribute that is453* greater than {@code x}, or {@code -1} if no integer in this454* set-of-integer attribute is greater than {@code x}.455*/456public int next(int x) {457// Do a linear search to find the range that contains x, if any.458int n = members.length;459for (int i = 0; i < n; ++ i) {460if (x < members[i][0]) {461return members[i][0];462} else if (x < members[i][1]) {463return x + 1;464}465}466return -1;467}468469/**470* Returns whether this set-of-integer attribute is equivalent to the passed471* in object. To be equivalent, all of the following conditions must be472* true:473* <ol type=1>474* <li>{@code object} is not {@code null}.475* <li>{@code object} is an instance of class {@code SetOfIntegerSyntax}.476* <li>This set-of-integer attribute's members and {@code object}'s477* members are the same.478* </ol>479*480* @param object {@code Object} to compare to481* @return {@code true} if {@code object} is equivalent to this482* set-of-integer attribute, {@code false} otherwise483*/484public boolean equals(Object object) {485if (object != null && object instanceof SetOfIntegerSyntax) {486int[][] myMembers = this.members;487int[][] otherMembers = ((SetOfIntegerSyntax) object).members;488int m = myMembers.length;489int n = otherMembers.length;490if (m == n) {491for (int i = 0; i < m; ++ i) {492if (myMembers[i][0] != otherMembers[i][0] ||493myMembers[i][1] != otherMembers[i][1]) {494return false;495}496}497return true;498} else {499return false;500}501} else {502return false;503}504}505506/**507* Returns a hash code value for this set-of-integer attribute. The hash508* code is the sum of the lower and upper bounds of the ranges in the509* canonical array form, or 0 for an empty set.510*/511public int hashCode() {512int result = 0;513int n = members.length;514for (int i = 0; i < n; ++ i) {515result += members[i][0] + members[i][1];516}517return result;518}519520/**521* Returns a string value corresponding to this set-of-integer attribute.522* The string value is a zero-length string if this set is empty. Otherwise,523* the string value is a comma-separated list of the ranges in the canonical524* array form, where each range is represented as <code>"<i>i</i>"</code> if525* the lower bound equals the upper bound or526* <code>"<i>i</i>-<i>j</i>"</code> otherwise.527*/528public String toString() {529StringBuilder result = new StringBuilder();530int n = members.length;531for (int i = 0; i < n; i++) {532if (i > 0) {533result.append (',');534}535result.append (members[i][0]);536if (members[i][0] != members[i][1]) {537result.append ('-');538result.append (members[i][1]);539}540}541return result.toString();542}543}544545546