001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023package dk.netarkivet.common.utils;
024
025import java.util.BitSet;
026import java.util.HashSet;
027import java.util.Set;
028
029/**
030 * A sparse implementation of a BitSet, that does not require memory linear to the largest index. This is done at the
031 * cost of performance, but should be fairly efficient on few set bits.
032 */
033@SuppressWarnings({"serial"})
034public class SparseBitSet extends BitSet {
035
036    /** A set of the indices of bits that are set in this BitSet. */
037    private Set<Integer> setbits = new HashSet<Integer>();
038
039    /**
040     * Initialise the bitset.
041     */
042    public SparseBitSet() {
043        // Initialise super class to a zero-length bitset, to avoid allocating
044        // a bit array.
045        super(0);
046    }
047
048    @Override
049    public void flip(int bitIndex) {
050        if (bitIndex < 0) {
051            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
052        }
053        if (setbits.contains(bitIndex)) {
054            setbits.remove(bitIndex);
055        } else {
056            setbits.add(bitIndex);
057        }
058    }
059
060    @Override
061    public void flip(int fromIndex, int toIndex) {
062        if (fromIndex < 0) {
063            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
064        }
065        if (toIndex < 0) {
066            throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
067        }
068        if (fromIndex > toIndex) {
069            throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex);
070        }
071        for (int i = fromIndex; i < toIndex; i++) {
072            flip(i);
073        }
074    }
075
076    @Override
077    public void set(int bitIndex) {
078        if (bitIndex < 0) {
079            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
080        }
081        setbits.add(bitIndex);
082    }
083
084    @Override
085    public void set(int bitIndex, boolean value) {
086        if (bitIndex < 0) {
087            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
088        }
089        if (value) {
090            setbits.add(bitIndex);
091        } else {
092            setbits.remove(bitIndex);
093        }
094    }
095
096    @Override
097    public void set(int fromIndex, int toIndex) {
098        if (fromIndex < 0) {
099            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
100        }
101        if (toIndex < 0) {
102            throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
103        }
104        if (fromIndex > toIndex) {
105            throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex);
106        }
107        for (int i = fromIndex; i < toIndex; i++) {
108            set(i);
109        }
110    }
111
112    @Override
113    public void set(int fromIndex, int toIndex, boolean value) {
114        if (fromIndex < 0) {
115            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
116        }
117        if (toIndex < 0) {
118            throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
119        }
120        if (fromIndex > toIndex) {
121            throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex);
122        }
123        for (int i = fromIndex; i < toIndex; i++) {
124            set(i, value);
125        }
126    }
127
128    @Override
129    public void clear(int bitIndex) {
130        if (bitIndex < 0) {
131            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
132        }
133        setbits.remove(bitIndex);
134    }
135
136    @Override
137    public void clear(int fromIndex, int toIndex) {
138        if (fromIndex < 0) {
139            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
140        }
141        if (toIndex < 0) {
142            throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
143        }
144        if (fromIndex > toIndex) {
145            throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex);
146        }
147        for (int i = fromIndex; i < toIndex; i++) {
148            clear(i);
149        }
150    }
151
152    @Override
153    public void clear() {
154        setbits.clear();
155    }
156
157    @Override
158    public boolean get(int bitIndex) {
159        if (bitIndex < 0) {
160            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
161        }
162        return setbits.contains(bitIndex);
163    }
164
165    @Override
166    public BitSet get(int fromIndex, int toIndex) {
167        if (fromIndex < 0) {
168            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
169        }
170        if (toIndex < 0) {
171            throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
172        }
173        if (fromIndex > toIndex) {
174            throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex);
175        }
176        SparseBitSet bitsubset = new SparseBitSet();
177        for (int i : setbits) {
178            if (i >= fromIndex && i < toIndex) {
179                bitsubset.set(i - fromIndex);
180            }
181        }
182        return bitsubset;
183    }
184
185    @Override
186    public int nextSetBit(int fromIndex) {
187        if (fromIndex < 0) {
188            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
189        }
190        int index = -1;
191        for (Integer i : setbits) {
192            if (i >= fromIndex && (index == -1 || i < index)) {
193                index = i;
194            }
195        }
196        return index;
197    }
198
199    @Override
200    public int nextClearBit(int fromIndex) {
201        if (fromIndex < 0) {
202            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
203        }
204        for (int i = fromIndex; i > 0; i++) {
205            if (!get(i)) {
206                return i;
207            }
208        }
209        return Integer.MIN_VALUE;
210    }
211
212    @Override
213    public int length() {
214        int index = -1;
215        for (Integer i : setbits) {
216            if (i > index) {
217                index = i;
218            }
219        }
220        return index + 1;
221    }
222
223    @Override
224    public boolean isEmpty() {
225        return setbits.isEmpty();
226    }
227
228    @Override
229    public boolean intersects(BitSet set) {
230        for (Integer index : setbits) {
231            if (set.get(index)) {
232                return true;
233            }
234        }
235        return false;
236    }
237
238    @Override
239    public int cardinality() {
240        return setbits.size();
241    }
242
243    @Override
244    public void and(BitSet set) {
245        Set<Integer> andbits = new HashSet<Integer>();
246        for (Integer index : setbits) {
247            if (set.get(index)) {
248                andbits.add(index);
249            }
250        }
251        setbits = andbits;
252    }
253
254    @Override
255    public void or(BitSet set) {
256        Set<Integer> orbits = new HashSet<Integer>(setbits);
257        for (int index = set.nextSetBit(0); index != -1; index = set.nextSetBit(index + 1)) {
258            orbits.add(index);
259        }
260        setbits = orbits;
261    }
262
263    @Override
264    public void xor(BitSet set) {
265        Set<Integer> xorbits = new HashSet<Integer>();
266        for (Integer index : setbits) {
267            if (!set.get(index)) {
268                xorbits.add(index);
269            }
270        }
271        for (int index = set.nextSetBit(0); index != -1; index = set.nextSetBit(index + 1)) {
272            if (!setbits.contains(index)) {
273                xorbits.add(index);
274            }
275        }
276        setbits = xorbits;
277    }
278
279    @Override
280    public void andNot(BitSet set) {
281        Set<Integer> andnotbits = new HashSet<Integer>(setbits);
282        for (Integer index : setbits) {
283            if (set.get(index)) {
284                andnotbits.remove(index);
285            }
286        }
287        setbits = andnotbits;
288    }
289
290    /**
291     * A hash code for this bit set. Note: The hash codes are not implemented to be compatible with
292     * java.util.BitSet#hashCode(). Implementing that algorithm would be difficult and inefficient on the current
293     * implementation.
294     *
295     * @return A hashcode.
296     */
297    public int hashCode() {
298        return setbits.hashCode();
299    }
300
301    /**
302     * In contrast with {@link BitSet#size()} this does not return the size in bytes used to represent this set.
303     * Instead, it returns the same as {@link #length()} for compatibility with {@link BitSet}. The actual space used is
304     * a hashset of size {@link #cardinality()}.
305     *
306     * @return the same as {@link #length()}
307     */
308    public int size() {
309        return length();
310    }
311
312    /**
313     * Two SparseBitSets are considered equal if they contain the same bits.
314     * <p>
315     * Note: A SparseBitSet is never considered equal to a BitSet. This would be impossible to implement in a way so
316     * equality is symmetric, since {@link BitSet#equals(Object)} is implemented using its private fields to determine
317     * equality.
318     *
319     * @param obj The object to compare for equality.
320     * @return true, if obj is a SparseBitSet and contains the same bits as this object.
321     */
322    public boolean equals(Object obj) {
323        return obj instanceof SparseBitSet && setbits.equals(((SparseBitSet) obj).setbits);
324    }
325
326    @Override
327    public Object clone() {
328        super.clone();
329        SparseBitSet newSparseBitSet = new SparseBitSet();
330        newSparseBitSet.setbits = new HashSet<Integer>(setbits);
331        return newSparseBitSet;
332    }
333
334    @Override
335    public String toString() {
336        return setbits.toString();
337    }
338
339}