001package org.cache2k.impl;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import org.cache2k.ClosableIterator;
026
027/**
028 * Iterator over all hash table entries of two hashes.
029 *
030 * <p>The hash has a usage/reference counter for iterations
031 * to suspend expand until the iteration finished. This is needed
032 * for correctness of the iteration, if an expand is done during
033 * the iteration process, the iterations returns duplicate
034 * entries or not all entries.
035 *
036 * <p>Failing to operate the increment/decrement in balance will
037 * mean that the hash table expands are blocked forever, which is a
038 * serious error condition. Typical problems arise by thrown
039 * exceptions during an iteration. Since there is a measure for the
040 * hash quality, a problem like this may be detected.
041 *
042 * <p>Rationale: We need to keep track of the entries/keys iterated.
043 * The cache may remove and insert entries from hash to refreshHash,
044 * also the application may do a remove and insert. A removed entry
045 * has always e.another kept intact, so traversal of the collision list
046 * will always work. If a iteration is going on, this means a removed
047 * entry needs to be cloned to reassign the e.another pointer.
048 *
049 * <p>Rationale: The iteration just works on the hash data structure.
050 * However, it needs to be checked, whether the cache is closed
051 * meanwhile. To signal this, the hash is closed also. This is
052 * a little complex, but safes the dependency on the cache here.
053 *
054 * @author Jens Wilke; created: 2013-12-21
055 */
056public class ClosableConcurrentHashEntryIterator<E extends Entry>
057  implements ClosableIterator<E> {
058
059  int iteratedCountLastRun;
060  Entry lastEntry = null;
061  Hash<E> hashCtl;
062  Hash<E> hashCtl2;
063  Entry[] hash;
064  Entry[] hash2;
065  Hash<E> hashCtlCopy;
066  Hash<E> hashCtl2Copy;
067  Entry[] hashCopy;
068  Entry[] hash2Copy;
069  Hash<Entry> iteratedCtl = new Hash<Entry>();
070  Entry[] iterated;
071  boolean keepIterated = false;
072  boolean stopOnClear = true;
073
074  public ClosableConcurrentHashEntryIterator(
075    Hash<E> _hashCtl, E[] _hash,
076    Hash<E> _hashCtl2, E[] _hash2) {
077    hashCopy = hash = _hash;
078    hash2Copy = hash2 = _hash2;
079    hashCtlCopy = hashCtl = _hashCtl;
080    hashCtl2Copy = hashCtl2 = _hashCtl2;
081    _hashCtl.incrementSuppressExpandCount();
082    iterated = iteratedCtl.init(Entry.class);
083  }
084
085  private Entry nextEntry() {
086    Entry e;
087    if (hash == null) {
088      return null;
089    }
090    if (hashCtl.shouldAbort()) {
091      if (checkForClearAndAbort()) {
092        return null;
093      }
094      if (stopOnClear && hashCtl.isCleared()) {
095        throw new CacheClosedException();
096      }
097    }
098    int idx = 0;
099    if (lastEntry != null) {
100      e = lastEntry.another;
101      if (e != null) {
102        e = checkIteratedOrNext((E) e);
103        if (e != null) {
104          lastEntry = e;
105          return e;
106        }
107      }
108      idx = Hash.index(hash, lastEntry.hashCode) + 1;
109    }
110    for (;;) {
111      if (idx >= hash.length) {
112        if (switchAndCheckAbort()) {
113          return null;
114        }
115        idx = 0;
116      }
117      e = hash[idx];
118      if (e != null) {
119        e = checkIteratedOrNext((E) e);
120        if (e != null) {
121          lastEntry = e;
122          return e;
123        }
124      }
125      idx++;
126    }
127  }
128
129  protected E checkIteratedOrNext(E e) {
130    do {
131      boolean _notYetIterated = !Hash.contains(iterated, e.key, e.hashCode);
132      if (_notYetIterated) {
133        Entry _newEntryIterated = new Entry();
134        _newEntryIterated.key = e.key;
135        _newEntryIterated.hashCode = e.hashCode;
136        iterated = iteratedCtl.insert(iterated, _newEntryIterated);
137        return e;
138      }
139      e = (E) e.another;
140    } while (e != null);
141    return null;
142  }
143
144  protected boolean switchAndCheckAbort() {
145    hashCtl.decrementSuppressExpandCount();
146    hash = hash2;
147    hashCtl = hashCtl2;
148    hash2 = null;
149    hashCtl2 = null;
150    if (hash == null) {
151      lastEntry = null;
152      if (iteratedCountLastRun == iteratedCtl.size) {
153        close();
154        return true;
155      }
156      iteratedCountLastRun = iteratedCtl.size;
157      hash = hashCopy;
158      hash2 = hash2Copy;
159      hashCtl = hashCtlCopy;
160      hashCtl2 = hashCtl2Copy;
161    }
162    hashCtl.incrementSuppressExpandCount();
163    return false;
164  }
165
166  /**
167   * Check if hash was cleared and we need to abort. Returns true
168   * if we should abort.
169   */
170  protected boolean checkForClearAndAbort() {
171    if (hashCtl.isCleared()) {
172      close();
173      return true;
174    }
175    return false;
176  }
177
178  @Override
179  public boolean hasNext() {
180    return nextEntry() != null;
181  }
182
183  @Override
184  public E next() {
185    return (E) lastEntry;
186  }
187
188  @Override
189  public void remove() {
190    throw new UnsupportedOperationException();
191  }
192
193  @Override
194  public void close() {
195    if (hashCtl != null) {
196      hashCtl.decrementSuppressExpandCount();
197      hashCtl = hashCtl2 = null;
198      hash = hash2 = null;
199      hashCtlCopy = hashCtl2Copy = null;
200      hashCopy = hash2Copy = null;
201      lastEntry = null;
202      if (!keepIterated) {
203        iterated = null;
204        iteratedCtl = null;
205      }
206    }
207  }
208
209  @Override
210  protected void finalize() throws Throwable {
211    super.finalize();
212    close();
213  }
214
215  /**
216   * Keep hash of iterated items, needed for storage iteration.
217   */
218  public void setKeepIterated(boolean keepIterated) {
219    this.keepIterated = keepIterated;
220  }
221
222  /**
223   * Iterations stops when storage is cleared, default is true.
224   */
225  public void setStopOnClear(boolean stopOnClear) {
226    this.stopOnClear = stopOnClear;
227  }
228}