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}