Java tutorial
/* * Copyright 2015 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.util; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayInteger; /** * This implementation uses bit groups of size 32 to keep track of when bits are * set to true or false. This implementation also uses the sparse nature of * JavaScript arrays to speed up cases when very few bits are set in a large bit * set. * * Since there is no speed advantage to pre-allocating array sizes in JavaScript * the underlying array's length is shrunk to Sun's "logical length" whenever * length() is called. This length is the index of the highest true bit, plus * one, or zero if there are aren't any. This may cause the size() method to * return a different size than in a true Java VM. * * TODO(moz): Add this to GWT. Pending changelist at: * https://gwt-review.googlesource.com/#/c/5771/ */ public class BitSet { // To speed up certain operations this class also uses the index properties // of arrays as described in section 15.4 of "Standard ECMA-262" (June 1997), // which can currently be found here: // http://www.mozilla.org/js/language/E262.pdf // // 15.4 Array Objects // Array objects give special treatment to a certain class of property names. // A property name P (in the form of a string value) is an array index if and // only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal // to (2^32)-1. // checks the index range private static void checkIndex(int bitIndex) { // we only need to test for negatives, as there is no bit index too high. if (bitIndex < 0) { throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); } } // checks to ensure indexes are not negative and not in reverse order private static void checkRange(int fromIndex, int toIndex) { if (fromIndex < 0) { throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); } if (toIndex < 0) { throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex); } if (fromIndex > toIndex) { throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex); } } // converts from a bit index to a word index private static int wordIndex(int bitIndex) { // 32 bits per index return bitIndex >>> 5; } // converts from a word index to a bit index private static int bitIndex(int wordIndex) { // 1 word index for every 32 bit indexes return wordIndex << 5; } // gives the word offset for a bit index private static int bitOffset(int bitIndex) { return bitIndex & 0x1f; } // // none of the following static method perform any bounds checking // // clears one bit private static void clear(JsArrayInteger array, int bitIndex) { int index = wordIndex(bitIndex); int word = getWord(array, index); if (word != 0) { // mask the correct bit out setWord(array, index, word & ~(1 << (bitOffset(bitIndex)))); } } // clones the JSArrayInteger array private static native JsArrayInteger clone(JsArrayInteger array) /*-{ return array.slice(0); }-*/; // flips one bit private static void flip(JsArrayInteger array, int bitIndex) { // calculate index and offset int index = wordIndex(bitIndex); int offset = bitOffset(bitIndex); // figure out if the bit is on or off int word = getWord(array, index); if (((word >>> offset) & 1) == 1) { // if on, turn it off setWord(array, index, word & ~(1 << offset)); } else { // if off, turn it on array.set(index, word | (1 << offset)); } } // gets one bit private static boolean get(JsArrayInteger array, int bitIndex) { // retrieve the bits for the given index int word = getWord(array, wordIndex(bitIndex)); // shift and mask the bit out return ((word >>> (bitOffset(bitIndex))) & 1) == 1; } // sets one bit to true private static void set(JsArrayInteger array, int bitIndex) { int index = wordIndex(bitIndex); array.set(index, getWord(array, index) | (1 << (bitOffset(bitIndex)))); } // sets all bits to true within the given range private static void set(JsArrayInteger array, int fromIndex, int toIndex) { int first = wordIndex(fromIndex); int last = wordIndex(toIndex); int startBit = bitOffset(fromIndex); int endBit = bitOffset(toIndex); if (first == last) { // set the bits in between first and last maskInWord(array, first, startBit, endBit); } else { // set the bits from fromIndex to the next 32 bit boundary if (startBit != 0) { maskInWord(array, first++, startBit, 32); } // set the bits from the last 32 bit boundary to the toIndex if (endBit != 0) { maskInWord(array, last, 0, endBit); } // // set everything in between // for (int i = first; i < last; i++) { array.set(i, 0xffffffff); } } } // copies a subset of the array private static native JsArrayInteger slice(JsArrayInteger array, int fromIndex, int toIndex) /*-{ return array.slice(fromIndex, toIndex); }-*/; // trims the array to the minimum size it can without losing data // returns index of the last element in the array, or -1 if empty private static native int trimToSize(JsArrayInteger array) /*-{ var length = array.length; if (length === 0) { return -1; } // check if the last bit is false var last = length - 1; if (array[last] !== undefined) { return last; } // interleave property checks and linear index checks from the end var biggestSeen = -1; for (var property in array) { // test the index first if (--last === -1) { return -1; } if (array[last] !== undefined) { return last; } // now check the property var number = property >>> 0; if (String(number) == property && number !== 0xffffffff) { if (number > biggestSeen) { biggestSeen = number; } } } array.length = biggestSeen + 1; return biggestSeen; }-*/; // // word methods use the literal index into the array, not the bit index // // deletes an element from the array private static native void deleteWord(JsArrayInteger array, int index) /*-{ delete array[index]; }-*/; // flips all bits stored at a certain index private static void flipWord(JsArrayInteger array, int index) { int word = getWord(array, index); if (word == 0) { array.set(index, 0xffffffff); } else { word = ~word; setWord(array, index, word); } } // flips all bits stored at a certain index within the given range private static void flipMaskedWord(JsArrayInteger array, int index, int from, int to) { if (from == to) { return; } // get the bits int word = getWord(array, index); // adjust "to" so it will shift out those bits to = 32 - to; // create a mask and XOR it in word ^= (((0xffffffff >>> from) << from) << to) >>> to; setWord(array, index, word); } // returns all bits stored at a certain index private static native int getWord(JsArrayInteger array, int index) /*-{ // OR converts an undefined to 0 return array[index] | 0; }-*/; // sets all bits to true at a certain index within the given bit range private static void maskInWord(JsArrayInteger array, int index, int from, int to) { // shifting by 32 is the same as shifting by 0, this check prevents that // from happening in addition to the obvious avoidance of extra work if (from != to) { // adjust "to" so it will shift out those bits to = 32 - to; // create a mask and OR it in int value = getWord(array, index); value |= ((0xffffffff >>> from) << (from + to)) >>> to; array.set(index, value); } } // sets all bits to false at a certain index within the given bit range private static void maskOutWord(JsArrayInteger array, int index, int from, int to) { int word = getWord(array, index); // something only happens if word has bits set if (word != 0) { // create a mask int mask; if (from != 0) { mask = 0xffffffff >>> (32 - from); } else { mask = 0; } // shifting by 32 is the same as shifting by 0 if (to != 32) { mask |= 0xffffffff << to; } // mask it out word &= mask; setWord(array, index, word); } } private static native int nextSetWord(JsArrayInteger array, int index) /*-{ // interleave property checks and linear "index" checks var length = array.length; var localMinimum = @java.lang.Integer::MAX_VALUE; for (var property in array) { // test the index first if (array[index] !== undefined) { return index; } if (++index >= length) { return -1; } // now check the property var number = property >>> 0; if (String(number) == property && number !== 0xffffffff) { if (number >= index && number < localMinimum) { localMinimum = number; } } } // if local minimum is what we started at, we found nothing if (localMinimum === @java.lang.Integer::MAX_VALUE) { return -1; } return localMinimum; }-*/; // sets all bits at a certain index to the given value private static void setWord(JsArrayInteger array, int index, int value) { // keep 0s out of the array if (value == 0) { deleteWord(array, index); } else { array.set(index, value); } } // sets the array length private static native void setLengthWords(JsArrayInteger array, int length) /*-{ array.length = length; }-*/; // our array of bits private JsArrayInteger array; public BitSet() { // create a new array array = JavaScriptObject.createArray().cast(); } public BitSet(int nbits) { this(); // throw an exception to be consistent // but (do we want to be consistent?) if (nbits < 0) { throw new NegativeArraySizeException("nbits < 0: " + nbits); } // even though the array's length is loosely kept to that of Sun's "logical // length," this might help in some cases where code uses size() to fill in // bits after constructing a BitSet, or after having one passed in as a // parameter. setLengthWords(array, wordIndex(nbits + 31)); } private BitSet(JsArrayInteger array) { this.array = array; } public void and(BitSet set) { // a & a is just a if (this == set) { return; } // trim the second set to avoid extra work trimToSize(set.array); // check if the length is longer than otherLength int otherLength = set.array.length(); if (array.length() > otherLength) { // shrink the array, effectively ANDing those bits to false setLengthWords(array, otherLength); } // truth table // // case | a | b | a & b | change? // 1 | false | false | false | a is already false // 2 | false | true | false | a is already false // 3 | true | false | false | set a to false // 4 | true | true | true | a is already true // // we only need to change something in case 3, so iterate over set a int index = 0; while ((index = nextSetWord(array, index)) != -1) { setWord(array, index, array.get(index) & getWord(set.array, index)); index++; } } public void andNot(BitSet set) { // a & !a is false if (this == set) { // all falses result in an empty BitSet clear(); return; } // trim the second set to avoid extra work trimToSize(array); int length = array.length(); // truth table // // case | a | b | !b | a & !b | change? // 1 | false | false | true | false | a is already false // 2 | false | true | false | false | a is already false // 3 | true | false | true | true | a is already true // 4 | true | true | false | false | set a to false // // we only need to change something in case 4 // whenever b is true, a should be false, so iterate over set b int index = 0; while ((index = nextSetWord(set.array, index)) != -1) { setWord(array, index, getWord(array, index) & ~set.array.get(index)); if (++index >= length) { // nothing further will affect anything break; } } } public native int cardinality() /*-{ var count = 0; var array = this.@java.util.BitSet::array; for (var property in array) { var number = property >>> 0; if (String(number) == property && number !== 0xffffffff) { count += @java.lang.Integer::bitCount(I)(array[number]); } } return count; }-*/; public void clear() { // create a new array array = JavaScriptObject.createArray().cast(); } public void clear(int bitIndex) { checkIndex(bitIndex); clear(array, bitIndex); } public void clear(int fromIndex, int toIndex) { checkRange(fromIndex, toIndex); int length = length(); if (fromIndex >= length) { // nothing to do return; } // check to see if toIndex is greater than our array length if (toIndex >= length) { // truncate the array by setting it's length int newLength = wordIndex(fromIndex + 31); setLengthWords(array, newLength); // remove the extra bits off the end if ((bitIndex(newLength)) - fromIndex != 0) { maskOutWord(array, newLength - 1, bitOffset(fromIndex), 32); } } else { int first = wordIndex(fromIndex); int last = wordIndex(toIndex); int startBit = bitOffset(fromIndex); int endBit = bitOffset(toIndex); if (first == last) { // clear the bits in between first and last maskOutWord(array, first, startBit, endBit); } else { // clear the bits from fromIndex to the next 32 bit boundary if (startBit != 0) { maskOutWord(array, first++, startBit, 32); } // clear the bits from the last 32 bit boundary to the toIndex if (endBit != 0) { maskOutWord(array, last, 0, endBit); } // // delete everything in between // for (int i = first; i < last; i++) { deleteWord(array, i); } } } } public Object clone() { return new BitSet(clone(array)); } @Override public boolean equals(Object obj) { if (this != obj) { if (!(obj instanceof BitSet)) { return false; } BitSet other = (BitSet) obj; int last = trimToSize(array); if (last != trimToSize(other.array)) { return false; } int index = 0; while ((index = nextSetWord(array, index)) != -1) { if (getWord(array, index) != getWord(other.array, index)) { return false; } index++; } } return true; } public void flip(int bitIndex) { checkIndex(bitIndex); flip(array, bitIndex); } public void flip(int fromIndex, int toIndex) { checkRange(fromIndex, toIndex); int length = length(); // if we are flipping bits beyond our length, we are setting them to true if (fromIndex >= length) { set(array, fromIndex, toIndex); return; } // check to see if toIndex is greater than our array length if (toIndex >= length) { set(array, length, toIndex); toIndex = length; } int first = wordIndex(fromIndex); int last = wordIndex(toIndex); int startBit = bitOffset(fromIndex); int end = bitOffset(toIndex); if (first == last) { // flip the bits in between first and last flipMaskedWord(array, first, startBit, end); } else { // clear the bits from fromIndex to the next 32 bit boundary if (startBit != 0) { flipMaskedWord(array, first++, startBit, 32); } // clear the bits from the last 32 bit boundary to the toIndex if (end != 0) { flipMaskedWord(array, last, 0, end); } // flip everything in between for (int i = first; i < last; i++) { flipWord(array, i); } } } public boolean get(int bitIndex) { checkIndex(bitIndex); return get(array, bitIndex); } public BitSet get(int fromIndex, int toIndex) { checkRange(fromIndex, toIndex); // no need to go past our length toIndex = Math.min(toIndex, length()); // this is the bit shift offset for each group of bits int rightShift = bitOffset(fromIndex); if (rightShift == 0) { int subFrom = wordIndex(fromIndex); int subTo = wordIndex(toIndex + 31); JsArrayInteger subSet = slice(array, subFrom, subTo); int leftOvers = bitOffset(toIndex); if (leftOvers != 0) { maskOutWord(subSet, subTo - subFrom - 1, leftOvers, 32); } return new BitSet(subSet); } BitSet subSet = new BitSet(); int first = wordIndex(fromIndex); int last = wordIndex(toIndex); if (first == last) { // number of bits to cut from the end int end = 32 - (bitOffset(toIndex)); // raw bits int word = getWord(array, first); // shift out those bits word = ((word << end) >>> end) >>> rightShift; // set it if (word != 0) { subSet.set(0, word); } } else { // this will hold the newly packed bits int current = 0; // this is the raw index into the sub set int subIndex = 0; // fence post, carry over initial bits int word = getWord(array, first++); current = word >>> rightShift; // a left shift will be used to shift our bits to the top of "current" int leftShift = 32 - rightShift; // loop through everything in the middle for (int i = first; i <= last; i++) { word = getWord(array, i); // shift out the bits from the top, OR them into current bits current |= word << leftShift; // flush it out if (current != 0) { subSet.array.set(subIndex, current); } // keep track of our index subIndex++; // carry over the unused bits current = word >>> rightShift; } // fence post, flush out the extra bits, but don't go past the "end" int end = 32 - (bitOffset(toIndex)); current = (current << (rightShift + end)) >>> (rightShift + end); if (current != 0) { subSet.array.set(subIndex, current); } } return subSet; } /** * This hash is different than the one described in Sun's documentation. The * described hash uses 64 bit integers and that's not practical in JavaScript. */ @Override public int hashCode() { // FNV constants final int fnvOffset = 0x811c9dc5; final int fnvPrime = 0x1000193; // initialize final int last = trimToSize(array); int hash = fnvOffset ^ last; // loop over the data for (int i = 0; i <= last; i++) { int value = getWord(array, i); // hash one byte at a time using FNV1 hash = (hash * fnvPrime) ^ (value & 0xff); hash = (hash * fnvPrime) ^ ((value >>> 8) & 0xff); hash = (hash * fnvPrime) ^ ((value >>> 16) & 0xff); hash = (hash * fnvPrime) ^ (value >>> 24); } return hash; } public boolean intersects(BitSet set) { int last = trimToSize(array); if (this == set) { // if it has any bits then it intersects itself return last != -1; } int length = set.array.length(); int index = 0; while ((index = nextSetWord(array, index)) != -1) { if ((array.get(index) & getWord(set.array, index)) != 0) { return true; } if (++index >= length) { // nothing further can intersect break; } } return false; } public boolean isEmpty() { return length() == 0; } public int length() { int last = trimToSize(array); if (last == -1) { return 0; } // compute the position of the leftmost bit's index int offsets[] = { 16, 8, 4, 2, 1 }; int bitMasks[] = { 0xffff0000, 0xff00, 0xf0, 0xc, 0x2 }; int position = bitIndex(last) + 1; int word = getWord(array, last); for (int i = 0; i < offsets.length; i++) { if ((word & bitMasks[i]) != 0) { word >>>= offsets[i]; position += offsets[i]; } } return position; } public int nextClearBit(int fromIndex) { checkIndex(fromIndex); int index = wordIndex(fromIndex); // special case for first index int fromBit = fromIndex - (bitIndex(index)); int word = getWord(array, index); for (int i = fromBit; i < 32; i++) { if ((word & (1 << i)) == 0) { return (bitIndex(index)) + i; } } // loop through the rest do { index++; word = getWord(array, index); } while (word == 0xffffffff); return bitIndex(index) + Integer.numberOfTrailingZeros(~word); } public int nextSetBit(int fromIndex) { checkIndex(fromIndex); int index = wordIndex(fromIndex); // check the current word int word = getWord(array, index); if (word != 0) { for (int i = bitOffset(fromIndex); i < 32; i++) { if ((word & (1 << i)) != 0) { return (bitIndex(index)) + i; } } } index++; // find the next set word trimToSize(array); index = nextSetWord(array, index); if (index == -1) { return -1; } // return the next set bit return (bitIndex(index)) + Integer.numberOfTrailingZeros(array.get(index)); } public void or(BitSet set) { // a | a is just a if (this == set) { return; } // truth table // // case | a | b | a | b | change? // 1 | false | false | false | a is already false // 2 | false | true | true | set a to true // 3 | true | false | true | a is already true // 4 | true | true | true | a is already true // // we only need to change something in case 2 // case 2 only happens when b is true, so iterate over set b int index = 0; while ((index = nextSetWord(set.array, index)) != -1) { setWord(array, index, getWord(array, index) | set.array.get(index)); index++; } } public void set(int bitIndex) { checkIndex(bitIndex); set(array, bitIndex); } public void set(int bitIndex, boolean value) { if (value == true) { set(bitIndex); } else { clear(bitIndex); } } public void set(int fromIndex, int toIndex) { checkRange(fromIndex, toIndex); set(array, fromIndex, toIndex); } public void set(int fromIndex, int toIndex, boolean value) { if (value == true) { set(fromIndex, toIndex); } else { clear(fromIndex, toIndex); } } public int size() { // the number of bytes that can fit without using "more" memory return bitIndex(array.length()); } @Override public String toString() { // possibly faster if done in JavaScript and all numerical properties are // put into an array and sorted int length = length(); if (length == 0) { // a "length" of 0 means there are no bits set to true return "{}"; } StringBuilder sb = new StringBuilder("{"); // at this point, there is at least one true bit, nextSetBit can not fail int next = nextSetBit(0); sb.append(next); // loop until nextSetBit returns -1 while ((next = nextSetBit(next + 1)) != -1) { sb.append(", "); sb.append(next); } sb.append("}"); return sb.toString(); } public void xor(BitSet set) { // a ^ a is false if (this == set) { // this results in an empty BitSet clear(); return; } // truth table // // case | a | b | a ^ b | change? // 1 | false | false | false | a is already false // 2 | false | true | true | set a to true // 3 | true | false | true | a is already true // 4 | true | true | false | set a to false // // we need to change something in cases 2 and 4 // cases 2 and 4 only happen when b is true, so iterate over set b int index = 0; while ((index = nextSetWord(set.array, index)) != -1) { setWord(array, index, getWord(array, index) ^ set.array.get(index)); index++; } } }