2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
6 * The Java Pathfinder core (jpf-core) platform is licensed under the
7 * Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0.
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package gov.nasa.jpf.util;
20 import java.util.ConcurrentModificationException;
21 import java.util.Iterator;
22 import java.util.NoSuchElementException;
25 * A generic sparse reference array that assumes clusters, and more
26 * frequent intra-cluster access.
28 * This is motivated by looking for a more suitable ADT to model a heap address
29 * space, where reference values consist of segment/offset pairs, we have
30 * reasonably dense and very dynamic population inside of well separated segment
31 * clusters, and intra-cluster access is more likely than inter-cluster access.
33 * An especially important feature is to be able to iterate efficiently over
34 * set/unset elements in index intervals (cluster sizes).
36 * The result should find a compromise between the fast element access & iteration
37 * of a simple, dense array, and the efficient memory storage of a HashMap
38 * (if only it could avoid box objects).
40 * <2do> so far, we are totally ignorant of population constraints
42 public class SparseClusterArray <E> implements Iterable<E> {
44 public static final int CHUNK_BITS = 8;
45 public static final int CHUNK_SIZE = 256;
46 public static final int N_ELEM = 1 << CHUNK_BITS; // 8 bits chunk index -> 24 bits segment key (3x8bits / 256 segs)
47 protected static final int ELEM_MASK = 0xff;
48 protected static final int BM_ENTRIES = N_ELEM / 64; // number of bitmap long entries
49 protected static final int MAX_BM_INDEX = BM_ENTRIES-1;
52 // 8 bits per segment -> 256 children
53 public static final int SEG_BITS = 8;
54 public static final int N_SEG = 1 << SEG_BITS;
55 protected static final int SEG_MASK = 0xff;
56 public static final int S1 = 32-SEG_BITS; // L1 shift
57 public static final int S2 = S1-SEG_BITS; // L2 shift
58 public static final int S3 = S2-SEG_BITS; // L3 shift
59 protected static final int CHUNK_BASEMASK = ~SEG_MASK;
61 public static final int MAX_CLUSTERS = CHUNK_SIZE; // max int with CHUNK_BITS bits (8)
62 public static final int MAX_CLUSTER_ENTRIES = 0xffffff; // max int with 32-CHUNK_BITS bits (24) = 16,777,215 elements
65 protected Chunk lastChunk;
66 protected Chunk head; // linked list for traversal
67 protected int nSet; // number of set elements;
69 protected boolean trackChanges = false;
70 protected Entry changes; // on demand change (LIFO) queue
72 //------------------------------------ public types
73 public static class Snapshot<T,E> {
77 public Snapshot (int size){
78 values = new Object[size];
79 indices = new int[size];
83 return indices.length;
85 public T getValue(int i){
88 public int getIndex(int i){
94 public static class Entry<E> { // queued element
100 Entry (int index, E value){
106 //------------------------------------ internal types
108 //--- how we keep our data - index based trie
109 protected static class Root {
110 public Node[] seg = new Node[N_SEG];
114 * this corresponds to a toplevel cluster (e.g. thread heap)
116 protected static class Node {
117 public ChunkNode[] seg = new ChunkNode[N_SEG];
118 //int minNextFree; // where to start looking for free elements, also used to determine if Node is full
121 protected static class ChunkNode {
122 public Chunk[] seg = new Chunk[N_SEG];
123 //int minNextFree; // where to start looking for free elements, also used to determine if ChunkNode is full
126 protected static class Chunk implements Cloneable { // with some extra info to optimize in-chunk access
127 public int base, top;
129 public Object[] elements; // it's actually E[], but of course we can't create arrays of a generic type
130 public long[] bitmap;
132 //int minNextFree; // where to start looking for free elements, also used to determine if Chunk is full
136 protected Chunk(int base){
138 this.top = base + N_ELEM;
140 elements = new Object[N_ELEM];
141 bitmap = new long[BM_ENTRIES];
145 public String toString() {
146 return "Chunk [base=" + base + ",top=" + top + ']';
149 @SuppressWarnings("unchecked")
150 public <E> Chunk deepCopy( Cloner<E> cloner) throws CloneNotSupportedException {
151 Chunk nc = (Chunk) super.clone();
153 E[] elem = (E[])elements; // bad, but we have to cope with type erasure
154 Object[] e = new Object[N_ELEM];
156 for (int i=nextSetBit(0); i>=0; i=nextSetBit(i+1)) {
157 e[i] = cloner.clone(elem[i]);
161 nc.bitmap = bitmap.clone();
166 protected int nextSetBit (int iStart) {
167 if (iStart < CHUNK_SIZE){
169 int j = (iStart >> 6); // bm word : iStart/64
170 long l = bm[j] & (0xffffffffffffffffL << iStart);
174 return Long.numberOfTrailingZeros(l) + (j << 6);
176 if (++j < BM_ENTRIES) {
188 protected int nextClearBit (int iStart) {
189 if (iStart < CHUNK_SIZE){
191 int j = (iStart >> 6); // bm word : iStart/64
192 long l = ~bm[j] & (0xffffffffffffffffL << iStart);
196 return Long.numberOfTrailingZeros(l) + (j << 6);
198 if (++j < BM_ENTRIES) {
211 public boolean isEmpty() {
214 for (int i=0; i<BM_ENTRIES; i++){
215 if (bm[i] != 0) return false;
222 //--- iteration over set elements
224 protected class ElementIterator<T> implements Iterator<T>, Iterable<T> {
225 int idx; // next chunk index
226 Chunk cur; // next chunk
228 public ElementIterator () {
229 for (Chunk c = head; c != null; c = c.next){
230 int i = c.nextSetBit(0);
240 public boolean hasNext() {
241 return (cur != null);
245 @SuppressWarnings("unchecked")
250 if (i < 0 || c == null){
251 throw new NoSuchElementException();
254 Object ret = c.elements[i];
258 i = c.nextSetBit(i+1);
264 // try to recover from a concurrent modification, maybe there is one left
277 // somebody pulled the rug under our feet
278 throw new ConcurrentModificationException();
284 public void remove() {
285 throw new UnsupportedOperationException();
289 public Iterator<T> iterator() {
294 protected class ElementIndexIterator implements IndexIterator {
298 public ElementIndexIterator () {
299 for (Chunk c = head; c != null; c = c.next){
300 int i = c.nextSetBit(0);
309 public ElementIndexIterator (int startIdx){
310 // locate the start chunk (they are sorted)
314 // get the first chunk at or above the startIdx
315 for (c=head; c!= null; c=c.next) {
316 if (c.top > startIdx) {
322 if (c.base < startIdx){
323 i = startIdx & ELEM_MASK;
328 for (; c != null; c = c.next){
346 if (i < 0 || c == null){
350 int iRet = (c.elements[i] != null) ? c.base + i : -1;
354 i = c.nextSetBit(i+1);
360 // try to recover from a concurrent modification, maybe there is one left
373 // somebody pulled the rug under our feet
374 throw new ConcurrentModificationException();
382 //------------------------------------ internal methods
384 void sortInChunk (Chunk newChunk) {
388 int base = newChunk.base;
389 if (base < head.base) {
390 newChunk.next = head;
394 for (cprev=head, c=cprev.next; c != null; cprev=c, c=c.next) {
400 cprev.next = newChunk;
405 //------------------------------------ public API
407 public SparseClusterArray (){
412 * be careful, this should only be used to get old stored elements during
415 protected SparseClusterArray (SparseClusterArray base){
421 @SuppressWarnings("unchecked")
422 public E get (int i) {
425 Chunk l3 = lastChunk;
428 throw new IndexOutOfBoundsException();
431 if (l3 != null && (l3.base == (i & CHUNK_BASEMASK))) { // cache optimization for in-cluster access
432 return (E) l3.elements[i & ELEM_MASK];
436 if ((l1 = root.seg[j]) != null) { // L1
437 j = (i >>> S2) & SEG_MASK;
438 if ((l2 = l1.seg[j]) != null) { // L2
439 j = (i >>> S3) & SEG_MASK;
440 if ((l3 = l2.seg[j]) != null) { // L3
441 // too bad we can't get rid of this cast
443 return (E) l3.elements[i & ELEM_MASK];
453 public void set (int i, E e) {
456 Chunk l3 = lastChunk;
460 throw new IndexOutOfBoundsException();
463 if (l3 == null || (l3.base != (i & CHUNK_BASEMASK))) { // cache optimization for in-cluster access
465 if ((l1 = root.seg[j]) == null) { // new L1 -> new L2,L3
469 j = (i >>> S2) & SEG_MASK;
470 l2 = new ChunkNode();
473 j = (i >>> S3) & SEG_MASK;
474 l3 = new Chunk(i & ~ELEM_MASK);
479 j = (i >>> S2) & SEG_MASK;
480 if ((l2 = l1.seg[j]) == null) { // new L2 -> new L3
481 l2 = new ChunkNode();
484 j = (i >>> S3) & SEG_MASK;
485 l3 = new Chunk(i & ~ELEM_MASK);
490 j = (i >>> S3) & SEG_MASK;
491 if ((l3 = l2.seg[j]) == null) { // new L3
492 l3 = new Chunk(i & ~ELEM_MASK);
504 long[] bm = l3.bitmap;
505 int u = (j >> 6); // j / 64 (64 bits per bm entry)
506 int v = (i & 0x7f); // index into bm[u] bitset
507 boolean isSet = ((bm[u] >> v) & 0x1) > 0;
510 Entry entry = new Entry(i,l3.elements[j]);
511 entry.next = changes;
524 l3.elements[j] = null;
527 // <2do> discard upwards if chunk is empty ? (maybe as an option)
533 * find first null element within given range [i, i+length[
534 * @return -1 if there is none
536 public int firstNullIndex (int i, int length) {
539 Chunk l3 = lastChunk;
541 int iMax = i + length;
543 if (l3 == null || (l3.base != (i & CHUNK_BASEMASK))) { // cache optimization for in-cluster access
545 if ((l1 = root.seg[j]) != null) { // new L1 -> new L2,L3
546 j = (i >>> S2) & SEG_MASK;
547 if ((l2 = l1.seg[j]) != null) { // new L2 -> new L3
548 j = (i >>> S3) & SEG_MASK;
549 if ((l3 = l2.seg[j]) == null){
550 return i; // no such l3 segment -> index is free
553 return i; // no such l2 segment yet -> index is free
555 } else { // we don't have that root segment yet -> index is free
560 int k = i & SEG_MASK;
562 k = l3.nextClearBit(k);
564 if (k >= 0) { // Ok, got one in the chunk
567 return (i < iMax) ? i : -1;
569 } else { // chunk full
570 Chunk l3Next = l3.next;
571 int nextBase = l3.base + CHUNK_SIZE;
572 if ((l3Next != null) && (l3Next.base == nextBase)) {
573 if (nextBase < iMax) {
581 return (nextBase < iMax) ? nextBase : -1;
586 // no allocated chunk for 'i'
593 * we need to do this depth first, right-to-left, to maintain the
594 * Chunk list ordering. We also compact during cloning, i.e. remove
595 * empty chunks and ChunkNodes/Nodes w/o descendants
597 public SparseClusterArray<E> deepCopy (Cloner<E> elementCloner) {
598 SparseClusterArray<E> a = new SparseClusterArray<E>();
601 Node[] newNodeList = a.root.seg;
604 ChunkNode newChunkNode = null;
605 Chunk newChunk = null, lastChunk = null;
607 Node[] nList = root.seg;
610 for (int i=0, i1=0; i<nList.length; i++) {
613 ChunkNode[] cnList = n.seg;
615 for (int j=0, j1=0; j<cnList.length; j++) {
616 ChunkNode cn = cnList[j];
618 Chunk[] cList = cn.seg;
620 for (int k=0, k1=0; k<cList.length; k++) {
623 if (c != null && !c.isEmpty()) {
624 newChunk = c.deepCopy(elementCloner);
625 if (lastChunk == null) {
626 a.head = lastChunk = newChunk;
628 lastChunk.next = newChunk;
629 lastChunk = newChunk;
632 // create the required ChunkNode/Node instances
633 if (newNode == null) {
634 newNode = new Node();
636 newNodeList[i1++] = newNode;
639 if (newChunkNode == null) {
640 newChunkNode = new ChunkNode();
641 newNode.seg[j1++] = newChunkNode;
644 newChunkNode.seg[k1++] = newChunk;
653 } catch (CloneNotSupportedException cnsx) {
654 return null; // maybe we should re-raise
661 * create a snapshot that can be used to restore a certain state of our array
662 * This is more suitable than cloning in case the array is very sparse, or
663 * the elements contain a lot of transient data we don't want to store
665 public <T> Snapshot<E,T> getSnapshot (Transformer<E,T> transformer){
666 Snapshot<E,T> snap = new Snapshot<E,T>(nSet);
667 populateSnapshot(snap, transformer);
672 protected <T> void populateSnapshot (Snapshot<E,T> snap, Transformer<E,T> transformer){
675 Object[] values = snap.values;
676 int[] indices = snap.indices;
679 for (Chunk c = head; c != null; c = c.next) {
682 while ((i=c.nextSetBit(i+1)) >= 0) {
683 Object val = transformer.transform((E)c.elements[i]);
685 indices[j] = base + i;
694 @SuppressWarnings("unchecked")
695 public <T> void restore (Snapshot<E,T> snap, Transformer<T,E> transformer) {
696 // <2do> - there are more efficient ways to restore small changes,
697 // but since snapshot elements are ordered it should be reasonably fast
700 T[] values = (T[])snap.values;
701 int[] indices = snap.indices;
702 int len = indices.length;
704 for (int i=0; i<len; i++){
705 E obj = transformer.transform(values[i]);
706 int index = indices[i];
712 public void clear() {
721 public void trackChanges () {
725 public void stopTrackingChanges() {
726 trackChanges = false;
729 public boolean isTrackingChanges() {
733 public Entry<E> getChanges() {
737 public void resetChanges() {
741 public void revertChanges (Entry<E> changes) {
742 for (Entry<E> e = changes; e != null; e = e.next) {
743 set(e.index, e.value);
748 public String toString() {
749 return "SparseClusterArray [nSet=" + nSet + ']';
752 public int numberOfElements() {
756 public int numberOfChunks() {
757 // that's only for debugging purposes, we should probably cache
759 for (Chunk c = head; c != null; c = c.next) {
765 //--- iteration over set elements
767 public IndexIterator getElementIndexIterator () {
768 return new ElementIndexIterator();
771 public IndexIterator getElementIndexIterator (int fromIndex) {
772 return new ElementIndexIterator(fromIndex);
776 public Iterator<E> iterator() {
777 return new ElementIterator<E>();
780 public int cardinality () {