├── .gitignore ├── LICENSE-2.0.html ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── romix │ └── scala │ ├── None.java │ ├── Option.java │ ├── Some.java │ └── collection │ └── concurrent │ ├── BasicNode.java │ ├── CNodeBase.java │ ├── Gen.java │ ├── INodeBase.java │ ├── ListMap.java │ ├── MainNode.java │ └── TrieMap.java └── test └── java └── com └── romix └── scala └── collection └── concurrent ├── TestCNodeFlagCollision.java ├── TestCNodeInsertionIncorrectOrder.java ├── TestConcurrentMapPutIfAbsent.java ├── TestConcurrentMapRemove.java ├── TestConcurrentMapReplace.java ├── TestDelete.java ├── TestHashCollisions.java ├── TestHashCollisionsRemove.java ├── TestHashCollisionsRemoveIterator.java ├── TestHelper.java ├── TestInsert.java ├── TestInstantiationSpeed.java ├── TestMapIterator.java ├── TestMultiThreadAddDelete.java ├── TestMultiThreadInserts.java ├── TestMultiThreadMapIterator.java ├── TestReadOnlyAndUpdatableIterators.java └── TestSerialization.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | -------------------------------------------------------------------------------- /LICENSE-2.0.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Apache License, Version 2.0 - The Apache Software Foundation 10 | 11 | 12 |

13 | Apache License
14 | Version 2.0, January 2004
15 | http://www.apache.org/licenses/ 16 |

17 | 18 |

19 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 20 |

21 | 22 |

1. Definitions.

23 | 24 |

25 | "License" shall mean the terms and conditions for use, reproduction, 26 | and distribution as defined by Sections 1 through 9 of this document. 27 |

28 | 29 |

30 | "Licensor" shall mean the copyright owner or entity authorized by 31 | the copyright owner that is granting the License. 32 |

33 | 34 |

35 | "Legal Entity" shall mean the union of the acting entity and all 36 | other entities that control, are controlled by, or are under common 37 | control with that entity. For the purposes of this definition, 38 | "control" means (i) the power, direct or indirect, to cause the 39 | direction or management of such entity, whether by contract or 40 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 41 | outstanding shares, or (iii) beneficial ownership of such entity. 42 |

43 | 44 |

45 | "You" (or "Your") shall mean an individual or Legal Entity 46 | exercising permissions granted by this License. 47 |

48 | 49 |

50 | "Source" form shall mean the preferred form for making modifications, 51 | including but not limited to software source code, documentation 52 | source, and configuration files. 53 |

54 | 55 |

56 | "Object" form shall mean any form resulting from mechanical 57 | transformation or translation of a Source form, including but 58 | not limited to compiled object code, generated documentation, 59 | and conversions to other media types. 60 |

61 | 62 |

63 | "Work" shall mean the work of authorship, whether in Source or 64 | Object form, made available under the License, as indicated by a 65 | copyright notice that is included in or attached to the work 66 | (an example is provided in the Appendix below). 67 |

68 | 69 |

70 | "Derivative Works" shall mean any work, whether in Source or Object 71 | form, that is based on (or derived from) the Work and for which the 72 | editorial revisions, annotations, elaborations, or other modifications 73 | represent, as a whole, an original work of authorship. For the purposes 74 | of this License, Derivative Works shall not include works that remain 75 | separable from, or merely link (or bind by name) to the interfaces of, 76 | the Work and Derivative Works thereof. 77 |

78 | 79 |

80 | "Contribution" shall mean any work of authorship, including 81 | the original version of the Work and any modifications or additions 82 | to that Work or Derivative Works thereof, that is intentionally 83 | submitted to Licensor for inclusion in the Work by the copyright owner 84 | or by an individual or Legal Entity authorized to submit on behalf of 85 | the copyright owner. For the purposes of this definition, "submitted" 86 | means any form of electronic, verbal, or written communication sent 87 | to the Licensor or its representatives, including but not limited to 88 | communication on electronic mailing lists, source code control systems, 89 | and issue tracking systems that are managed by, or on behalf of, the 90 | Licensor for the purpose of discussing and improving the Work, but 91 | excluding communication that is conspicuously marked or otherwise 92 | designated in writing by the copyright owner as "Not a Contribution." 93 |

94 | 95 |

96 | "Contributor" shall mean Licensor and any individual or Legal Entity 97 | on behalf of whom a Contribution has been received by Licensor and 98 | subsequently incorporated within the Work. 99 |

100 | 101 |

2. Grant of Copyright License. 102 | Subject to the terms and conditions of 103 | this License, each Contributor hereby grants to You a perpetual, 104 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 105 | copyright license to reproduce, prepare Derivative Works of, 106 | publicly display, publicly perform, sublicense, and distribute the 107 | Work and such Derivative Works in Source or Object form. 108 |

109 | 110 |

3. Grant of Patent License. 111 | Subject to the terms and conditions of 112 | this License, each Contributor hereby grants to You a perpetual, 113 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 114 | (except as stated in this section) patent license to make, have made, 115 | use, offer to sell, sell, import, and otherwise transfer the Work, 116 | where such license applies only to those patent claims licensable 117 | by such Contributor that are necessarily infringed by their 118 | Contribution(s) alone or by combination of their Contribution(s) 119 | with the Work to which such Contribution(s) was submitted. If You 120 | institute patent litigation against any entity (including a 121 | cross-claim or counterclaim in a lawsuit) alleging that the Work 122 | or a Contribution incorporated within the Work constitutes direct 123 | or contributory patent infringement, then any patent licenses 124 | granted to You under this License for that Work shall terminate 125 | as of the date such litigation is filed. 126 |

127 | 128 |

4. Redistribution. 129 | You may reproduce and distribute copies of the 130 | Work or Derivative Works thereof in any medium, with or without 131 | modifications, and in Source or Object form, provided that You 132 | meet the following conditions: 133 |

134 |
    135 |
  1. You must give any other recipients of the Work or 136 | Derivative Works a copy of this License; and 137 |

  2. 138 | 139 |
  3. You must cause any modified files to carry prominent notices 140 | stating that You changed the files; and 141 |

  4. 142 | 143 |
  5. You must retain, in the Source form of any Derivative Works 144 | that You distribute, all copyright, patent, trademark, and 145 | attribution notices from the Source form of the Work, 146 | excluding those notices that do not pertain to any part of 147 | the Derivative Works; and 148 |

  6. 149 | 150 |
  7. If the Work includes a "NOTICE" text file as part of its 151 | distribution, then any Derivative Works that You distribute must 152 | include a readable copy of the attribution notices contained 153 | within such NOTICE file, excluding those notices that do not 154 | pertain to any part of the Derivative Works, in at least one 155 | of the following places: within a NOTICE text file distributed 156 | as part of the Derivative Works; within the Source form or 157 | documentation, if provided along with the Derivative Works; or, 158 | within a display generated by the Derivative Works, if and 159 | wherever such third-party notices normally appear. The contents 160 | of the NOTICE file are for informational purposes only and 161 | do not modify the License. You may add Your own attribution 162 | notices within Derivative Works that You distribute, alongside 163 | or as an addendum to the NOTICE text from the Work, provided 164 | that such additional attribution notices cannot be construed 165 | as modifying the License. 166 |
  8. 167 |
168 | You may add Your own copyright statement to Your modifications and 169 | may provide additional or different license terms and conditions 170 | for use, reproduction, or distribution of Your modifications, or 171 | for any such Derivative Works as a whole, provided Your use, 172 | reproduction, and distribution of the Work otherwise complies with 173 | the conditions stated in this License. 174 | 175 |

5. Submission of Contributions. 176 | Unless You explicitly state otherwise, 177 | any Contribution intentionally submitted for inclusion in the Work 178 | by You to the Licensor shall be under the terms and conditions of 179 | this License, without any additional terms or conditions. 180 | Notwithstanding the above, nothing herein shall supersede or modify 181 | the terms of any separate license agreement you may have executed 182 | with Licensor regarding such Contributions. 183 |

184 | 185 |

6. Trademarks. 186 | This License does not grant permission to use the trade 187 | names, trademarks, service marks, or product names of the Licensor, 188 | except as required for reasonable and customary use in describing the 189 | origin of the Work and reproducing the content of the NOTICE file. 190 |

191 | 192 |

7. Disclaimer of Warranty. 193 | Unless required by applicable law or 194 | agreed to in writing, Licensor provides the Work (and each 195 | Contributor provides its Contributions) on an "AS IS" BASIS, 196 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 197 | implied, including, without limitation, any warranties or conditions 198 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 199 | PARTICULAR PURPOSE. You are solely responsible for determining the 200 | appropriateness of using or redistributing the Work and assume any 201 | risks associated with Your exercise of permissions under this License. 202 |

203 | 204 |

8. Limitation of Liability. 205 | In no event and under no legal theory, 206 | whether in tort (including negligence), contract, or otherwise, 207 | unless required by applicable law (such as deliberate and grossly 208 | negligent acts) or agreed to in writing, shall any Contributor be 209 | liable to You for damages, including any direct, indirect, special, 210 | incidental, or consequential damages of any character arising as a 211 | result of this License or out of the use or inability to use the 212 | Work (including but not limited to damages for loss of goodwill, 213 | work stoppage, computer failure or malfunction, or any and all 214 | other commercial damages or losses), even if such Contributor 215 | has been advised of the possibility of such damages. 216 |

217 | 218 |

9. Accepting Warranty or Additional Liability. 219 | While redistributing 220 | the Work or Derivative Works thereof, You may choose to offer, 221 | and charge a fee for, acceptance of support, warranty, indemnity, 222 | or other liability obligations and/or rights consistent with this 223 | License. However, in accepting such obligations, You may act only 224 | on Your own behalf and on Your sole responsibility, not on behalf 225 | of any other Contributor, and only if You agree to indemnify, 226 | defend, and hold each Contributor harmless for any liability 227 | incurred by, or claims asserted against, such Contributor by reason 228 | of your accepting any such warranty or additional liability. 229 |

230 | 231 |

232 | END OF TERMS AND CONDITIONS 233 |

234 | 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ============================= 3 | 4 | This is a Java port of a concurrent trie hash map implementation from the Scala collections library. It is almost a line-by-line 5 | conversion from Scala to Java. 6 | 7 | Idea + implementation techniques can be found in these reports written by Aleksandar Prokopec: 8 | * http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf - this is a nice introduction to Ctries, along with a correctness proof 9 | * http://lamp.epfl.ch/~prokopec/ctries-snapshot.pdf - a more up-to-date writeup which describes the snapshot operation 10 | 11 | The original Scala implementation can be found here and is a part of scala.collection.concurrent: 12 | * [Scala implementation](https://github.com/scala/scala/blob/930c85d6c96507d798d1847ea078eebf93dc0acb/src/library/scala/collection/concurrent/TrieMap.scala) 13 | 14 | Some of the tests and implementation details were borrowed from this project: 15 | * https://github.com/flegall/concurrent-hash-trie 16 | 17 | Implementation status : 18 | * The given implementation is complete and implements all features of the original Scala implementation including support for 19 | snapshots. 20 | * Wherever necessary, code was adapted to be more easily usable in Java, e.g. it returns Objects instead of Option as 21 | many methods of Scala's collections do. 22 | * This class implements all the ConcurrentMap & Iterator methods and passes all the tests. Can be used as a drop-in replacement 23 | for usual Java maps, including ConcurrentHashMap. 24 | 25 | 26 | What is a concurrent trie hash map also known as ctrie? 27 | ======================================================== 28 | ctrie is a lock-Free Concurrent Hash Array Mapped Trie. 29 | 30 | A concurrent hash-trie or Ctrie is a concurrent thread-safe lock-free implementation of a hash array mapped trie. 31 | 32 | It is used to implement the concurrent map abstraction. It has particularly scalable concurrent insert and remove operations 33 | and is memory-efficient. 34 | 35 | It supports O(1), atomic, lock-free snapshots which are used to implement linearizable lock-free size, iterator and clear operations. 36 | The cost of evaluating the (lazy) snapshot is distributed across subsequent updates, thus making snapshot evaluation horizontally scalable. 37 | 38 | The original Scala-based implementation of the Ctrie is a part of the Scala standard library since the version 2.10. 39 | 40 | More info about Ctries: 41 | 42 | - http://infoscience.epfl.ch/record/166908/files/ctries-techreport.pdf - this is a nice introduction to Ctries, along with a correctness proof 43 | - http://lamp.epfl.ch/~prokopec/ctries-snapshot.pdf - a more up-to-date writeup (more coherent with the current version of the code) which describes the snapshot operation 44 | 45 | 46 | License 47 | =============================== 48 | 49 | This library is licensed under the Apache 2.0 license. 50 | 51 | 52 | Usage 53 | =============================== 54 | 55 | Usage of this library is very simple. Simply import the class com.romix.scala.collection.concurrent.TrieMap and use it as a usual Map. 56 | 57 | import com.romix.scala.collection.concurrent.TrieMap; 58 | 59 | Map myMap = new TrieMap (); 60 | myMap.put("key", "value"); 61 | 62 | 63 | Building the library 64 | =============================== 65 | 66 | Use a usual `mvn clean install` 67 | 68 | Using the library with Maven projects 69 | ===================================== 70 | The prebuilt binaries of the library are available from Maven central. Please use the following dependency in your POM files: 71 | 72 | 73 | com.github.romix 74 | java-concurrent-hash-trie-map 75 | 0.2.23 76 | 77 | 78 | 79 | External dependencies 80 | ===================================== 81 | This library is self-contained. It does not depend on any additional libraries. In particular, it does not require the rather big Scala's 82 | standard library to be used. 83 | 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | org.sonatype.oss 5 | oss-parent 6 | 7 7 | 8 | com.github.romix 9 | java-concurrent-hash-trie-map 10 | 0.2.24-SNAPSHOT 11 | TrieMap 12 | Java implementation of a concurrent trie hash map from Scala collections library 13 | bundle 14 | https://github.com/romix/java-concurrent-hash-trie-map 15 | 16 | 17 | The Apache Software License, Version 2.0 18 | http://www.apache.org/licenses/LICENSE-2.0.txt 19 | repo 20 | 21 | 22 | 23 | https://github.com/romix/java-concurrent-hash-trie-map 24 | scm:git:https://github.com/romix/java-concurrent-hash-trie-map.git 25 | scm:git:https://github.com/romix/java-concurrent-hash-trie-map.git 26 | HEAD 27 | 28 | 29 | 30 | romix 31 | Roman Levenstein 32 | romixlev@gmail.com 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-compiler-plugin 40 | 2.3.2 41 | 42 | 1.6 43 | 1.6 44 | iso8859-1 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-release-plugin 50 | 2.5 51 | 52 | 53 | pom.xml 54 | 55 | 56 | 57 | 58 | org.apache.felix 59 | maven-bundle-plugin 60 | 2.4.0 61 | true 62 | 63 | 64 | ${project.groupId}.${project.artifactId} 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | junit 73 | junit 74 | 4.9 75 | test 76 | 77 | 78 | 79 | 80 | sonatype-nexus-staging 81 | Nexus Staging Repository 82 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 83 | 84 | 85 | sonatype-nexus-snapshots 86 | Nexus Snapshots Repository 87 | https://oss.sonatype.org/content/repositories/snapshots/ 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/None.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala; 2 | 3 | /** 4 | * Mimic None in Scala 5 | * 6 | * @author Roman Levenstein 7 | * 8 | * @param 9 | */ 10 | public class None extends Option{ 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/Option.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala; 2 | 3 | /** 4 | * Mimic Option in Scala 5 | * 6 | * @author Roman Levenstein 7 | * 8 | * @param 9 | */ 10 | @SuppressWarnings({"rawtypes", "unchecked"}) 11 | public class Option { 12 | private static final None NONE = new None(); 13 | public static Option makeOption(V o){ 14 | return o != null ? new Some(o) : NONE; 15 | } 16 | 17 | public static Option makeOption(){ 18 | return NONE; 19 | } 20 | public boolean nonEmpty () { 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/Some.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala; 2 | 3 | /** 4 | * Mimic Some in Scala 5 | * 6 | * @author Roman Levenstein 7 | * 8 | * @param 9 | */ 10 | public class Some extends Option{ 11 | final V value; 12 | public Some(V v) { 13 | value = v; 14 | } 15 | 16 | public V get() { 17 | return value; 18 | } 19 | 20 | public boolean nonEmpty () { 21 | return value != null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/BasicNode.java: -------------------------------------------------------------------------------- 1 | /* __ *\ 2 | ** ________ ___ / / ___ Scala API ** 3 | ** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** 4 | ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** 5 | ** /____/\___/_/ |_/____/_/ | | ** 6 | ** |/ ** 7 | \* */ 8 | 9 | package com.romix.scala.collection.concurrent; 10 | 11 | 12 | 13 | 14 | 15 | 16 | abstract class BasicNode { 17 | 18 | public abstract String string(int lev); 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/CNodeBase.java: -------------------------------------------------------------------------------- 1 | /* __ *\ 2 | ** ________ ___ / / ___ Scala API ** 3 | ** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** 4 | ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** 5 | ** /____/\___/_/ |_/____/_/ | | ** 6 | ** |/ ** 7 | \* */ 8 | 9 | package com.romix.scala.collection.concurrent; 10 | 11 | 12 | 13 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 14 | 15 | 16 | 17 | abstract class CNodeBase extends MainNode { 18 | 19 | @SuppressWarnings("rawtypes") 20 | private static final AtomicIntegerFieldUpdater UPDATER = AtomicIntegerFieldUpdater.newUpdater(CNodeBase.class, "csize"); 21 | 22 | private volatile int csize = -1; 23 | 24 | public boolean CAS_SIZE(int oldval, int nval) { 25 | return UPDATER.compareAndSet(this, oldval, nval); 26 | } 27 | 28 | public void WRITE_SIZE(int nval) { 29 | UPDATER.set(this, nval); 30 | } 31 | 32 | public int READ_SIZE() { 33 | return csize; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/Gen.java: -------------------------------------------------------------------------------- 1 | /* __ *\ 2 | ** ________ ___ / / ___ Scala API ** 3 | ** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** 4 | ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** 5 | ** /____/\___/_/ |_/____/_/ | | ** 6 | ** |/ ** 7 | \* */ 8 | 9 | package com.romix.scala.collection.concurrent; 10 | 11 | 12 | 13 | 14 | 15 | 16 | final class Gen { 17 | } -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/INodeBase.java: -------------------------------------------------------------------------------- 1 | /* __ *\ 2 | ** ________ ___ / / ___ Scala API ** 3 | ** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** 4 | ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** 5 | ** /____/\___/_/ |_/____/_/ | | ** 6 | ** |/ ** 7 | \* */ 8 | 9 | package com.romix.scala.collection.concurrent; 10 | 11 | 12 | 13 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 14 | 15 | 16 | 17 | abstract class INodeBase extends BasicNode { 18 | 19 | public static final AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(INodeBase.class, MainNode.class, "mainnode"); 20 | 21 | public static final Object RESTART = new Object(); 22 | 23 | public volatile MainNode mainnode = null; 24 | 25 | public final Gen gen; 26 | 27 | public INodeBase(Gen generation) { 28 | gen = generation; 29 | } 30 | 31 | public BasicNode prev() { 32 | return null; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/ListMap.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.AbstractMap.SimpleImmutableEntry; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | 8 | import com.romix.scala.Option; 9 | 10 | /** 11 | * Mimic immutable ListMap in Scala 12 | * 13 | * @author Roman Levenstein 14 | * 15 | * @param 16 | */ 17 | abstract class ListMap { 18 | 19 | ListMap next; 20 | 21 | static ListMap map(K k, V v, ListMap tail){ 22 | return new Node (k, v, tail); 23 | } 24 | 25 | static ListMap map(K k, V v){ 26 | return new Node (k, v, null); 27 | } 28 | 29 | static ListMap map(K k1, V v1, K k2, V v2){ 30 | return new Node (k1, v1, new Node(k2,v2, null)); 31 | } 32 | 33 | public abstract int size (); 34 | 35 | public abstract boolean isEmpty() ; 36 | 37 | abstract public boolean contains(K k, V v); 38 | 39 | abstract public boolean contains(K key); 40 | 41 | abstract public Option get (K key); 42 | 43 | abstract public ListMap add (K key, V value); 44 | 45 | abstract public ListMap remove (K key); 46 | 47 | abstract public Iterator> iterator(); 48 | 49 | 50 | static class EmptyListMap extends ListMap { 51 | public ListMap add (K key, V value) { 52 | return ListMap.map(key, value, null); 53 | } 54 | 55 | public boolean contains(K k, V v) { 56 | return false; 57 | } 58 | 59 | public boolean contains(K k) { 60 | return false; 61 | } 62 | 63 | public ListMap remove(K key) { 64 | return this; 65 | } 66 | 67 | @Override 68 | public int size () { 69 | return 0; 70 | } 71 | 72 | @Override 73 | public boolean isEmpty () { 74 | return true; 75 | } 76 | 77 | @Override 78 | public Option get (K key) { 79 | return Option.makeOption (null); 80 | } 81 | 82 | @Override 83 | public Iterator> iterator () { 84 | return new EmptyListMapIterator (); 85 | } 86 | 87 | static class EmptyListMapIterator implements Iterator> { 88 | 89 | @Override 90 | public boolean hasNext () { 91 | return false; 92 | } 93 | 94 | @Override 95 | public Entry next () { 96 | return null; 97 | } 98 | 99 | @Override 100 | public void remove () { 101 | throw new RuntimeException("Operation not supported"); 102 | } 103 | 104 | } 105 | } 106 | 107 | static class Node extends ListMap { 108 | final K k; 109 | final V v; 110 | 111 | Node(K k, V v, ListMap next) { 112 | this.k = k; 113 | this.v = v; 114 | this.next = next; 115 | } 116 | 117 | public ListMap add (K key, V value) { 118 | return ListMap.map(key, value, remove (key)); 119 | } 120 | 121 | public boolean contains(K k, V v) { 122 | if(k.equals (this.k) && v.equals (this.v)) 123 | return true; 124 | else if(next != null) 125 | return next.contains (k, v); 126 | return false; 127 | } 128 | 129 | public boolean contains(K k) { 130 | if(k.equals (this.k)) 131 | return true; 132 | else if(next != null) 133 | return next.contains (k); 134 | return false; 135 | } 136 | 137 | public ListMap remove(K key) { 138 | if(!contains(key)) 139 | return this; 140 | else 141 | return remove0(key); 142 | } 143 | 144 | private ListMap remove0 (K key) { 145 | ListMap n = this; 146 | ListMap newN = null; 147 | ListMap lastN = null; 148 | while (n != null) { 149 | if(n instanceof EmptyListMap) { 150 | newN.next = n; 151 | break; 152 | } 153 | Node nn = (Node)n; 154 | if (key.equals (nn.k)) { 155 | n = n.next; 156 | continue; 157 | } else { 158 | if (newN != null) { 159 | lastN.next = ListMap.map (nn.k, nn.v, null); 160 | lastN = lastN.next; 161 | } else { 162 | newN = ListMap.map (nn.k, nn.v, null); 163 | lastN = newN; 164 | } 165 | } 166 | n = n.next; 167 | } 168 | return newN; 169 | } 170 | 171 | @Override 172 | public int size () { 173 | if(next == null) 174 | return 1; 175 | else 176 | return 1+next.size (); 177 | } 178 | 179 | @Override 180 | public boolean isEmpty () { 181 | return false; 182 | } 183 | 184 | @Override 185 | public Option get (K key) { 186 | if(key.equals (k)) 187 | return Option.makeOption (v); 188 | if(next != null) 189 | return next.get (key); 190 | return Option.makeOption (null); 191 | } 192 | 193 | 194 | @Override 195 | public Iterator> iterator () { 196 | return new NodeIterator (this); 197 | } 198 | 199 | static class NodeIterator implements Iterator> { 200 | ListMap n; 201 | 202 | public NodeIterator (Node n) { 203 | this.n = n; 204 | } 205 | 206 | @Override 207 | public boolean hasNext () { 208 | // return n!= null && n.next != null && !(n.next instanceof EmptyListMap); 209 | return n!= null && !(n instanceof EmptyListMap); 210 | } 211 | 212 | @Override 213 | public Entry next () { 214 | if (n instanceof Node) { 215 | Node nn = (Node) n; 216 | Entry res = new SimpleImmutableEntry (nn.k, nn.v); 217 | n = n.next; 218 | return res; 219 | } else { 220 | return null; 221 | } 222 | } 223 | 224 | @Override 225 | public void remove () { 226 | throw new RuntimeException("Operation not supported"); 227 | } 228 | 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/MainNode.java: -------------------------------------------------------------------------------- 1 | /* __ *\ 2 | ** ________ ___ / / ___ Scala API ** 3 | ** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** 4 | ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** 5 | ** /____/\___/_/ |_/____/_/ | | ** 6 | ** |/ ** 7 | \* */ 8 | 9 | package com.romix.scala.collection.concurrent; 10 | 11 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 12 | 13 | abstract class MainNode extends BasicNode { 14 | 15 | public static final AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater (MainNode.class, MainNode.class, "prev"); 16 | 17 | public volatile MainNode prev = null; 18 | 19 | public abstract int cachedSize (Object ct); 20 | 21 | public boolean CAS_PREV (MainNode oldval, MainNode nval) { 22 | return updater.compareAndSet (this, oldval, nval); 23 | } 24 | 25 | public void WRITE_PREV (MainNode nval) { 26 | updater.set (this, nval); 27 | } 28 | 29 | // do we need this? unclear in the javadocs... 30 | // apparently not - volatile reads are supposed to be safe 31 | // regardless of whether there are concurrent ARFU updates 32 | public MainNode READ_PREV () { 33 | return updater.get (this); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/romix/scala/collection/concurrent/TrieMap.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.io.IOException; 4 | import java.io.ObjectInputStream; 5 | import java.io.ObjectOutputStream; 6 | import java.io.Serializable; 7 | import java.lang.reflect.Field; 8 | import java.util.AbstractMap; 9 | import java.util.AbstractSet; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.HashMap; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.NoSuchElementException; 17 | import java.util.Set; 18 | import java.util.concurrent.ConcurrentMap; 19 | import java.util.concurrent.ThreadLocalRandom; 20 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 21 | import com.romix.scala.None; 22 | import com.romix.scala.Option; 23 | import com.romix.scala.Some; 24 | 25 | /*** 26 | * This is a port of Scala's TrieMap class from the Scala Collections library. 27 | * 28 | * @author Roman Levenstein 29 | * 30 | * @param 31 | * @param 32 | */ 33 | @SuppressWarnings({"unchecked", "rawtypes", "unused"}) 34 | public class TrieMap extends AbstractMap implements ConcurrentMap, Serializable { 35 | private static final AtomicReferenceFieldUpdater ROOT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(TrieMap.class, Object.class, "root"); 36 | private static final long serialVersionUID = 1L; 37 | private static final Field READONLY_FIELD; 38 | 39 | static { 40 | final Field f; 41 | try { 42 | f = TrieMap.class.getDeclaredField("readOnly"); 43 | } catch (NoSuchFieldException e) { 44 | throw new ExceptionInInitializerError(e); 45 | } catch (SecurityException e) { 46 | throw new ExceptionInInitializerError(e); 47 | } 48 | f.setAccessible(true); 49 | READONLY_FIELD = f; 50 | } 51 | 52 | /** 53 | * EntrySet 54 | */ 55 | private transient final EntrySet entrySet = new EntrySet (); 56 | 57 | public static TrieMap empty () { 58 | return new TrieMap(); 59 | } 60 | 61 | // static class MangledHashing extends Hashing { 62 | // int hash(K k) { 63 | // return util.hashing.byteswap32(k); 64 | // } 65 | // } 66 | 67 | static class INode extends INodeBase { 68 | static final Object KEY_PRESENT = new Object (); 69 | static final Object KEY_ABSENT = new Object (); 70 | 71 | static INode newRootNode () { 72 | Gen gen = new Gen (); 73 | CNode cn = new CNode (0, new BasicNode[] {}, gen); 74 | return new INode(cn, gen); 75 | } 76 | 77 | public INode (MainNode bn, Gen g) { 78 | super (g); 79 | WRITE (bn); 80 | } 81 | 82 | public INode (Gen g) { 83 | this (null, g); 84 | } 85 | 86 | final void WRITE (final MainNode nval) { 87 | INodeBase.updater.set (this, nval); 88 | } 89 | 90 | final boolean CAS (final MainNode old, final MainNode n) { 91 | return INodeBase.updater.compareAndSet (this, old, n); 92 | } 93 | 94 | final MainNode gcasRead (final TrieMap ct) { 95 | return GCAS_READ (ct); 96 | } 97 | 98 | final MainNode GCAS_READ (TrieMap ct) { 99 | MainNode m = /* READ */mainnode; 100 | MainNode prevval = /* READ */m.prev; 101 | if (prevval == null) 102 | return m; 103 | else 104 | return GCAS_Complete (m, ct); 105 | } 106 | 107 | private MainNode GCAS_Complete (MainNode m, final TrieMap ct) { 108 | while (true) { 109 | if (m == null) 110 | return null; 111 | else { 112 | // complete the GCAS 113 | MainNode prev = /* READ */m.prev; 114 | INode ctr = ct.readRoot (true); 115 | 116 | if (prev == null) 117 | return m; 118 | 119 | if (prev instanceof FailedNode) { 120 | // try to commit to previous value 121 | FailedNode fn = (FailedNode) prev; 122 | if (CAS (m, fn.prev)) 123 | return fn.prev; 124 | else { 125 | // Tailrec 126 | // return GCAS_Complete (/* READ */mainnode, ct); 127 | m = /* READ */mainnode; 128 | continue; 129 | } 130 | } else { 131 | // Assume that you've read the root from the generation 132 | // G. 133 | // Assume that the snapshot algorithm is correct. 134 | // ==> you can only reach nodes in generations <= G. 135 | // ==> `gen` is <= G. 136 | // We know that `ctr.gen` is >= G. 137 | // ==> if `ctr.gen` = `gen` then they are both equal to 138 | // G. 139 | // ==> otherwise, we know that either `ctr.gen` > G, 140 | // `gen` < 141 | // G, 142 | // or both 143 | if ((ctr.gen == gen) && ct.nonReadOnly ()) { 144 | // try to commit 145 | if (m.CAS_PREV (prev, null)) 146 | return m; 147 | else { 148 | // return GCAS_Complete (m, ct); 149 | // tailrec 150 | continue; 151 | } 152 | } else { 153 | // try to abort 154 | m.CAS_PREV (prev, new FailedNode (prev)); 155 | return GCAS_Complete (/* READ */mainnode, ct); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | final boolean GCAS (final MainNode old, final MainNode n, final TrieMap ct) { 163 | n.WRITE_PREV (old); 164 | if (CAS (old, n)) { 165 | GCAS_Complete (n, ct); 166 | return /* READ */n.prev == null; 167 | } else 168 | return false; 169 | } 170 | 171 | private boolean equal (final K k1, final K k2, final TrieMap ct) { 172 | return ct.equality ().equiv (k1, k2); 173 | } 174 | 175 | private INode inode (final MainNode cn) { 176 | INode nin = new INode (gen); 177 | nin.WRITE (cn); 178 | return nin; 179 | } 180 | 181 | final INode copyToGen (final Gen ngen, final TrieMap ct) { 182 | INode nin = new INode (ngen); 183 | MainNode main = GCAS_READ (ct); 184 | nin.WRITE (main); 185 | return nin; 186 | } 187 | 188 | /** 189 | * Inserts a key value pair, overwriting the old pair if the keys match. 190 | * 191 | * @return true if successful, false otherwise 192 | */ 193 | final boolean rec_insert (final K k, final V v, final int hc, final int lev, final INode parent, final Gen startgen, final TrieMap ct) { 194 | while (true) { 195 | MainNode m = GCAS_READ (ct); // use -Yinline! 196 | 197 | if (m instanceof CNode) { 198 | // 1) a multiway node 199 | CNode cn = (CNode) m; 200 | int idx = (hc >>> lev) & 0x1f; 201 | int flag = 1 << idx; 202 | int bmp = cn.bitmap; 203 | int mask = flag - 1; 204 | int pos = Integer.bitCount (bmp & mask); 205 | if ((bmp & flag) != 0) { 206 | // 1a) insert below 207 | BasicNode cnAtPos = cn.array [pos]; 208 | if (cnAtPos instanceof INode) { 209 | INode in = (INode) cnAtPos; 210 | if (startgen == in.gen) 211 | return in.rec_insert (k, v, hc, lev + 5, this, startgen, ct); 212 | else { 213 | if (GCAS (cn, cn.renewed (startgen, ct), ct)) { 214 | // return rec_insert (k, v, hc, lev, parent, 215 | // startgen, ct); 216 | // tailrec 217 | continue; 218 | } else 219 | return false; 220 | } 221 | } else if (cnAtPos instanceof SNode) { 222 | SNode sn = (SNode) cnAtPos; 223 | if (sn.hc == hc && equal (sn.k, k, ct)) 224 | return GCAS (cn, cn.updatedAt (pos, new SNode (k, v, hc), gen), ct); 225 | else { 226 | CNode rn = (cn.gen == gen) ? cn : cn.renewed (gen, ct); 227 | MainNode nn = rn.updatedAt (pos, inode (CNode.dual (sn, sn.hc, new SNode (k, v, hc), hc, lev + 5, gen)), gen); 228 | return GCAS (cn, nn, ct); 229 | } 230 | } 231 | } else { 232 | CNode rn = (cn.gen == gen) ? cn : cn.renewed (gen, ct); 233 | MainNode ncnode = rn.insertedAt (pos, flag, new SNode (k, v, hc), gen); 234 | return GCAS (cn, ncnode, ct); 235 | } 236 | } else if (m instanceof TNode) { 237 | clean (parent, ct, lev - 5); 238 | return false; 239 | } else if (m instanceof LNode) { 240 | LNode ln = (LNode) m; 241 | MainNode nn = ln.inserted (k, v); 242 | return GCAS (ln, nn, ct); 243 | } 244 | 245 | throw new RuntimeException ("Should not happen"); 246 | } 247 | } 248 | 249 | /** 250 | * Inserts a new key value pair, given that a specific condition is met. 251 | * 252 | * @param cond 253 | * null - don't care if the key was there 254 | * KEY_ABSENT - key wasn't there 255 | * KEY_PRESENT - key was there 256 | * other value `v` - key must be bound to `v` 257 | * @return null if unsuccessful, Option[V] otherwise (indicating 258 | * previous value bound to the key) 259 | */ 260 | final Option rec_insertif (final K k, final V v, final int hc, final Object cond, final int lev, final INode parent, final Gen startgen, final TrieMap ct) { 261 | while (true) { 262 | MainNode m = GCAS_READ (ct); // use -Yinline! 263 | 264 | if (m instanceof CNode) { 265 | // 1) a multiway node 266 | CNode cn = (CNode) m; 267 | int idx = (hc >>> lev) & 0x1f; 268 | int flag = 1 << idx; 269 | int bmp = cn.bitmap; 270 | int mask = flag - 1; 271 | int pos = Integer.bitCount (bmp & mask); 272 | 273 | if ((bmp & flag) != 0) { 274 | // 1a) insert below 275 | BasicNode cnAtPos = cn.array [pos]; 276 | if (cnAtPos instanceof INode) { 277 | INode in = (INode) cnAtPos; 278 | if (startgen == in.gen) 279 | return in.rec_insertif (k, v, hc, cond, lev + 5, this, startgen, ct); 280 | else { 281 | if (GCAS (cn, cn.renewed (startgen, ct), ct)) { 282 | // return rec_insertif (k, v, hc, cond, lev, 283 | // parent, startgen, ct); 284 | // tailrec 285 | continue; 286 | } else 287 | return null; 288 | } 289 | } else if (cnAtPos instanceof SNode) { 290 | SNode sn = (SNode) cnAtPos; 291 | if (cond == null) { 292 | if (sn.hc == hc && equal (sn.k, k, ct)) { 293 | if (GCAS (cn, cn.updatedAt (pos, new SNode (k, v, hc), gen), ct)) 294 | return Option.makeOption(sn.v); 295 | else 296 | return null; 297 | } else { 298 | CNode rn = (cn.gen == gen) ? cn : cn.renewed (gen, ct); 299 | MainNode nn = rn.updatedAt (pos, inode (CNode.dual (sn, sn.hc, new SNode (k, v, hc), hc, lev + 5, gen)), gen); 300 | if (GCAS (cn, nn, ct)) 301 | return Option.makeOption(); // None; 302 | else 303 | return null; 304 | } 305 | 306 | } else if (cond == INode.KEY_ABSENT) { 307 | if (sn.hc == hc && equal (sn.k, k, ct)) 308 | return Option.makeOption(sn.v); 309 | else { 310 | CNode rn = (cn.gen == gen) ? cn : cn.renewed (gen, ct); 311 | MainNode nn = rn.updatedAt (pos, inode (CNode.dual (sn, sn.hc, new SNode (k, v, hc), hc, lev + 5, gen)), gen); 312 | if (GCAS (cn, nn, ct)) 313 | return Option.makeOption (); // None 314 | else 315 | return null; 316 | } 317 | } else if (cond == INode.KEY_PRESENT) { 318 | if (sn.hc == hc && equal (sn.k, k, ct)) { 319 | if (GCAS (cn, cn.updatedAt (pos, new SNode (k, v, hc), gen), ct)) 320 | return Option.makeOption (sn.v); 321 | else 322 | return null; 323 | 324 | } else 325 | return Option.makeOption ();// None; 326 | } else { 327 | if (sn.hc == hc && equal (sn.k, k, ct) && sn.v == cond) { 328 | if (GCAS (cn, cn.updatedAt (pos, new SNode (k, v, hc), gen), ct)) 329 | return Option.makeOption (sn.v); 330 | else 331 | return null; 332 | } else 333 | return Option.makeOption (); // None 334 | } 335 | 336 | } 337 | } else if (cond == null || cond == INode.KEY_ABSENT) { 338 | CNode rn = (cn.gen == gen) ? cn : cn.renewed (gen, ct); 339 | CNode ncnode = rn.insertedAt (pos, flag, new SNode (k, v, hc), gen); 340 | if (GCAS (cn, ncnode, ct)) 341 | return Option.makeOption ();// None 342 | else 343 | return null; 344 | } else if (cond == INode.KEY_PRESENT) { 345 | return Option.makeOption ();// None; 346 | } else 347 | return Option.makeOption (); // None 348 | } else if (m instanceof TNode) { 349 | clean (parent, ct, lev - 5); 350 | return null; 351 | } else if (m instanceof LNode) { 352 | // 3) an l-node 353 | LNode ln = (LNode) m; 354 | if (cond == null) { 355 | Option optv = ln.get (k); 356 | if (insertln (ln, k, v, ct)) 357 | return optv; 358 | else 359 | return null; 360 | } else if (cond == INode.KEY_ABSENT) { 361 | Option t = ln.get (k); 362 | if (t == null || t instanceof None) { 363 | if (insertln (ln, k, v, ct)) 364 | return Option.makeOption ();// None 365 | else 366 | return null; 367 | } else 368 | return t; 369 | } else if (cond == INode.KEY_PRESENT) { 370 | Option t = ln.get (k); 371 | if (t != null) { 372 | if (insertln (ln, k, v, ct)) 373 | return t; 374 | else 375 | return null; 376 | } else 377 | return null; // None 378 | } else { 379 | Option t = ln.get (k); 380 | if (t != null) { 381 | if (((Some) t).get () == cond) { 382 | if (insertln (ln, k, v, ct)) 383 | return new Some ((V) cond); 384 | else 385 | return null; 386 | 387 | } else 388 | return Option.makeOption (); 389 | } 390 | } 391 | } 392 | 393 | // throw new RuntimeException ("Should not happen"); 394 | } 395 | } 396 | 397 | final boolean insertln (final LNode ln, final K k, final V v, final TrieMap ct) { 398 | LNode nn = ln.inserted (k, v); 399 | return GCAS (ln, nn, ct); 400 | } 401 | 402 | /** 403 | * Looks up the value associated with the key. 404 | * 405 | * @return null if no value has been found, RESTART if the operation 406 | * wasn't successful, or any other value otherwise 407 | */ 408 | final Object rec_lookup (final K k, final int hc, int lev, INode parent, final Gen startgen, final TrieMap ct) { 409 | while (true) { 410 | MainNode m = GCAS_READ (ct); // use -Yinline! 411 | 412 | if (m instanceof CNode) { 413 | // 1) a multinode 414 | final CNode cn = (CNode) m; 415 | int idx = (hc >>> lev) & 0x1f; 416 | int flag = 1 << idx; 417 | int bmp = cn.bitmap; 418 | if ((bmp & flag) == 0) 419 | return null; // 1a) bitmap shows no binding 420 | else { // 1b) bitmap contains a value - descend 421 | int pos = (bmp == 0xffffffff) ? idx : Integer.bitCount (bmp & (flag - 1)); 422 | final BasicNode sub = cn.array [pos]; 423 | if (sub instanceof INode) { 424 | INode in = (INode) sub; 425 | if (ct.isReadOnly () || (startgen == ((INodeBase) sub).gen)) 426 | return in.rec_lookup (k, hc, lev + 5, this, startgen, ct); 427 | else { 428 | if (GCAS (cn, cn.renewed (startgen, ct), ct)) { 429 | // return rec_lookup (k, hc, lev, parent, 430 | // startgen, ct); 431 | // Tailrec 432 | continue; 433 | } else 434 | return RESTART; // used to be throw 435 | // RestartException 436 | } 437 | } else if (sub instanceof SNode) { 438 | // 2) singleton node 439 | SNode sn = (SNode) sub; 440 | if (((SNode) sub).hc == hc && equal (sn.k, k, ct)) 441 | return sn.v; 442 | else 443 | return null; 444 | } 445 | } 446 | } else if (m instanceof TNode) { 447 | // 3) non-live node 448 | return cleanReadOnly ((TNode) m, lev, parent, ct, k, hc); 449 | } else if (m instanceof LNode) { 450 | // 5) an l-node 451 | return ((LNode) m).get (k); 452 | } 453 | 454 | throw new RuntimeException ("Should not happen"); 455 | } 456 | } 457 | 458 | private Object cleanReadOnly (final TNode tn, final int lev, final INode parent, final TrieMap ct, K k, int hc) { 459 | if (ct.nonReadOnly ()) { 460 | clean (parent, ct, lev - 5); 461 | return RESTART; // used to be throw RestartException 462 | } else { 463 | if (tn.hc == hc && equal(tn.k, k, ct)) 464 | return tn.v; 465 | else 466 | return null; 467 | } 468 | } 469 | 470 | /** 471 | * Removes the key associated with the given value. 472 | * 473 | * @param v 474 | * if null, will remove the key irregardless of the value; 475 | * otherwise removes only if binding contains that exact key 476 | * and value 477 | * @return null if not successful, an Option[V] indicating the previous 478 | * value otherwise 479 | */ 480 | final Option rec_remove (K k, V v, int hc, int lev, final INode parent, final Gen startgen, final TrieMap ct) { 481 | MainNode m = GCAS_READ (ct); // use -Yinline! 482 | 483 | if (m instanceof CNode) { 484 | CNode cn = (CNode) m; 485 | int idx = (hc >>> lev) & 0x1f; 486 | int bmp = cn.bitmap; 487 | int flag = 1 << idx; 488 | if ((bmp & flag) == 0) 489 | return Option.makeOption (); 490 | else { 491 | int pos = Integer.bitCount (bmp & (flag - 1)); 492 | BasicNode sub = cn.array [pos]; 493 | Option res = null; 494 | if (sub instanceof INode) { 495 | INode in = (INode) sub; 496 | if (startgen == in.gen) 497 | res = in.rec_remove (k, v, hc, lev + 5, this, startgen, ct); 498 | else { 499 | if (GCAS (cn, cn.renewed (startgen, ct), ct)) 500 | res = rec_remove (k, v, hc, lev, parent, startgen, ct); 501 | } 502 | 503 | } else if (sub instanceof SNode) { 504 | SNode sn = (SNode) sub; 505 | if (sn.hc == hc && equal (sn.k, k, ct) && (v == null || v.equals(sn.v))) { 506 | MainNode ncn = cn.removedAt (pos, flag, gen).toContracted (lev); 507 | if (GCAS (cn, ncn, ct)) 508 | res = Option.makeOption (sn.v); 509 | } else 510 | res = Option.makeOption (); 511 | } 512 | 513 | if (res instanceof None || (res == null)) 514 | return res; 515 | else { 516 | if (parent != null) { // never tomb at root 517 | MainNode n = GCAS_READ (ct); 518 | if (n instanceof TNode) 519 | cleanParent (n, parent, ct, hc, lev, startgen); 520 | } 521 | 522 | return res; 523 | } 524 | } 525 | } else if (m instanceof TNode) { 526 | clean (parent, ct, lev - 5); 527 | return null; 528 | } else if (m instanceof LNode) { 529 | LNode ln = (LNode) m; 530 | if (v == null) { 531 | Option optv = ln.get (k); 532 | MainNode nn = ln.removed (k, ct); 533 | if (GCAS (ln, nn, ct)) 534 | return optv; 535 | else 536 | return null; 537 | } else { 538 | Option tmp = ln.get (k); 539 | if (tmp instanceof Some) { 540 | Some tmp1 = (Some) tmp; 541 | if (tmp1.get () == v) { 542 | MainNode nn = ln.removed (k, ct); 543 | if (GCAS (ln, nn, ct)) 544 | return tmp; 545 | else 546 | return null; 547 | } 548 | } 549 | } 550 | } 551 | throw new RuntimeException ("Should not happen"); 552 | } 553 | 554 | void cleanParent (final Object nonlive, final INode parent, final TrieMap ct, final int hc, final int lev, final Gen startgen) { 555 | while (true) { 556 | MainNode pm = parent.GCAS_READ (ct); 557 | if (pm instanceof CNode) { 558 | CNode cn = (CNode) pm; 559 | int idx = (hc >>> (lev - 5)) & 0x1f; 560 | int bmp = cn.bitmap; 561 | int flag = 1 << idx; 562 | if ((bmp & flag) == 0) { 563 | } // somebody already removed this i-node, we're done 564 | else { 565 | int pos = Integer.bitCount (bmp & (flag - 1)); 566 | BasicNode sub = cn.array [pos]; 567 | if (sub == this) { 568 | if (nonlive instanceof TNode) { 569 | TNode tn = (TNode) nonlive; 570 | MainNode ncn = cn.updatedAt (pos, tn.copyUntombed (), gen).toContracted (lev - 5); 571 | if (!parent.GCAS (cn, ncn, ct)) 572 | if (ct.readRoot ().gen == startgen) { 573 | // cleanParent (nonlive, parent, ct, hc, 574 | // lev, startgen); 575 | // tailrec 576 | continue; 577 | } 578 | } 579 | } 580 | } 581 | } else { 582 | // parent is no longer a cnode, we're done 583 | } 584 | break; 585 | } 586 | } 587 | 588 | private void clean (final INode nd, final TrieMap ct, int lev) { 589 | MainNode m = nd.GCAS_READ (ct); 590 | if (m instanceof CNode) { 591 | CNode cn = (CNode) m; 592 | nd.GCAS (cn, cn.toCompressed (ct, lev, gen), ct); 593 | } 594 | } 595 | 596 | final boolean isNullInode (final TrieMap ct) { 597 | return GCAS_READ (ct) == null; 598 | } 599 | 600 | final int cachedSize (final TrieMap ct) { 601 | MainNode m = GCAS_READ (ct); 602 | return m.cachedSize (ct); 603 | } 604 | 605 | // /* this is a quiescent method! */ 606 | // def string(lev: Int) = "%sINode -> %s".format(" " * lev, mainnode 607 | // match { 608 | // case null => "" 609 | // case tn: TNode[_, _] => "TNode(%s, %s, %d, !)".format(tn.k, tn.v, 610 | // tn.hc) 611 | // case cn: CNode[_, _] => cn.string(lev) 612 | // case ln: LNode[_, _] => ln.string(lev) 613 | // case x => "".format(x) 614 | // }) 615 | 616 | public String string (int lev) { 617 | return "INode"; 618 | } 619 | 620 | } 621 | 622 | private static final class FailedNode extends MainNode { 623 | MainNode p; 624 | 625 | FailedNode (final MainNode p) { 626 | this.p = p; 627 | WRITE_PREV (p); 628 | } 629 | 630 | public String string (int lev) { 631 | throw new UnsupportedOperationException (); 632 | } 633 | 634 | public int cachedSize (Object ct) { 635 | throw new UnsupportedOperationException (); 636 | } 637 | 638 | public String toString () { 639 | return String.format ("FailedNode(%s)", p); 640 | } 641 | } 642 | 643 | private interface KVNode { 644 | Map.Entry kvPair (); 645 | } 646 | 647 | private static final class SNode extends BasicNode implements KVNode { 648 | final K k; 649 | final V v; 650 | final int hc; 651 | 652 | SNode (final K k, final V v, final int hc) { 653 | this.k = k; 654 | this.v = v; 655 | this.hc = hc; 656 | } 657 | 658 | final SNode copy() { 659 | return new SNode (k, v, hc); 660 | } 661 | 662 | final TNode copyTombed () { 663 | return new TNode (k, v, hc); 664 | } 665 | 666 | final SNode copyUntombed () { 667 | return new SNode (k, v, hc); 668 | } 669 | 670 | final public Map.Entry kvPair () { 671 | return new SimpleImmutableEntry (k, v); 672 | } 673 | 674 | final public String string (int lev) { 675 | // (" " * lev) + "SNode(%s, %s, %x)".format(k, v, hc); 676 | return "SNode"; 677 | } 678 | } 679 | 680 | private static final class TNode extends MainNode implements KVNode { 681 | final K k; 682 | final V v; 683 | final int hc; 684 | 685 | TNode (final K k, final V v, final int hc) { 686 | this.k = k; 687 | this.v = v; 688 | this.hc = hc; 689 | } 690 | 691 | final TNode copy () { 692 | return new TNode (k, v, hc); 693 | } 694 | 695 | final TNode copyTombed () { 696 | return new TNode (k, v, hc); 697 | } 698 | 699 | final SNode copyUntombed () { 700 | return new SNode (k, v, hc); 701 | } 702 | 703 | final public Entry kvPair () { 704 | return new SimpleImmutableEntry (k, v); 705 | } 706 | 707 | final public int cachedSize (Object ct) { 708 | return 1; 709 | } 710 | 711 | final public String string (int lev) { 712 | // (" " * lev) + "TNode(%s, %s, %x, !)".format(k, v, hc); 713 | return "TNode"; 714 | } 715 | } 716 | 717 | private final static class LNode extends MainNode { 718 | final ListMap listmap; 719 | 720 | public LNode (final ListMap listmap) { 721 | this.listmap = listmap; 722 | } 723 | 724 | public LNode(K k, V v) { 725 | this (ListMap.map (k, v)); 726 | } 727 | 728 | public LNode (K k1, V v1, K k2, V v2) { 729 | this (ListMap.map (k1, v1, k2, v2)); 730 | } 731 | 732 | LNode inserted (K k, V v) { 733 | return new LNode (listmap.add (k, v)); 734 | } 735 | 736 | MainNode removed (K k, final TrieMap ct) { 737 | ListMap updmap = listmap.remove (k); 738 | if (updmap.size () > 1) 739 | return new LNode (updmap); 740 | else { 741 | Entry kv = updmap.iterator ().next (); 742 | // create it tombed so that it gets compressed on subsequent 743 | // accesses 744 | return new TNode (kv.getKey (), kv.getValue (), ct.computeHash (kv.getKey ())); 745 | } 746 | } 747 | 748 | Option get (K k) { 749 | return listmap.get (k); 750 | } 751 | 752 | public int cachedSize (Object ct) { 753 | return listmap.size (); 754 | } 755 | 756 | public String string (int lev) { 757 | // (" " * lev) + "LNode(%s)".format(listmap.mkString(", ")) 758 | return "LNode"; 759 | } 760 | } 761 | 762 | private static final class CNode extends CNodeBase { 763 | 764 | final int bitmap; 765 | final BasicNode[] array; 766 | final Gen gen; 767 | 768 | CNode (final int bitmap, final BasicNode[] array, final Gen gen) { 769 | this.bitmap = bitmap; 770 | this.array = array; 771 | this.gen = gen; 772 | } 773 | 774 | // this should only be called from within read-only snapshots 775 | final public int cachedSize (Object ct) { 776 | int currsz = READ_SIZE (); 777 | if (currsz != -1) 778 | return currsz; 779 | else { 780 | int sz = computeSize ((TrieMap) ct); 781 | while (READ_SIZE () == -1) 782 | CAS_SIZE (-1, sz); 783 | return READ_SIZE (); 784 | } 785 | } 786 | 787 | // lends itself towards being parallelizable by choosing 788 | // a random starting offset in the array 789 | // => if there are concurrent size computations, they start 790 | // at different positions, so they are more likely to 791 | // to be independent 792 | private int computeSize (final TrieMap ct) { 793 | int i = 0; 794 | int sz = 0; 795 | final int offset = (array.length > 0) ? ThreadLocalRandom.current().nextInt(0, array.length) : 0; 796 | while (i < array.length) { 797 | int pos = (i + offset) % array.length; 798 | BasicNode elem = array [pos]; 799 | if (elem instanceof SNode) 800 | sz += 1; 801 | else if (elem instanceof INode) 802 | sz += ((INode) elem).cachedSize (ct); 803 | i += 1; 804 | } 805 | return sz; 806 | } 807 | 808 | final CNode updatedAt (int pos, final BasicNode nn, final Gen gen) { 809 | BasicNode[] narr = array.clone(); 810 | narr [pos] = nn; 811 | return new CNode (bitmap, narr, gen); 812 | } 813 | 814 | final CNode removedAt (int pos, int flag, final Gen gen) { 815 | BasicNode[] arr = array; 816 | int len = arr.length; 817 | BasicNode[] narr = new BasicNode[len - 1]; 818 | System.arraycopy (arr, 0, narr, 0, pos); 819 | System.arraycopy (arr, pos + 1, narr, pos, len - pos - 1); 820 | return new CNode (bitmap ^ flag, narr, gen); 821 | } 822 | 823 | final CNode insertedAt (int pos, int flag, final BasicNode nn, final Gen gen) { 824 | int len = array.length; 825 | int bmp = bitmap; 826 | BasicNode[] narr = new BasicNode[len + 1]; 827 | System.arraycopy (array, 0, narr, 0, pos); 828 | narr [pos] = nn; 829 | System.arraycopy (array, pos, narr, pos + 1, len - pos); 830 | return new CNode (bmp | flag, narr, gen); 831 | } 832 | 833 | /** 834 | * Returns a copy of this cnode such that all the i-nodes below it are 835 | * copied to the specified generation `ngen`. 836 | */ 837 | final CNode renewed (final Gen ngen, final TrieMap ct) { 838 | int i = 0; 839 | BasicNode[] arr = array; 840 | int len = arr.length; 841 | BasicNode[] narr = new BasicNode[len]; 842 | while (i < len) { 843 | BasicNode elem = arr [i]; 844 | if (elem instanceof INode) { 845 | INode in = (INode) elem; 846 | narr [i] = in.copyToGen (ngen, ct); 847 | } else 848 | narr [i] = elem; 849 | i += 1; 850 | } 851 | return new CNode (bitmap, narr, ngen); 852 | } 853 | 854 | private BasicNode resurrect (final INode inode, final Object inodemain) { 855 | if (inodemain instanceof TNode) { 856 | TNode tn = (TNode) inodemain; 857 | return tn.copyUntombed (); 858 | } else 859 | return inode; 860 | } 861 | 862 | final MainNode toContracted (int lev) { 863 | if (array.length == 1 && lev > 0) { 864 | if (array [0] instanceof SNode) { 865 | SNode sn = (SNode) array [0]; 866 | return sn.copyTombed (); 867 | } else 868 | return this; 869 | 870 | } else 871 | return this; 872 | } 873 | 874 | // - if the branching factor is 1 for this CNode, and the child 875 | // is a tombed SNode, returns its tombed version 876 | // - otherwise, if there is at least one non-null node below, 877 | // returns the version of this node with at least some null-inodes 878 | // removed (those existing when the op began) 879 | // - if there are only null-i-nodes below, returns null 880 | final MainNode toCompressed (final TrieMap ct, int lev, Gen gen) { 881 | int bmp = bitmap; 882 | int i = 0; 883 | BasicNode[] arr = array; 884 | BasicNode[] tmparray = new BasicNode[arr.length]; 885 | while (i < arr.length) { // construct new bitmap 886 | BasicNode sub = arr [i]; 887 | if (sub instanceof INode) { 888 | INode in = (INode) sub; 889 | MainNode inodemain = in.gcasRead (ct); 890 | assert (inodemain != null); 891 | tmparray [i] = resurrect (in, inodemain); 892 | } else if (sub instanceof SNode) { 893 | tmparray [i] = sub; 894 | } 895 | i += 1; 896 | } 897 | 898 | return new CNode (bmp, tmparray, gen).toContracted (lev); 899 | } 900 | 901 | public String string (int lev) { 902 | // "CNode %x\n%s".format(bitmap, array.map(_.string(lev + 903 | // 1)).mkString("\n")); 904 | return "CNode"; 905 | } 906 | 907 | /* 908 | * quiescently consistent - don't call concurrently to anything 909 | * involving a GCAS!! 910 | */ 911 | // protected Seq collectElems() { 912 | // array flatMap { 913 | // case sn: SNode[K, V] => Some(sn.kvPair) 914 | // case in: INode[K, V] => in.mainnode match { 915 | // case tn: TNode[K, V] => Some(tn.kvPair) 916 | // case ln: LNode[K, V] => ln.listmap.toList 917 | // case cn: CNode[K, V] => cn.collectElems 918 | // } 919 | // } 920 | // } 921 | 922 | // protected Seq collectLocalElems() { 923 | // // array flatMap { 924 | // // case sn: SNode[K, V] => Some(sn.kvPair._2.toString) 925 | // // case in: INode[K, V] => Some(in.toString.drop(14) + "(" + in.gen + 926 | // ")") 927 | // // } 928 | // return null; 929 | // } 930 | 931 | public String toString () { 932 | // val elems = collectLocalElems 933 | // "CNode(sz: %d; %s)".format(elems.size, 934 | // elems.sorted.mkString(", ")) 935 | return "CNode"; 936 | } 937 | 938 | static MainNode dual (final SNode x, int xhc, final SNode y, int yhc, int lev, Gen gen) { 939 | if (lev < 35) { 940 | int xidx = (xhc >>> lev) & 0x1f; 941 | int yidx = (yhc >>> lev) & 0x1f; 942 | int bmp = (1 << xidx) | (1 << yidx); 943 | 944 | if (xidx == yidx) { 945 | INode subinode = new INode (gen);// (TrieMap.inodeupdater) 946 | subinode.mainnode = dual (x, xhc, y, yhc, lev + 5, gen); 947 | return new CNode (bmp, new BasicNode[] { subinode }, gen); 948 | } else { 949 | if (xidx < yidx) 950 | return new CNode (bmp, new BasicNode[] { x, y }, gen); 951 | else 952 | return new CNode (bmp, new BasicNode[] { y, x }, gen); 953 | } 954 | } else { 955 | return new LNode (x.k, x.v, y.k, y.v); 956 | } 957 | } 958 | 959 | } 960 | 961 | private static class RDCSS_Descriptor { 962 | INode old; 963 | MainNode expectedmain; 964 | INode nv; 965 | volatile boolean committed = false; 966 | 967 | public RDCSS_Descriptor (final INode old, final MainNode expectedmain, final INode nv) { 968 | this.old = old; 969 | this.expectedmain = expectedmain; 970 | this.nv = nv; 971 | } 972 | 973 | } 974 | 975 | private static class Equiv implements Serializable { 976 | private static final long serialVersionUID = 1L; 977 | 978 | public boolean equiv (K k1, K k2) { 979 | return k1.equals (k2); 980 | } 981 | 982 | static final Equiv universal = new Equiv (); 983 | } 984 | 985 | private static interface Hashing extends Serializable { 986 | public int hash (K k); 987 | } 988 | 989 | static class Default implements Hashing { 990 | private static final long serialVersionUID = 1L; 991 | 992 | public int hash (K k) { 993 | int h = k.hashCode (); 994 | // This function ensures that hashCodes that differ only by 995 | // constant multiples at each bit position have a bounded 996 | // number of collisions (approximately 8 at default load factor). 997 | h ^= (h >>> 20) ^ (h >>> 12); 998 | h ^= (h >>> 7) ^ (h >>> 4); 999 | return h; 1000 | } 1001 | 1002 | static final Default instance = new Default (); 1003 | } 1004 | 1005 | private final Hashing hashingobj; 1006 | private final Equiv equalityobj; 1007 | 1008 | Hashing hashing () { 1009 | return hashingobj; 1010 | } 1011 | 1012 | Equiv equality () { 1013 | return equalityobj; 1014 | } 1015 | 1016 | private transient volatile Object root; 1017 | private final transient boolean readOnly; 1018 | 1019 | TrieMap (final Hashing hashf, final Equiv ef, final boolean readOnly) { 1020 | this.hashingobj = hashf; 1021 | this.equalityobj = ef; 1022 | this.readOnly = readOnly; 1023 | } 1024 | 1025 | TrieMap (final Object r, final Hashing hashf, final Equiv ef, boolean readOnly) { 1026 | this(hashf, ef, readOnly); 1027 | this.root = r; 1028 | } 1029 | 1030 | public TrieMap (final Hashing hashf, final Equiv ef) { 1031 | this(INode.newRootNode(), hashf, ef, false); 1032 | } 1033 | 1034 | public TrieMap () { 1035 | this (Default.instance, Equiv.universal); 1036 | } 1037 | 1038 | /* internal methods */ 1039 | 1040 | final boolean CAS_ROOT (Object ov, Object nv) { 1041 | if (isReadOnly()) { 1042 | throw new IllegalStateException("Attempted to modify a read-only snapshot"); 1043 | } 1044 | return ROOT_UPDATER.compareAndSet (this, ov, nv); 1045 | } 1046 | 1047 | // FIXME: abort = false by default 1048 | final INode readRoot (boolean abort) { 1049 | return RDCSS_READ_ROOT (abort); 1050 | } 1051 | 1052 | final INode readRoot () { 1053 | return RDCSS_READ_ROOT (false); 1054 | } 1055 | 1056 | final INode RDCSS_READ_ROOT () { 1057 | return RDCSS_READ_ROOT (false); 1058 | } 1059 | 1060 | final INode RDCSS_READ_ROOT (boolean abort) { 1061 | Object r = /* READ */root; 1062 | if (r instanceof INode) 1063 | return (INode) r; 1064 | else if (r instanceof RDCSS_Descriptor) { 1065 | return RDCSS_Complete (abort); 1066 | } 1067 | throw new RuntimeException ("Should not happen"); 1068 | } 1069 | 1070 | private final INode RDCSS_Complete (final boolean abort) { 1071 | while (true) { 1072 | Object v = /* READ */root; 1073 | if (v instanceof INode) 1074 | return (INode) v; 1075 | else if (v instanceof RDCSS_Descriptor) { 1076 | RDCSS_Descriptor desc = (RDCSS_Descriptor) v; 1077 | INode ov = desc.old; 1078 | MainNode exp = desc.expectedmain; 1079 | INode nv = desc.nv; 1080 | 1081 | if (abort) { 1082 | if (CAS_ROOT (desc, ov)) 1083 | return ov; 1084 | else { 1085 | // return RDCSS_Complete (abort); 1086 | // tailrec 1087 | continue; 1088 | } 1089 | } else { 1090 | MainNode oldmain = ov.gcasRead (this); 1091 | if (oldmain == exp) { 1092 | if (CAS_ROOT (desc, nv)) { 1093 | desc.committed = true; 1094 | return nv; 1095 | } else { 1096 | // return RDCSS_Complete (abort); 1097 | // tailrec 1098 | continue; 1099 | } 1100 | } else { 1101 | if (CAS_ROOT (desc, ov)) 1102 | return ov; 1103 | else { 1104 | // return RDCSS_Complete (abort); 1105 | // tailrec 1106 | continue; 1107 | 1108 | } 1109 | } 1110 | } 1111 | } 1112 | 1113 | throw new RuntimeException ("Should not happen"); 1114 | } 1115 | } 1116 | 1117 | private boolean RDCSS_ROOT (final INode ov, final MainNode expectedmain, final INode nv) { 1118 | RDCSS_Descriptor desc = new RDCSS_Descriptor (ov, expectedmain, nv); 1119 | if (CAS_ROOT (ov, desc)) { 1120 | RDCSS_Complete (false); 1121 | return /* READ */desc.committed; 1122 | } else 1123 | return false; 1124 | } 1125 | 1126 | private void inserthc (final K k, final int hc, final V v) { 1127 | while (true) { 1128 | INode r = RDCSS_READ_ROOT (); 1129 | if (!r.rec_insert (k, v, hc, 0, null, r.gen, this)) { 1130 | // inserthc (k, hc, v); 1131 | // tailrec 1132 | continue; 1133 | } 1134 | break; 1135 | } 1136 | } 1137 | 1138 | private Option insertifhc (final K k, final int hc, final V v, final Object cond) { 1139 | while (true) { 1140 | INode r = RDCSS_READ_ROOT (); 1141 | 1142 | Option ret = r.rec_insertif (k, v, hc, cond, 0, null, r.gen, this); 1143 | if (ret == null) { 1144 | // return insertifhc (k, hc, v, cond); 1145 | // tailrec 1146 | continue; 1147 | } else 1148 | return ret; 1149 | } 1150 | } 1151 | 1152 | private Object lookuphc (final K k, final int hc) { 1153 | while (true) { 1154 | INode r = RDCSS_READ_ROOT (); 1155 | Object res = r.rec_lookup (k, hc, 0, null, r.gen, this); 1156 | if (res == INodeBase.RESTART) { 1157 | // return lookuphc (k, hc); 1158 | // tailrec 1159 | continue; 1160 | } else 1161 | return res; 1162 | } 1163 | } 1164 | 1165 | private Option removehc (final K k, final V v, final int hc) { 1166 | while (true) { 1167 | INode r = RDCSS_READ_ROOT (); 1168 | Option res = r.rec_remove (k, v, hc, 0, null, r.gen, this); 1169 | if (res != null) 1170 | return res; 1171 | else { 1172 | // return removehc (k, v, hc); 1173 | // tailrec 1174 | continue; 1175 | } 1176 | } 1177 | } 1178 | 1179 | /** 1180 | * Ensure this instance is read-write, throw UnsupportedOperationException 1181 | * otherwise. Used by Map-type methods for quick check. 1182 | */ 1183 | private void ensureReadWrite() { 1184 | if (isReadOnly()) { 1185 | throw new UnsupportedOperationException("Attempted to modify a read-only view"); 1186 | } 1187 | } 1188 | 1189 | public String string () { 1190 | // RDCSS_READ_ROOT().string(0); 1191 | return "Root"; 1192 | } 1193 | 1194 | /* public methods */ 1195 | 1196 | // public Seq seq() { 1197 | // return this; 1198 | // } 1199 | 1200 | // override def par = new ParTrieMap(this) 1201 | 1202 | // static TrieMap empty() { 1203 | // return new TrieMap(); 1204 | // } 1205 | 1206 | final boolean isReadOnly () { 1207 | return readOnly; 1208 | } 1209 | 1210 | final boolean nonReadOnly () { 1211 | return !readOnly; 1212 | } 1213 | 1214 | /** 1215 | * Returns a snapshot of this TrieMap. This operation is lock-free and 1216 | * linearizable. 1217 | * 1218 | * The snapshot is lazily updated - the first time some branch in the 1219 | * snapshot or this TrieMap are accessed, they are rewritten. This means 1220 | * that the work of rebuilding both the snapshot and this TrieMap is 1221 | * distributed across all the threads doing updates or accesses subsequent 1222 | * to the snapshot creation. 1223 | */ 1224 | 1225 | final public TrieMap snapshot () { 1226 | while (true) { 1227 | INode r = RDCSS_READ_ROOT (); 1228 | final MainNode expmain = r.gcasRead (this); 1229 | if (RDCSS_ROOT (r, expmain, r.copyToGen (new Gen (), this))) 1230 | return new TrieMap (r.copyToGen (new Gen (), this), hashing (), equality (), readOnly); 1231 | else { 1232 | // return snapshot (); 1233 | // tailrec 1234 | continue; 1235 | } 1236 | } 1237 | } 1238 | 1239 | /** 1240 | * Returns a read-only snapshot of this TrieMap. This operation is lock-free 1241 | * and linearizable. 1242 | * 1243 | * The snapshot is lazily updated - the first time some branch of this 1244 | * TrieMap are accessed, it is rewritten. The work of creating the snapshot 1245 | * is thus distributed across subsequent updates and accesses on this 1246 | * TrieMap by all threads. Note that the snapshot itself is never rewritten 1247 | * unlike when calling the `snapshot` method, but the obtained snapshot 1248 | * cannot be modified. 1249 | * 1250 | * This method is used by other methods such as `size` and `iterator`. 1251 | */ 1252 | final public TrieMap readOnlySnapshot () { 1253 | // Is it a snapshot of a read-only snapshot? 1254 | if(!nonReadOnly ()) 1255 | return this; 1256 | 1257 | while (true) { 1258 | INode r = RDCSS_READ_ROOT (); 1259 | MainNode expmain = r.gcasRead (this); 1260 | if (RDCSS_ROOT (r, expmain, r.copyToGen (new Gen (), this))) 1261 | return new TrieMap (r, hashing (), equality (), true); 1262 | else { 1263 | // return readOnlySnapshot (); 1264 | continue; 1265 | } 1266 | } 1267 | } 1268 | 1269 | final public void clear () { 1270 | while (true) { 1271 | INode r = RDCSS_READ_ROOT (); 1272 | if (!RDCSS_ROOT (r, r.gcasRead (this), INode.newRootNode ())) { 1273 | continue; 1274 | }else{ 1275 | return; 1276 | } 1277 | } 1278 | } 1279 | 1280 | // @inline 1281 | int computeHash (K k) { 1282 | return hashingobj.hash (k); 1283 | } 1284 | 1285 | final V lookup (K k) { 1286 | int hc = computeHash (k); 1287 | // return (V) lookuphc (k, hc); 1288 | Object o = lookuphc (k, hc); 1289 | if(o instanceof Some) { 1290 | return ((Some)o).get (); 1291 | } else if(o instanceof None) 1292 | return null; 1293 | else 1294 | return (V)o; 1295 | } 1296 | 1297 | final V apply (K k) { 1298 | int hc = computeHash (k); 1299 | Object res = lookuphc (k, hc); 1300 | if (res == null) 1301 | throw new NoSuchElementException (); 1302 | else 1303 | return (V) res; 1304 | } 1305 | 1306 | // final public Option get (K k) { 1307 | // int hc = computeHash (k); 1308 | // return Option.makeOption ((V)lookuphc (k, hc)); 1309 | // } 1310 | 1311 | final public V get (Object k) { 1312 | return lookup((K)k); 1313 | } 1314 | 1315 | final public Option putOpt(Object key, Object value) { 1316 | int hc = computeHash ((K)key); 1317 | return insertifhc ((K)key, hc, (V)value, null); 1318 | } 1319 | 1320 | @Override 1321 | final public V put (Object key, Object value) { 1322 | ensureReadWrite(); 1323 | int hc = computeHash ((K)key); 1324 | Option ov = insertifhc ((K)key, hc, (V)value, null); 1325 | if(ov instanceof Some) { 1326 | Some sv = (Some)ov; 1327 | return sv.get (); 1328 | } else 1329 | return null; 1330 | } 1331 | 1332 | final public void update (K k, V v) { 1333 | int hc = computeHash (k); 1334 | inserthc (k, hc, v); 1335 | } 1336 | 1337 | final public TrieMap add (K k, V v) { 1338 | update (k, v); 1339 | return this; 1340 | } 1341 | 1342 | final Option removeOpt (K k) { 1343 | int hc = computeHash (k); 1344 | return removehc (k, null, hc); 1345 | } 1346 | 1347 | @Override 1348 | final public V remove (Object k) { 1349 | ensureReadWrite(); 1350 | int hc = computeHash ((K)k); 1351 | Option ov = removehc ((K)k, null, hc); 1352 | if(ov instanceof Some) { 1353 | Some sv = (Some)ov; 1354 | return sv.get(); 1355 | } else 1356 | return null; 1357 | } 1358 | 1359 | // final public TrieMap remove (Object k) { 1360 | // removeOpt ((K)k); 1361 | // return this; 1362 | // } 1363 | 1364 | final public Option putIfAbsentOpt (K k, V v) { 1365 | int hc = computeHash (k); 1366 | return insertifhc (k, hc, v, INode.KEY_ABSENT); 1367 | } 1368 | 1369 | @Override 1370 | final public V putIfAbsent (Object k, Object v) { 1371 | ensureReadWrite(); 1372 | int hc = computeHash ((K)k); 1373 | Option ov = insertifhc ((K)k, hc, (V)v, INode.KEY_ABSENT); 1374 | if(ov instanceof Some) { 1375 | Some sv = (Some)ov; 1376 | return sv.get(); 1377 | } else 1378 | return null; 1379 | } 1380 | 1381 | @Override 1382 | public boolean remove (Object k, Object v) { 1383 | ensureReadWrite(); 1384 | int hc = computeHash ((K)k); 1385 | return removehc ((K)k, (V)v, hc).nonEmpty (); 1386 | } 1387 | 1388 | @Override 1389 | public boolean replace (K k, V oldvalue, V newvalue) { 1390 | ensureReadWrite(); 1391 | int hc = computeHash (k); 1392 | return insertifhc (k, hc, newvalue, oldvalue).nonEmpty (); 1393 | } 1394 | 1395 | public Option replaceOpt (K k, V v) { 1396 | int hc = computeHash (k); 1397 | return insertifhc (k, hc, v, INode.KEY_PRESENT); 1398 | } 1399 | 1400 | @Override 1401 | public V replace (Object k, Object v) { 1402 | ensureReadWrite(); 1403 | int hc = computeHash ((K)k); 1404 | Option ov = insertifhc ((K)k, hc, (V)v, INode.KEY_PRESENT); 1405 | if(ov instanceof Some) { 1406 | Some sv = (Some)ov; 1407 | return sv.get(); 1408 | } else 1409 | return null; 1410 | } 1411 | 1412 | /*** 1413 | * Return an iterator over a TrieMap. 1414 | * 1415 | * If this is a read-only snapshot, it would return a read-only iterator. 1416 | * 1417 | * If it is the original TrieMap or a non-readonly snapshot, it would return 1418 | * an iterator that would allow for updates. 1419 | * 1420 | * @return 1421 | */ 1422 | public Iterator> iterator () { 1423 | if (!nonReadOnly ()) 1424 | return readOnlySnapshot ().readOnlyIterator (); 1425 | else 1426 | return new TrieMapIterator (0, this); 1427 | } 1428 | 1429 | /*** 1430 | * Return an iterator over a TrieMap. 1431 | * This is a read-only iterator. 1432 | * 1433 | * @return 1434 | */ 1435 | public Iterator> readOnlyIterator () { 1436 | if (nonReadOnly ()) 1437 | return readOnlySnapshot ().readOnlyIterator (); 1438 | else 1439 | return new TrieMapReadOnlyIterator (0, this); 1440 | } 1441 | 1442 | private int cachedSize () { 1443 | INode r = RDCSS_READ_ROOT (); 1444 | return r.cachedSize (this); 1445 | } 1446 | 1447 | public int size () { 1448 | if (nonReadOnly ()) 1449 | return readOnlySnapshot ().size (); 1450 | else 1451 | return cachedSize (); 1452 | } 1453 | 1454 | String stringPrefix () { 1455 | return "TrieMap"; 1456 | } 1457 | 1458 | /*** 1459 | * This iterator is a read-only one and does not allow for any update 1460 | * operations on the underlying data structure. 1461 | * 1462 | * @param 1463 | * @param 1464 | */ 1465 | private static class TrieMapReadOnlyIterator extends TrieMapIterator { 1466 | TrieMapReadOnlyIterator (int level, final TrieMap ct, boolean mustInit) { 1467 | super (level, ct, mustInit); 1468 | } 1469 | 1470 | TrieMapReadOnlyIterator (int level, TrieMap ct) { 1471 | this (level, ct, true); 1472 | } 1473 | void initialize () { 1474 | assert (ct.isReadOnly ()); 1475 | super.initialize (); 1476 | } 1477 | 1478 | public void remove () { 1479 | throw new UnsupportedOperationException ("Operation not supported for read-only iterators"); 1480 | } 1481 | 1482 | Map.Entry nextEntry(final Map.Entry rr) { 1483 | // Return non-updatable entry 1484 | return rr; 1485 | } 1486 | } 1487 | 1488 | private static class TrieMapIterator implements java.util.Iterator> { 1489 | private int level; 1490 | protected TrieMap ct; 1491 | private final boolean mustInit; 1492 | private BasicNode[][] stack = new BasicNode[7][]; 1493 | private int[] stackpos = new int[7]; 1494 | private int depth = -1; 1495 | private Iterator> subiter = null; 1496 | private KVNode current = null; 1497 | private Map.Entry lastReturned = null; 1498 | 1499 | TrieMapIterator (int level, final TrieMap ct, boolean mustInit) { 1500 | this.level = level; 1501 | this.ct = ct; 1502 | this.mustInit = mustInit; 1503 | if (this.mustInit) 1504 | initialize (); 1505 | } 1506 | 1507 | TrieMapIterator (int level, TrieMap ct) { 1508 | this (level, ct, true); 1509 | } 1510 | 1511 | 1512 | public boolean hasNext () { 1513 | return (current != null) || (subiter != null); 1514 | } 1515 | 1516 | public Map.Entry next () { 1517 | if (hasNext ()) { 1518 | Map.Entry r = null; 1519 | if (subiter != null) { 1520 | r = subiter.next (); 1521 | checkSubiter (); 1522 | } else { 1523 | r = current.kvPair (); 1524 | advance (); 1525 | } 1526 | 1527 | lastReturned = r; 1528 | return r != null ? nextEntry(r) : null; 1529 | } else { 1530 | // return Iterator.empty ().next (); 1531 | return null; 1532 | } 1533 | } 1534 | 1535 | Map.Entry nextEntry(final Map.Entry rr) { 1536 | return new Map.Entry() { 1537 | private V updated = null; 1538 | 1539 | @Override 1540 | public K getKey () { 1541 | return rr.getKey (); 1542 | } 1543 | 1544 | @Override 1545 | public V getValue () { 1546 | return (updated == null)?rr.getValue (): updated; 1547 | } 1548 | 1549 | @Override 1550 | public V setValue (V value) { 1551 | updated = value; 1552 | return ct.replace (getKey (), value); 1553 | } 1554 | }; 1555 | } 1556 | 1557 | private void readin (INode in) { 1558 | MainNode m = in.gcasRead (ct); 1559 | if (m instanceof CNode) { 1560 | CNode cn = (CNode) m; 1561 | depth += 1; 1562 | stack [depth] = cn.array; 1563 | stackpos [depth] = -1; 1564 | advance (); 1565 | } else if (m instanceof TNode) { 1566 | current = (TNode) m; 1567 | } else if (m instanceof LNode) { 1568 | subiter = ((LNode) m).listmap.iterator (); 1569 | checkSubiter (); 1570 | } else if (m == null) { 1571 | current = null; 1572 | } 1573 | } 1574 | 1575 | // @inline 1576 | private void checkSubiter () { 1577 | if (!subiter.hasNext ()) { 1578 | subiter = null; 1579 | advance (); 1580 | } 1581 | } 1582 | 1583 | // @inline 1584 | void initialize () { 1585 | // assert (ct.isReadOnly ()); 1586 | INode r = ct.RDCSS_READ_ROOT (); 1587 | readin (r); 1588 | } 1589 | 1590 | void advance () { 1591 | if (depth >= 0) { 1592 | int npos = stackpos [depth] + 1; 1593 | if (npos < stack [depth].length) { 1594 | stackpos [depth] = npos; 1595 | BasicNode elem = stack [depth] [npos]; 1596 | if (elem instanceof SNode) { 1597 | current = (SNode) elem; 1598 | } else if (elem instanceof INode) { 1599 | readin ((INode) elem); 1600 | } 1601 | } else { 1602 | depth -= 1; 1603 | advance (); 1604 | } 1605 | } else 1606 | current = null; 1607 | } 1608 | 1609 | protected TrieMapIterator newIterator (int _lev, TrieMap _ct, boolean _mustInit) { 1610 | return new TrieMapIterator (_lev, _ct, _mustInit); 1611 | } 1612 | 1613 | protected void dupTo (TrieMapIterator it) { 1614 | it.level = this.level; 1615 | it.ct = this.ct; 1616 | it.depth = this.depth; 1617 | it.current = this.current; 1618 | 1619 | // these need a deep copy 1620 | System.arraycopy (this.stack, 0, it.stack, 0, 7); 1621 | System.arraycopy (this.stackpos, 0, it.stackpos, 0, 7); 1622 | 1623 | // this one needs to be evaluated 1624 | if (this.subiter == null) 1625 | it.subiter = null; 1626 | else { 1627 | List> lst = toList (this.subiter); 1628 | this.subiter = lst.iterator (); 1629 | it.subiter = lst.iterator (); 1630 | } 1631 | } 1632 | 1633 | // /** Returns a sequence of iterators over subsets of this iterator. 1634 | // * It's used to ease the implementation of splitters for a parallel 1635 | // version of the TrieMap. 1636 | // */ 1637 | // protected def subdivide(): Seq[Iterator[(K, V)]] = if (subiter ne 1638 | // null) { 1639 | // // the case where an LNode is being iterated 1640 | // val it = subiter 1641 | // subiter = null 1642 | // advance() 1643 | // this.level += 1 1644 | // Seq(it, this) 1645 | // } else if (depth == -1) { 1646 | // this.level += 1 1647 | // Seq(this) 1648 | // } else { 1649 | // var d = 0 1650 | // while (d <= depth) { 1651 | // val rem = stack(d).length - 1 - stackpos(d) 1652 | // if (rem > 0) { 1653 | // val (arr1, arr2) = stack(d).drop(stackpos(d) + 1).splitAt(rem / 2) 1654 | // stack(d) = arr1 1655 | // stackpos(d) = -1 1656 | // val it = newIterator(level + 1, ct, false) 1657 | // it.stack(0) = arr2 1658 | // it.stackpos(0) = -1 1659 | // it.depth = 0 1660 | // it.advance() // <-- fix it 1661 | // this.level += 1 1662 | // return Seq(this, it) 1663 | // } 1664 | // d += 1 1665 | // } 1666 | // this.level += 1 1667 | // Seq(this) 1668 | // } 1669 | 1670 | private List> toList (Iterator> it) { 1671 | ArrayList> list = new ArrayList> (); 1672 | while (it.hasNext ()) { 1673 | list.add (it.next ()); 1674 | } 1675 | return list; 1676 | } 1677 | 1678 | void printDebug () { 1679 | System.out.println ("ctrie iterator"); 1680 | System.out.println (Arrays.toString (stackpos)); 1681 | System.out.println ("depth: " + depth); 1682 | System.out.println ("curr.: " + current); 1683 | // System.out.println(stack.mkString("\n")); 1684 | } 1685 | 1686 | @Override 1687 | public void remove () { 1688 | if (lastReturned != null) { 1689 | ct.remove (lastReturned.getKey ()); 1690 | lastReturned = null; 1691 | } else 1692 | throw new IllegalStateException(); 1693 | } 1694 | 1695 | } 1696 | 1697 | /** Only used for ctrie serialization. */ 1698 | // @SerialVersionUID(0L - 7237891413820527142L) 1699 | private static long TrieMapSerializationEnd = 0L - 7237891413820527142L; 1700 | 1701 | 1702 | public boolean containsKey (Object key) { 1703 | return lookup ((K) key) != null; 1704 | } 1705 | 1706 | 1707 | @Override 1708 | public Set> entrySet () { 1709 | return entrySet; 1710 | } 1711 | 1712 | /*** 1713 | * Support for EntrySet operations required by the Map interface 1714 | * 1715 | */ 1716 | final class EntrySet extends AbstractSet> { 1717 | 1718 | @Override 1719 | public Iterator> iterator () { 1720 | return TrieMap.this.iterator (); 1721 | } 1722 | 1723 | @Override 1724 | public final boolean contains (final Object o) { 1725 | if (!(o instanceof Map.Entry)) { 1726 | return false; 1727 | } 1728 | final Map.Entry e = (Map.Entry) o; 1729 | final K k = e.getKey (); 1730 | final V v = lookup (k); 1731 | return v != null; 1732 | } 1733 | 1734 | @Override 1735 | public final boolean remove (final Object o) { 1736 | if (!(o instanceof Map.Entry)) { 1737 | return false; 1738 | } 1739 | final Map.Entry e = (Map.Entry) o; 1740 | final K k = e.getKey (); 1741 | return null != TrieMap.this.remove (k); 1742 | } 1743 | 1744 | @Override 1745 | public final int size () { 1746 | int size = 0; 1747 | for (final Iterator i = iterator (); i.hasNext (); i.next ()) { 1748 | size++; 1749 | } 1750 | return size; 1751 | } 1752 | 1753 | @Override 1754 | public final void clear () { 1755 | TrieMap.this.clear (); 1756 | } 1757 | } 1758 | 1759 | private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { 1760 | inputStream.defaultReadObject(); 1761 | this.root = INode.newRootNode(); 1762 | 1763 | final boolean ro = inputStream.readBoolean(); 1764 | final int size = inputStream.readInt(); 1765 | for (int i = 0; i < size; ++i) { 1766 | final K key = (K)inputStream.readObject(); 1767 | final V value = (V)inputStream.readObject(); 1768 | add(key, value); 1769 | } 1770 | 1771 | // Propagate the read-only bit 1772 | try { 1773 | READONLY_FIELD.setBoolean(this, ro); 1774 | } catch (IllegalAccessException e) { 1775 | throw new IOException("Failed to set read-only flag", e); 1776 | } 1777 | } 1778 | 1779 | private void writeObject(ObjectOutputStream outputStream) throws IOException { 1780 | outputStream.defaultWriteObject(); 1781 | 1782 | final Map ro = readOnlySnapshot(); 1783 | outputStream.writeBoolean(isReadOnly()); 1784 | outputStream.writeInt(ro.size()); 1785 | 1786 | for (Entry e : ro.entrySet()) { 1787 | outputStream.writeObject(e.getKey()); 1788 | outputStream.writeObject(e.getValue()); 1789 | } 1790 | } 1791 | } 1792 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestCNodeFlagCollision.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.Map; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestCNodeFlagCollision { 8 | @Test 9 | public void testCNodeFlagCollision () { 10 | final Map map = new TrieMap (); 11 | final Integer z15169 = Integer.valueOf (15169); 12 | final Integer z28336 = Integer.valueOf (28336); 13 | 14 | TestHelper.assertTrue (null == map.get (z15169)); 15 | TestHelper.assertTrue (null == map.get (z28336)); 16 | 17 | map.put (z15169, z15169); 18 | TestHelper.assertTrue (null != map.get (z15169)); 19 | TestHelper.assertTrue (null == map.get (z28336)); 20 | 21 | map.put (z28336, z28336); 22 | TestHelper.assertTrue (null != map.get (z15169)); 23 | TestHelper.assertTrue (null != map.get (z28336)); 24 | 25 | map.remove (z15169); 26 | 27 | TestHelper.assertTrue (null == map.get (z15169)); 28 | TestHelper.assertTrue (null != map.get (z28336)); 29 | 30 | map.remove (z28336); 31 | 32 | TestHelper.assertTrue (null == map.get (z15169)); 33 | TestHelper.assertTrue (null == map.get (z28336)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestCNodeInsertionIncorrectOrder.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.Map; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestCNodeInsertionIncorrectOrder { 8 | 9 | @Test 10 | public void testCNodeInsertionIncorrectOrder () { 11 | final Map map = new TrieMap (); 12 | final Integer z3884 = Integer.valueOf (3884); 13 | final Integer z4266 = Integer.valueOf (4266); 14 | map.put (z3884, z3884); 15 | TestHelper.assertTrue (null != map.get (z3884)); 16 | 17 | map.put (z4266, z4266); 18 | TestHelper.assertTrue (null != map.get (z3884)); 19 | TestHelper.assertTrue (null != map.get (z4266)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestConcurrentMapPutIfAbsent.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.concurrent.ConcurrentMap; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestConcurrentMapPutIfAbsent { 8 | private static final int COUNT = 50*1000; 9 | 10 | @Test 11 | public void testConcurrentMapPutIfAbsent () { 12 | final ConcurrentMap map = new TrieMap (); 13 | 14 | for (int i = 0; i < COUNT; i++) { 15 | TestHelper.assertTrue (null == map.putIfAbsent (i, i)); 16 | TestHelper.assertTrue (Integer.valueOf (i).equals (map.putIfAbsent (i, i))); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestConcurrentMapRemove.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.concurrent.ConcurrentMap; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestConcurrentMapRemove { 8 | private static final int COUNT = 50*1000; 9 | 10 | @Test 11 | public void testConcurrentMapRemove () { 12 | final ConcurrentMap map = new TrieMap (); 13 | 14 | for (int i = 128; i < COUNT; i++) { 15 | TestHelper.assertFalse (map.remove (i, i)); 16 | TestHelper.assertTrue (null == map.put (i, i)); 17 | TestHelper.assertFalse (map.remove (i, "lol")); 18 | TestHelper.assertTrue (map.containsKey (i)); 19 | TestHelper.assertTrue (map.remove (i, i)); 20 | TestHelper.assertFalse (map.containsKey (i)); 21 | TestHelper.assertTrue (null == map.put (i, i)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestConcurrentMapReplace.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.concurrent.ConcurrentMap; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestConcurrentMapReplace { 8 | private static final int COUNT = 50*1000; 9 | 10 | @Test 11 | public void testConcurrentMapReplace () { 12 | final ConcurrentMap map = new TrieMap (); 13 | 14 | for (int i = 0; i < COUNT; i++) { 15 | TestHelper.assertTrue (null == map.replace (i, "lol")); 16 | TestHelper.assertFalse (map.replace (i, i, "lol2")); 17 | TestHelper.assertTrue (null == map.put (i, i)); 18 | TestHelper.assertTrue (Integer.valueOf (i).equals (map.replace (i, "lol"))); 19 | TestHelper.assertFalse (map.replace (i, i, "lol2")); 20 | TestHelper.assertTrue (map.replace (i, "lol", i)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestDelete.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import org.junit.Test; 4 | 5 | 6 | public class TestDelete { 7 | @Test 8 | public void testDelete () { 9 | final TrieMap bt = new TrieMap (); 10 | 11 | for (int i = 0; i < 10000; i++) { 12 | TestHelper.assertEquals (null, bt.put (Integer.valueOf (i), Integer.valueOf (i))); 13 | final Object lookup = bt.lookup (Integer.valueOf (i)); 14 | TestHelper.assertEquals (Integer.valueOf (i), lookup); 15 | } 16 | 17 | checkAddInsert (bt, 536); 18 | checkAddInsert (bt, 4341); 19 | checkAddInsert (bt, 8437); 20 | 21 | for (int i = 0; i < 10000; i++) { 22 | boolean removed = null != bt.remove(Integer.valueOf (i)); 23 | TestHelper.assertEquals (Boolean.TRUE, Boolean.valueOf (removed)); 24 | final Object lookup = bt.lookup (Integer.valueOf (i)); 25 | TestHelper.assertEquals (null, lookup); 26 | } 27 | 28 | bt.toString (); 29 | } 30 | 31 | private static void checkAddInsert (final TrieMap bt, int k) { 32 | final Integer v = Integer.valueOf (k); 33 | bt.remove (v); 34 | Object foundV = bt.lookup (v); 35 | TestHelper.assertEquals (null, foundV); 36 | TestHelper.assertEquals (null, bt.put (v, v)); 37 | foundV = bt.lookup (v); 38 | TestHelper.assertEquals (v, foundV); 39 | 40 | TestHelper.assertEquals (v, bt.put (v, Integer.valueOf (-1))); 41 | TestHelper.assertEquals (Integer.valueOf (-1), bt.put (v, v)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestHashCollisions.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | 4 | import org.junit.Test; 5 | 6 | public class TestHashCollisions { 7 | @Test 8 | public void testHashCollisions () { 9 | final TrieMap bt = new TrieMap (); 10 | 11 | insertStrings (bt); 12 | insertChars (bt); 13 | insertInts (bt); 14 | insertBytes (bt); 15 | 16 | removeStrings (bt); 17 | removeChars (bt); 18 | removeInts (bt); 19 | removeBytes (bt); 20 | 21 | insertStrings (bt); 22 | insertInts (bt); 23 | insertBytes (bt); 24 | insertChars (bt); 25 | 26 | removeBytes (bt); 27 | removeStrings (bt); 28 | removeChars (bt); 29 | removeInts (bt); 30 | 31 | insertStrings (bt); 32 | insertInts (bt); 33 | insertBytes (bt); 34 | insertChars (bt); 35 | 36 | removeStrings (bt); 37 | removeChars (bt); 38 | removeInts (bt); 39 | removeBytes (bt); 40 | 41 | insertStrings (bt); 42 | insertInts (bt); 43 | insertBytes (bt); 44 | insertChars (bt); 45 | 46 | removeChars (bt); 47 | removeInts (bt); 48 | removeBytes (bt); 49 | removeStrings (bt); 50 | 51 | insertStrings (bt); 52 | insertInts (bt); 53 | insertBytes (bt); 54 | insertChars (bt); 55 | 56 | removeInts (bt); 57 | removeBytes (bt); 58 | removeStrings (bt); 59 | removeChars (bt); 60 | 61 | System.out.println (bt); 62 | } 63 | 64 | private static void insertChars (final TrieMap bt) { 65 | TestHelper.assertEquals (null, bt.put ('a', 'a')); 66 | TestHelper.assertEquals (null, bt.put ('b', 'b')); 67 | TestHelper.assertEquals (null, bt.put ('c', 'c')); 68 | TestHelper.assertEquals (null, bt.put ('d', 'd')); 69 | TestHelper.assertEquals (null, bt.put ('e', 'e')); 70 | 71 | TestHelper.assertEquals ('a', bt.put ('a', 'a')); 72 | TestHelper.assertEquals ('b', bt.put ('b', 'b')); 73 | TestHelper.assertEquals ('c', bt.put ('c', 'c')); 74 | TestHelper.assertEquals ('d', bt.put ('d', 'd')); 75 | TestHelper.assertEquals ('e', bt.put ('e', 'e')); 76 | } 77 | 78 | private static void insertStrings (final TrieMap bt) { 79 | TestHelper.assertEquals (null, bt.put ("a", "a")); 80 | TestHelper.assertEquals (null, bt.put ("b", "b")); 81 | TestHelper.assertEquals (null, bt.put ("c", "c")); 82 | TestHelper.assertEquals (null, bt.put ("d", "d")); 83 | TestHelper.assertEquals (null, bt.put ("e", "e")); 84 | 85 | TestHelper.assertEquals ("a", bt.put ("a", "a")); 86 | TestHelper.assertEquals ("b", bt.put ("b", "b")); 87 | TestHelper.assertEquals ("c", bt.put ("c", "c")); 88 | TestHelper.assertEquals ("d", bt.put ("d", "d")); 89 | TestHelper.assertEquals ("e", bt.put ("e", "e")); 90 | } 91 | 92 | private static void insertBytes (final TrieMap bt) { 93 | for (byte i = 0; i < 128 && i >= 0; i++) { 94 | final Byte bigB = Byte.valueOf (i); 95 | TestHelper.assertEquals (null, bt.put (bigB, bigB)); 96 | TestHelper.assertEquals (bigB, bt.put (bigB, bigB)); 97 | } 98 | } 99 | 100 | private static void insertInts (final TrieMap bt) { 101 | for (int i = 0; i < 128; i++) { 102 | final Integer bigI = Integer.valueOf (i); 103 | TestHelper.assertEquals (null, bt.put (bigI, bigI)); 104 | TestHelper.assertEquals (bigI, bt.put (bigI, bigI)); 105 | } 106 | } 107 | 108 | private static void removeChars (final TrieMap bt) { 109 | TestHelper.assertTrue (null != bt.lookup ('a')); 110 | TestHelper.assertTrue (null != bt.lookup ('b')); 111 | TestHelper.assertTrue (null != bt.lookup ('c')); 112 | TestHelper.assertTrue (null != bt.lookup ('d')); 113 | TestHelper.assertTrue (null != bt.lookup ('e')); 114 | 115 | TestHelper.assertTrue (null != bt.remove ('a')); 116 | TestHelper.assertTrue (null != bt.remove ('b')); 117 | TestHelper.assertTrue (null != bt.remove ('c')); 118 | TestHelper.assertTrue (null != bt.remove ('d')); 119 | TestHelper.assertTrue (null != bt.remove ('e')); 120 | 121 | TestHelper.assertFalse (null != bt.remove ('a')); 122 | TestHelper.assertFalse (null != bt.remove ('b')); 123 | TestHelper.assertFalse (null != bt.remove ('c')); 124 | TestHelper.assertFalse (null != bt.remove ('d')); 125 | TestHelper.assertFalse (null != bt.remove ('e')); 126 | 127 | TestHelper.assertTrue (null == bt.lookup ('a')); 128 | TestHelper.assertTrue (null == bt.lookup ('b')); 129 | TestHelper.assertTrue (null == bt.lookup ('c')); 130 | TestHelper.assertTrue (null == bt.lookup ('d')); 131 | TestHelper.assertTrue (null == bt.lookup ('e')); 132 | } 133 | 134 | private static void removeStrings (final TrieMap bt) { 135 | TestHelper.assertTrue (null != bt.lookup ("a")); 136 | TestHelper.assertTrue (null != bt.lookup ("b")); 137 | TestHelper.assertTrue (null != bt.lookup ("c")); 138 | TestHelper.assertTrue (null != bt.lookup ("d")); 139 | TestHelper.assertTrue (null != bt.lookup ("e")); 140 | 141 | TestHelper.assertTrue (null != bt.remove ("a")); 142 | TestHelper.assertTrue (null != bt.remove ("b")); 143 | TestHelper.assertTrue (null != bt.remove ("c")); 144 | TestHelper.assertTrue (null != bt.remove ("d")); 145 | TestHelper.assertTrue (null != bt.remove ("e")); 146 | 147 | TestHelper.assertFalse (null != bt.remove ("a")); 148 | TestHelper.assertFalse (null != bt.remove ("b")); 149 | TestHelper.assertFalse (null != bt.remove ("c")); 150 | TestHelper.assertFalse (null != bt.remove ("d")); 151 | TestHelper.assertFalse (null != bt.remove ("e")); 152 | 153 | TestHelper.assertTrue (null == bt.lookup ("a")); 154 | TestHelper.assertTrue (null == bt.lookup ("b")); 155 | TestHelper.assertTrue (null == bt.lookup ("c")); 156 | TestHelper.assertTrue (null == bt.lookup ("d")); 157 | TestHelper.assertTrue (null == bt.lookup ("e")); 158 | } 159 | 160 | private static void removeInts (final TrieMap bt) { 161 | for (int i = 0; i < 128; i++) { 162 | final Integer bigI = Integer.valueOf (i); 163 | TestHelper.assertTrue (null != bt.lookup (bigI)); 164 | TestHelper.assertTrue (null != bt.remove (bigI)); 165 | TestHelper.assertFalse (null != bt.remove (bigI)); 166 | TestHelper.assertTrue (null == bt.lookup (bigI)); 167 | } 168 | } 169 | 170 | private static void removeBytes (final TrieMap bt) { 171 | for (byte i = 0; i < 128 && i >= 0; i++) { 172 | final Byte bigB = Byte.valueOf (i); 173 | TestHelper.assertTrue (null != bt.lookup (bigB)); 174 | TestHelper.assertTrue (null != bt.remove (bigB)); 175 | TestHelper.assertFalse (null != bt.remove (bigB)); 176 | TestHelper.assertTrue (null == bt.lookup (bigB)); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestHashCollisionsRemove.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.Map; 4 | 5 | import org.junit.Test; 6 | 7 | public class TestHashCollisionsRemove { 8 | @Test 9 | public void testHashCollisionsRemove() { 10 | final Map bt = new TrieMap (); 11 | int count = 50000; 12 | for (int j = 0; j < count; j++) { 13 | final Object[] objects = TestMultiThreadMapIterator.getObjects (j); 14 | for (final Object o : objects) { 15 | bt.put (o, o); 16 | } 17 | } 18 | 19 | for (int j = 0; j < count; j++) { 20 | final Object[] objects = TestMultiThreadMapIterator.getObjects (j); 21 | for (final Object o : objects) { 22 | bt.remove (o); 23 | } 24 | } 25 | 26 | TestHelper.assertEquals (0, bt.size ()); 27 | TestHelper.assertTrue (bt.isEmpty ()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestHashCollisionsRemoveIterator.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | 9 | import org.junit.Test; 10 | 11 | public class TestHashCollisionsRemoveIterator { 12 | @Test 13 | public void testHashCollisionsRemoveIterator () { 14 | final Map bt = new TrieMap (); 15 | int count = 50000; 16 | for (int j = 0; j < count; j++) { 17 | bt.put (Integer.valueOf (j), Integer.valueOf (j)); 18 | } 19 | 20 | final Collection list = new ArrayList (); 21 | for (final Iterator> i = bt.entrySet ().iterator (); i.hasNext ();) { 22 | final Entry e = i.next (); 23 | final Object key = e.getKey (); 24 | list.add (key); 25 | i.remove (); 26 | } 27 | 28 | TestHelper.assertEquals (0, bt.size ()); 29 | TestHelper.assertTrue (bt.isEmpty ()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestHelper.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import org.junit.Assert; 4 | 5 | public class TestHelper { 6 | 7 | public static void assertEquals (long expected, long found) { 8 | Assert.assertEquals (expected, found); 9 | } 10 | 11 | public static void assertEquals (int expected, int found) { 12 | Assert.assertEquals (expected, found); 13 | } 14 | 15 | public static void assertEquals (Object expected, Object found) { 16 | Assert.assertEquals (expected, found); 17 | } 18 | 19 | public static void assertTrue (boolean found) { 20 | Assert.assertTrue (found); 21 | } 22 | 23 | public static void assertFalse (boolean found) { 24 | Assert.assertFalse (found); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestInsert.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import org.junit.Test; 4 | 5 | public class TestInsert { 6 | @Test 7 | public void testInsert () { 8 | final TrieMap bt = new TrieMap (); 9 | TestHelper.assertEquals (null, bt.put ("a", "a")); 10 | TestHelper.assertEquals (null, bt.put ("b", "b")); 11 | TestHelper.assertEquals (null, bt.put ("c", "b")); 12 | TestHelper.assertEquals (null, bt.put ("d", "b")); 13 | TestHelper.assertEquals (null, bt.put ("e", "b")); 14 | 15 | for (int i = 0; i < 10000; i++) { 16 | TestHelper.assertEquals (null, bt.put (Integer.valueOf (i), Integer.valueOf (i))); 17 | final Object lookup = bt.lookup (Integer.valueOf (i)); 18 | TestHelper.assertEquals (Integer.valueOf (i), lookup); 19 | } 20 | 21 | bt.toString (); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestInstantiationSpeed.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import org.junit.Test; 4 | 5 | public class TestInstantiationSpeed { 6 | private static final int COUNT = 100000; 7 | private static final int ITERATIONS = 10; 8 | private static final int WARMUP = 20; 9 | 10 | private static long runIteration() { 11 | final TrieMap[] maps = new TrieMap[COUNT]; 12 | final long start = System.nanoTime(); 13 | 14 | for (int i = 0; i < COUNT; ++i) { 15 | maps[i] = new TrieMap(); 16 | } 17 | 18 | final long stop = System.nanoTime(); 19 | return stop - start; 20 | } 21 | 22 | @Test 23 | public void testInstantiation() { 24 | 25 | for (int i = 0; i < WARMUP; ++i) { 26 | final long time = runIteration(); 27 | System.out.println(String.format("Warmup %s took %sns (%sns)", i, time, time / COUNT)); 28 | } 29 | 30 | long acc = 0; 31 | for (int i = 0; i < ITERATIONS; ++i) { 32 | final long time = runIteration(); 33 | System.out.println(String.format("Iteration %s took %sns (%sns)", i, time, time / COUNT)); 34 | acc += time; 35 | } 36 | 37 | System.out.println("Instantiation cost " + acc / ITERATIONS / COUNT + "ns"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestMapIterator.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.HashSet; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | import java.util.Random; 8 | import java.util.Set; 9 | 10 | import org.junit.Test; 11 | 12 | public class TestMapIterator { 13 | @Test 14 | public void testMapIterator () { 15 | for (int i = 0; i < 60 * 1000; i+= 400 + new Random ().nextInt (400)) { 16 | System.out.println (i); 17 | final Map bt = new TrieMap (); 18 | for (int j = 0; j < i; j++) { 19 | TestHelper.assertEquals (null, bt.put (Integer.valueOf (j), Integer.valueOf (j))); 20 | } 21 | int count = 0; 22 | final Set set = new HashSet (); 23 | for (final Map.Entry e : bt.entrySet ()) { 24 | set.add (e.getKey ()); 25 | count++; 26 | } 27 | for (final Integer j : set) { 28 | TestHelper.assertTrue (bt.containsKey (j)); 29 | } 30 | for (final Integer j : bt.keySet ()) { 31 | TestHelper.assertTrue (set.contains (j)); 32 | } 33 | 34 | TestHelper.assertEquals (i, count); 35 | TestHelper.assertEquals (i, bt.size ()); 36 | 37 | for (final Iterator> iter = bt.entrySet ().iterator (); iter.hasNext ();) { 38 | final Entry e = iter.next (); 39 | TestHelper.assertTrue (e.getValue () == bt.get (e.getKey ())); 40 | e.setValue (e.getValue () + 1); 41 | TestHelper.assertTrue (e.getValue () == e.getKey () + 1); 42 | TestHelper.assertTrue (e.getValue () == bt.get (e.getKey ())); 43 | e.setValue (e.getValue () - 1); 44 | } 45 | 46 | for (final Iterator iter = bt.keySet ().iterator (); iter.hasNext ();) { 47 | final Integer k = iter.next (); 48 | TestHelper.assertTrue (bt.containsKey (k)); 49 | iter.remove (); 50 | TestHelper.assertFalse (bt.containsKey (k)); 51 | } 52 | 53 | TestHelper.assertEquals (0, bt.size ()); 54 | TestHelper.assertTrue (bt.isEmpty ()); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestMultiThreadAddDelete.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.junit.Test; 9 | 10 | public class TestMultiThreadAddDelete { 11 | private static final int RETRIES = 1; 12 | private static final int N_THREADS = 7; 13 | private static final int COUNT = 50 * 1000; 14 | 15 | @Test 16 | public void testMultiThreadAddDelete () { 17 | for (int j = 0; j < RETRIES; j++) { 18 | final Map bt = new TrieMap (); 19 | 20 | { 21 | final ExecutorService es = Executors.newFixedThreadPool (N_THREADS); 22 | for (int i = 0; i < N_THREADS; i++) { 23 | final int threadNo = i; 24 | es.execute (new Runnable () { 25 | @Override 26 | public void run () { 27 | for (int j = 0; j < COUNT; j++) { 28 | if (j % N_THREADS == threadNo) { 29 | bt.put (Integer.valueOf (j), Integer.valueOf (j)); 30 | } 31 | } 32 | } 33 | }); 34 | } 35 | es.shutdown (); 36 | try { 37 | es.awaitTermination (3600L, TimeUnit.SECONDS); 38 | } catch (final InterruptedException e) { 39 | e.printStackTrace (); 40 | } 41 | } 42 | 43 | TestHelper.assertEquals (COUNT, bt.size ()); 44 | TestHelper.assertFalse (bt.isEmpty ()); 45 | 46 | { 47 | final ExecutorService es = Executors.newFixedThreadPool (N_THREADS); 48 | for (int i = 0; i < N_THREADS; i++) { 49 | final int threadNo = i; 50 | es.execute (new Runnable () { 51 | @Override 52 | public void run () { 53 | for (int j = 0; j < COUNT; j++) { 54 | if (j % N_THREADS == threadNo) { 55 | bt.remove (Integer.valueOf (j)); 56 | } 57 | } 58 | } 59 | }); 60 | } 61 | es.shutdown (); 62 | try { 63 | es.awaitTermination (3600L, TimeUnit.SECONDS); 64 | } catch (final InterruptedException e) { 65 | e.printStackTrace (); 66 | } 67 | } 68 | 69 | 70 | TestHelper.assertEquals (0, bt.size ()); 71 | TestHelper.assertTrue (bt.isEmpty ()); 72 | 73 | { 74 | final ExecutorService es = Executors.newFixedThreadPool (N_THREADS); 75 | for (int i = 0; i < N_THREADS; i++) { 76 | final int threadNo = i; 77 | es.execute (new Runnable () { 78 | @Override 79 | public void run () { 80 | for (int j = 0; j < COUNT; j++) { 81 | if (j % N_THREADS == threadNo) { 82 | try { 83 | bt.put (Integer.valueOf (j), Integer.valueOf (j)); 84 | if (!bt.containsKey (Integer.valueOf (j))) { 85 | System.out.println (j); 86 | } 87 | bt.remove (Integer.valueOf (j)); 88 | if (bt.containsKey (Integer.valueOf (j))) { 89 | System.out.println (-j); 90 | } 91 | } catch (Throwable t) { 92 | t.printStackTrace (); 93 | } 94 | } 95 | } 96 | } 97 | }); 98 | } 99 | es.shutdown (); 100 | try { 101 | es.awaitTermination (3600L, TimeUnit.SECONDS); 102 | } catch (final InterruptedException e) { 103 | e.printStackTrace (); 104 | } 105 | } 106 | 107 | TestHelper.assertEquals (0, bt.size ()); 108 | if (!bt.isEmpty ()) { 109 | System.out.println (); 110 | } 111 | TestHelper.assertTrue (bt.isEmpty ()); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestMultiThreadInserts.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.junit.Test; 8 | 9 | public class TestMultiThreadInserts { 10 | @Test 11 | public void testMultiThreadInserts () { 12 | final int nThreads = 2; 13 | final ExecutorService es = Executors.newFixedThreadPool (nThreads); 14 | final TrieMap bt = new TrieMap (); 15 | for (int i = 0; i < nThreads; i++) { 16 | final int threadNo = i; 17 | es.execute (new Runnable () { 18 | @Override 19 | public void run () { 20 | for (int j = 0; j < 500 * 1000; j++) { 21 | if (j % nThreads == threadNo) { 22 | bt.put (Integer.valueOf (j), Integer.valueOf (j)); 23 | } 24 | } 25 | } 26 | }); 27 | } 28 | 29 | es.shutdown (); 30 | try { 31 | es.awaitTermination (3600L, TimeUnit.SECONDS); 32 | } catch (final InterruptedException e) { 33 | e.printStackTrace (); 34 | } 35 | 36 | for (int j = 0; j < 500 * 1000; j++) { 37 | final Object lookup = bt.lookup (Integer.valueOf (j)); 38 | TestHelper.assertEquals (Integer.valueOf (j), lookup); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestMultiThreadMapIterator.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.LinkedList; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import org.junit.Test; 14 | 15 | public class TestMultiThreadMapIterator { 16 | private static final int NTHREADS = 7; 17 | 18 | @Test 19 | public void testMultiThreadMapIterator () { 20 | final Map bt = new TrieMap (); 21 | for (int j = 0; j < 50 * 1000; j++) { 22 | final Object[] objects = getObjects (j); 23 | for (final Object o : objects) { 24 | bt.put (o, o); 25 | } 26 | } 27 | 28 | System.out.println ("Size of initialized map is " + bt.size ()); 29 | int count = 0; 30 | { 31 | final ExecutorService es = Executors.newFixedThreadPool (NTHREADS); 32 | for (int i = 0; i < NTHREADS; i++) { 33 | final int threadNo = i; 34 | es.execute (new Runnable () { 35 | @Override 36 | public void run () { 37 | for (final Iterator> i = bt.entrySet ().iterator (); i.hasNext ();) { 38 | final Entry e = i.next (); 39 | if (accepts (threadNo, NTHREADS, e.getKey ())) { 40 | String newValue = "TEST:" + threadNo; 41 | e.setValue (newValue); 42 | } 43 | } 44 | } 45 | }); 46 | } 47 | 48 | es.shutdown (); 49 | try { 50 | es.awaitTermination (3600L, TimeUnit.SECONDS); 51 | } catch (final InterruptedException e) { 52 | e.printStackTrace (); 53 | } 54 | } 55 | 56 | count = 0; 57 | for (final Map.Entry kv : bt.entrySet ()) { 58 | Object value = kv.getValue (); 59 | TestHelper.assertTrue (value instanceof String); 60 | count++; 61 | } 62 | TestHelper.assertEquals (50000 + 2000 + 1000 + 100, count); 63 | 64 | final ConcurrentHashMap removed = new ConcurrentHashMap (); 65 | 66 | { 67 | final ExecutorService es = Executors.newFixedThreadPool (NTHREADS); 68 | for (int i = 0; i < NTHREADS; i++) { 69 | final int threadNo = i; 70 | es.execute (new Runnable () { 71 | @Override 72 | public void run () { 73 | for (final Iterator> i = bt.entrySet ().iterator (); i.hasNext ();) { 74 | final Entry e = i.next (); 75 | Object key = e.getKey (); 76 | if (accepts (threadNo, NTHREADS, key)) { 77 | if (null == bt.get (key)) { 78 | System.out.println (key); 79 | } 80 | i.remove (); 81 | if (null != bt.get (key)) { 82 | System.out.println (key); 83 | } 84 | removed.put (key, key); 85 | } 86 | } 87 | } 88 | }); 89 | } 90 | 91 | es.shutdown (); 92 | try { 93 | es.awaitTermination (3600L, TimeUnit.SECONDS); 94 | } catch (final InterruptedException e) { 95 | e.printStackTrace (); 96 | } 97 | } 98 | 99 | count = 0; 100 | for (final Object value : bt.keySet ()) { 101 | value.toString (); 102 | count++; 103 | } 104 | for (final Object o : bt.keySet ()) { 105 | if (!removed.contains (bt.get (o))) { 106 | System.out.println ("Not removed: " + o); 107 | } 108 | } 109 | TestHelper.assertEquals (0, count); 110 | TestHelper.assertEquals (0, bt.size ()); 111 | TestHelper.assertTrue (bt.isEmpty ()); 112 | } 113 | 114 | protected static boolean accepts (final int threadNo, final int nThreads, final Object key) { 115 | int val = getKeyValue (key); 116 | if(val>=0) 117 | return val % nThreads == threadNo; 118 | else 119 | return false; 120 | } 121 | 122 | private static int getKeyValue (final Object key) { 123 | int val = 0; 124 | if (key instanceof Integer) { 125 | val = ((Integer) key).intValue (); 126 | } 127 | else if (key instanceof Character) { 128 | val = Math.abs (Character.getNumericValue ((Character) key) + 1); 129 | } 130 | else if (key instanceof Short) { 131 | val = ((Short) key).intValue () + 2; 132 | } 133 | else if (key instanceof Byte) { 134 | val = ((Byte) key).intValue () + 3; 135 | } else 136 | return -1; 137 | return val; 138 | } 139 | 140 | static Object[] getObjects (final int j) { 141 | final Collection results = new LinkedList (); 142 | results.add (Integer.valueOf (j)); 143 | if (j < 2000) { 144 | results.add (Character.valueOf ((char) j)); 145 | } 146 | if (j < 1000) { 147 | results.add (Short.valueOf ((short) j)); 148 | } 149 | if (j < 100) { 150 | results.add (Byte.valueOf ((byte) j)); 151 | } 152 | 153 | return results.toArray (); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestReadOnlyAndUpdatableIterators.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map.Entry; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | /*** 10 | * 11 | * Test that read-only iterators do not allow for any updates. 12 | * Test that non read-only iterators allow for updates. 13 | * 14 | */ 15 | public class TestReadOnlyAndUpdatableIterators { 16 | TrieMap bt; 17 | private static final int MAP_SIZE = 200; 18 | 19 | @Before 20 | public void setUp() { 21 | bt = new TrieMap (); 22 | for (int j = 0; j < MAP_SIZE; j++) { 23 | TestHelper.assertEquals (null, bt.put (Integer.valueOf (j), Integer.valueOf (j))); 24 | } 25 | } 26 | 27 | @Test 28 | public void testReadOnlyIterator () { 29 | Iterator> it = bt.readOnlyIterator (); 30 | try { 31 | it.next().setValue (0); 32 | // It should have generated an exception, because it is a read-only iterator 33 | TestHelper.assertFalse (true); 34 | } catch (Exception e) { 35 | 36 | } 37 | try { 38 | it.remove (); 39 | // It should have generated an exception, because it is a read-only iterator 40 | TestHelper.assertFalse (true); 41 | } catch (Exception e) { 42 | 43 | } 44 | } 45 | 46 | @Test 47 | public void testReadOnlySnapshotReadOnlyIterator () { 48 | TrieMap roSnapshot = bt.readOnlySnapshot (); 49 | Iterator> it = roSnapshot.readOnlyIterator (); 50 | try { 51 | it.next().setValue (0); 52 | // It should have generated an exception, because it is a read-only iterator 53 | TestHelper.assertFalse (true); 54 | } catch (Exception e) { 55 | 56 | } 57 | try { 58 | it.remove (); 59 | // It should have generated an exception, because it is a read-only iterator 60 | TestHelper.assertFalse (true); 61 | } catch (Exception e) { 62 | 63 | } 64 | } 65 | 66 | @Test 67 | public void testReadOnlySnapshotIterator () { 68 | TrieMap roSnapshot = bt.readOnlySnapshot (); 69 | Iterator> it = roSnapshot.iterator (); 70 | try { 71 | it.next().setValue (0); 72 | // It should have generated an exception, because it is a read-only iterator 73 | TestHelper.assertFalse (true); 74 | } catch (Exception e) { 75 | 76 | } 77 | try { 78 | it.remove (); 79 | // It should have generated an exception, because it is a read-only iterator 80 | TestHelper.assertFalse (true); 81 | } catch (Exception e) { 82 | 83 | } 84 | } 85 | 86 | @Test 87 | public void testIterator () { 88 | Iterator> it = bt.iterator (); 89 | try { 90 | it.next().setValue (0); 91 | } catch (Exception e) { 92 | // It should not have generated an exception, because it is a non read-only iterator 93 | TestHelper.assertFalse (true); 94 | } 95 | 96 | try { 97 | it.remove (); 98 | } catch (Exception e) { 99 | // It should not have generated an exception, because it is a non read-only iterator 100 | TestHelper.assertFalse (true); 101 | } 102 | 103 | // All changes are done on the original map 104 | TestHelper.assertEquals (MAP_SIZE - 1, bt.size ()); 105 | } 106 | 107 | @Test 108 | public void testSnapshotIterator () { 109 | TrieMap snapshot = bt.snapshot (); 110 | Iterator> it = snapshot.iterator (); 111 | try { 112 | it.next().setValue (0); 113 | } catch (Exception e) { 114 | // It should not have generated an exception, because it is a non read-only iterator 115 | TestHelper.assertFalse (true); 116 | } 117 | try { 118 | it.remove (); 119 | } catch (Exception e) { 120 | // It should not have generated an exception, because it is a non read-only iterator 121 | TestHelper.assertFalse (true); 122 | } 123 | 124 | // All changes are done on the snapshot, not on the original map 125 | // Map size should remain unchanged 126 | TestHelper.assertEquals (MAP_SIZE, bt.size ()); 127 | // snapshot size was changed 128 | TestHelper.assertEquals (MAP_SIZE-1, snapshot.size ()); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/com/romix/scala/collection/concurrent/TestSerialization.java: -------------------------------------------------------------------------------- 1 | package com.romix.scala.collection.concurrent; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.ObjectInputStream; 7 | import java.io.ObjectOutputStream; 8 | 9 | import junit.framework.Assert; 10 | 11 | import org.junit.Test; 12 | 13 | public class TestSerialization { 14 | @Test 15 | public void testSerialization() throws IOException, ClassNotFoundException { 16 | TrieMap map = new TrieMap(); 17 | 18 | map.put("dude-0", "tom"); 19 | map.put("dude-1", "john"); 20 | map.put("dude-3", "ravi"); 21 | map.put("dude-4", "alex"); 22 | 23 | TrieMap expected = map.readOnlySnapshot(); 24 | 25 | final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 26 | final ObjectOutputStream oos = new ObjectOutputStream(bos); 27 | oos.writeObject(expected); 28 | oos.close(); 29 | 30 | final byte[] bytes = bos.toByteArray(); 31 | final ByteArrayInputStream bis = new ByteArrayInputStream(bytes); 32 | final ObjectInputStream ois = new ObjectInputStream(bis); 33 | 34 | @SuppressWarnings("unchecked") 35 | final TrieMap actual = (TrieMap) ois.readObject(); 36 | ois.close(); 37 | 38 | Assert.assertEquals(expected, actual); 39 | } 40 | } 41 | --------------------------------------------------------------------------------