001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.util;
014
015import java.util.Iterator;
016import java.util.NoSuchElementException;
017
018import org.jikesrvm.VM;
019import org.jikesrvm.mm.mminterface.MemoryManager;
020
021/**
022 * Common super class for all VM hash sets.
023 */
024abstract class AbstractHashSetRVM<T>  implements Iterable<T> {
025
026  protected static final int DEFAULT_SIZE = 7;
027  private static final float LOAD = 3;
028  protected AbstractBucket<T>[] buckets;
029  protected int numElems = 0;
030
031  abstract static class AbstractBucket<T> {
032    /**
033     * Change the next bucket after this bucket, possibly constructing a new
034     * abstract bucket.
035     *
036     * @param next the new value for the next bucket
037     * @return previous bucket of the given bucket
038     */
039    abstract AbstractBucket<T> setNext(AbstractBucket<T> next);
040
041    abstract AbstractBucket<T> getNext();
042
043    abstract T getKey();
044  }
045
046  abstract AbstractBucket<T> createNewBucket(T key, AbstractBucket<T> next);
047
048  @SuppressWarnings("unchecked")
049  protected AbstractBucket<T>[] newBucketArray(int size) {
050    return new AbstractBucket[size];
051  }
052
053  AbstractHashSetRVM(int size) {
054    buckets = newBucketArray(size);
055  }
056
057  public int size() {
058    return numElems;
059  }
060
061  /**
062   * Advise against growing the buckets if they are immortal, as it will lead
063   * to multiple sets of buckets that will be scanned
064   *
065   * @return whether the map is allowed to grow
066   */
067  private boolean growMapAllowed() {
068    return !VM.runningVM || !MemoryManager.isImmortal(buckets);
069  }
070
071  public void add(T key) {
072    if (VM.VerifyAssertions) VM._assert(key != null);
073    if (growMapAllowed() && numElems > (buckets.length * LOAD)) {
074      growMap();
075    }
076
077    int bucketIdx = bucketIndex(key, buckets.length);
078    AbstractBucket<T> cur = buckets[bucketIdx];
079    while (cur != null && !cur.getKey().equals(key)) {
080      cur = cur.getNext();
081    }
082    if (cur == null) {
083      buckets[bucketIdx] = createNewBucket(key, buckets[bucketIdx]);
084      numElems++;
085    }
086  }
087
088  public T get(T key) {
089    if (key == null) {
090      return null;
091    }
092    int bucketIdx = bucketIndex(key, buckets.length);
093    AbstractBucket<T> cur = buckets[bucketIdx];
094    while (cur != null && !cur.getKey().equals(key)) {
095      cur = cur.getNext();
096    }
097    if (cur == null) {
098      return null;
099    } else {
100      return cur.getKey();
101    }
102  }
103
104  public boolean contains(T key) {
105    return get(key) != null;
106  }
107
108  public void addAll(AbstractHashSetRVM<T> c) {
109    for (T t : c) {
110      add(t);
111    }
112  }
113
114  private void growMap() {
115    AbstractBucket<T>[] newBuckets = newBucketArray(buckets.length * 2 + 1);
116    for (AbstractBucket<T> cur : buckets) {
117      while (cur != null) {
118        AbstractBucket<T> next = cur.getNext();
119        int newIdx = bucketIndex(cur.getKey(), newBuckets.length);
120        cur = cur.setNext(newBuckets[newIdx]);
121        newBuckets[newIdx] = cur;
122        cur = next;
123      }
124    }
125    buckets = newBuckets;
126  }
127
128  public void remove(T key) {
129    if (VM.VerifyAssertions) VM._assert(key != null);
130    int bucketIdx = bucketIndex(key, buckets.length);
131    AbstractBucket<T> cur = buckets[bucketIdx];
132    AbstractBucket<T> prev = null;
133    while (cur != null && !cur.getKey().equals(key)) {
134      prev = cur;
135      cur = cur.getNext();
136    }
137    if (cur != null) {
138      if (prev == null) {
139        // removing first bucket in chain.
140        buckets[bucketIdx] = cur.getNext();
141      } else {
142        AbstractBucket<T> newPrev = prev.setNext(cur.getNext());
143        if (newPrev != prev) {
144          throw new UnsupportedOperationException();
145        }
146      }
147      numElems--;
148    }
149  }
150
151  public void removeAll() {
152    for (int i = 0; i < buckets.length; i++) {
153      buckets[i] = null;
154    }
155    numElems = 0;
156  }
157
158  @Override
159  public Iterator<T> iterator() {
160    return new SetIterator();
161  }
162
163  private int bucketIndex(T key, int divisor) {
164    if (key == null) {
165      return 0;
166    } else {
167      return (key.hashCode() & 0x7fffffff) % divisor;
168    }
169  }
170
171  /**
172   * Iterator
173   */
174  class SetIterator implements Iterator<T> {
175    private int bucketIndex = 0;
176    private AbstractBucket<T> next = null;
177    private int numVisited = 0;
178
179    @Override
180    public T next() {
181      if (!hasNext()) {
182        throw new NoSuchElementException();
183      }
184
185      while (next == null) {
186        next = buckets[bucketIndex++];
187      }
188      AbstractBucket<T> ans = next;
189      next = ans.getNext();
190      numVisited++;
191      return ans.getKey();
192    }
193
194    @Override
195    public boolean hasNext() {
196      return numVisited < numElems;
197    }
198
199    @Override
200    public void remove() {
201      throw new UnsupportedOperationException();
202    }
203  }
204}