├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── release ├── src ├── main │ └── java │ │ └── net │ │ └── intelie │ │ └── tinymap │ │ ├── CacheAdapter.java │ │ ├── CacheableBuilder.java │ │ ├── ObjectCache.java │ │ ├── TinyList.java │ │ ├── TinyListBuilder.java │ │ ├── TinyMap.java │ │ ├── TinyMapBuilder.java │ │ ├── TinySet.java │ │ ├── TinySetBuilder.java │ │ ├── base │ │ ├── IndexedCollection.java │ │ ├── IndexedCollectionBase.java │ │ ├── IndexedList.java │ │ ├── IndexedListBase.java │ │ ├── IndexedMap.java │ │ ├── IndexedMapBase.java │ │ ├── IndexedSet.java │ │ └── IndexedSetBase.java │ │ ├── json │ │ ├── FastDouble.java │ │ ├── JsonToken.java │ │ ├── TinyJsonDecoder.java │ │ └── TinyJsonReader.java │ │ └── util │ │ ├── DefaultDoubleCache.java │ │ ├── DefaultObjectCache.java │ │ ├── ObjectOptimizer.java │ │ ├── Preconditions.java │ │ ├── StringCacheAdapter.java │ │ └── TinyMapGenerated.java └── test │ ├── java │ └── net │ │ └── intelie │ │ └── tinymap │ │ ├── ObjectCacheTest.java │ │ ├── Playground.java │ │ ├── StringCacheTest.java │ │ ├── TinyListBuilderTest.java │ │ ├── TinyListTest.java │ │ ├── TinyMapBuilderAdapterTest.java │ │ ├── TinyMapBuilderTest.java │ │ ├── TinyMapTest.java │ │ ├── TinySetBuilderAdapterTest.java │ │ ├── TinySetBuilderTest.java │ │ ├── TinySetTest.java │ │ ├── benchmark │ │ ├── MapAccessTime.java │ │ ├── MapSize.java │ │ └── MapSizeReal.java │ │ ├── json │ │ ├── FastDoubleTest.java │ │ ├── TinyJsonDecoderTest.java │ │ └── TinyJsonReaderTest.java │ │ ├── lazytests │ │ ├── LazyTester.java │ │ ├── ListLazyTests.java │ │ └── SerialVersionUIDTest.java │ │ ├── support │ │ ├── ImmutableOptimizer.java │ │ ├── JavaOptimizer.java │ │ ├── ListAsserts.java │ │ ├── MapAsserts.java │ │ ├── SerializationHelper.java │ │ ├── SetAsserts.java │ │ └── TestSizeUtils.java │ │ └── util │ │ ├── DefaultDoubleCacheTest.java │ │ ├── GenerateClasses.java │ │ ├── ObjectOptimizerTest.java │ │ ├── PreconditionsTest.java │ │ ├── StringCacheAdapterTest.java │ │ └── SuppressForbidden.java │ └── resources │ └── templates │ └── TinyMapGenerated.template └── test /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | *.log 5 | *.pyc 6 | *.versionsBackup 7 | .ideas 8 | .idea 9 | .DS_Store 10 | /target 11 | /*/target 12 | .jhw-cache 13 | ldapdata 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016 Intelie 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyMap 2 | 3 | Memory-Efficient Immutable HashMap 4 | 5 | This library provides a straightforward open-addressing ordered hash table implementation. That implementation, along 6 | with an aggressive object reuse strategy (also provided here) can lead to incredibly low memory usage for semi-structured 7 | hashmaps. 8 | 9 | That is very useful to represent small immutable events. 10 | 11 | The main advantage in TinyMap is that it can reuse not only keys and values, but also entire maps, keysets, and lists. This can lead to a representation up to 97% smaller than a typical HashMap. 12 | 13 | Below you can compare the memory requirements of loading 50,000 events as Gson's LinkedTreeMap, converting them to LinkedHashMap, guava's ImmutableMap, and TinyMap, both with and without objects reuse. 14 | 15 | ![](https://docs.google.com/spreadsheets/d/e/2PACX-1vQGaL2vuiOAxMH8809j4HiYPfK1uxSYpNIYNQAl-_eGbvhBC2BJR2bE_-sbAhBkq-xFpTzTa3hcUZ9i/pubchart?oid=1134324197&format=image) 16 | 17 | ### Are there any downsides? 18 | 19 | Yes. We save some memory by not storing Entry objects, only arrays of keys and values. 20 | So, iterating through the map's `entrySet` performs more allocations than a typical Map implementation. 21 | 22 | This can be mitigated by using `Map#forEach` or using `getKeyAt(index)` and `getValueAt(index)` methods in a for loop. 23 | 24 | ## Usage 25 | 26 | TinyMap is available through Maven Central repository, just add the following 27 | dependency to your `pom.xml` file: 28 | 29 | ```xml 30 | 31 | net.intelie.tinymap 32 | tinymap 33 | 0.7 34 | 35 | ``` 36 | 37 | ### Building a new map (without reuse) 38 | 39 | ```java 40 | TinyMapBuilder builder = TinyMap.builder(); 41 | builder.put("key1", "value1"); 42 | builder.put("key2", "value2"); 43 | builder.put("key3", 333); 44 | 45 | TinyMap map = builder.build(); 46 | ``` 47 | 48 | This map uses exactly 384 bytes in Java 8, considering all its object tree. This is already better than 49 | Guava's ImmutableMap (488 bytes) and LinkedHashMap (512 bytes). 50 | 51 | Also note that `TinyMapBuilder` is also a `Map` and can be used as such. 52 | 53 | ### Optimizing existing map (with reuse) 54 | 55 | TinyMap can leverage aggressive object reuse to avoid representing same maps, keySets, or even Strings multiple times. 56 | 57 | ```java 58 | ArrayList list = new ArrayList<>(); 59 | 60 | for (int i = 0; i < 1000; i++) { 61 | LinkedHashMap map = new LinkedHashMap<>(); 62 | map.put("key1", "value" + i); 63 | map.put("key2", i); 64 | map.put("key3", (double)(i/100)); 65 | list.add(map); 66 | } 67 | 68 | ObjectOptimizer optimizer = new ObjectOptimizer(new ObjectCache()); 69 | TinyList tinyList = optimizer.optimizeList(list); 70 | ``` 71 | 72 | The optimized version uses almost 80% less memory than the pure Java version (105.95 KB vs 504.86 KB). 73 | 74 | ### Parsing JSON 75 | 76 | TinyMap includes a JSON parser that creates TinyMap/TinyList objects directly, without need for subsequent optimization. 77 | 78 | Reading a JSON is as easy as passing a Reader to TinyJsonDecoder: 79 | 80 | ```java 81 | ObjectCache cache = new ObjectCache(); 82 | try (TinyJsonDecoder decoder = new TinyJsonDecoder(cache, new StringReader("{abc:123}"))) { 83 | System.out.println(decoder.nextObject()); 84 | } 85 | ``` 86 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.intelie.tinymap 8 | tinymap 9 | 0.10-SNAPSHOT 10 | 11 | TinyMap 12 | Memory-Efficient Immutable HashMaps 13 | http://www.intelie.com/tinymap 14 | 15 | 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 20 | 21 | 22 | 23 | The Apache License, Version 2.0 24 | http://www.apache.org/licenses/LICENSE-2.0.txt 25 | 26 | 27 | 28 | 29 | 30 | Juan Lopes 31 | juan.lopes@intelie.com.br 32 | Intelie 33 | http://www.intelie.com 34 | 35 | 36 | 37 | 38 | scm:git:git://github.com/intelie/tinymap.git 39 | scm:git:ssh://github.com:intelie/tinymap.git 40 | http://github.com/intelie/tinymap/tree/master 41 | 42 | 43 | 44 | 45 | junit 46 | junit 47 | 4.13.2 48 | test 49 | 50 | 51 | 52 | org.assertj 53 | assertj-core 54 | 3.25.3 55 | test 56 | 57 | 58 | 59 | net.intelie.introspective 60 | introspective 61 | 0.12 62 | test 63 | 64 | 65 | 66 | com.google.code.gson 67 | gson 68 | 2.10.1 69 | test 70 | 71 | 72 | 73 | com.google.guava 74 | guava 75 | 33.1.0-jre 76 | test 77 | 78 | 79 | 80 | com.google.guava 81 | guava-testlib 82 | 33.1.0-jre 83 | test 84 | 85 | 86 | 87 | org.mockito 88 | mockito-core 89 | 4.11.0 90 | test 91 | 92 | 93 | 94 | org.freemarker 95 | freemarker 96 | 2.3.32 97 | test 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-enforcer-plugin 106 | 3.4.1 107 | 108 | 109 | enforce-maven 110 | 111 | enforce 112 | 113 | 114 | 115 | 116 | 3.2.5 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | de.thetaphi 126 | forbiddenapis 127 | 3.6 128 | 129 | 130 | jdk-unsafe 131 | jdk-deprecated 132 | jdk-internal 133 | jdk-non-portable 134 | jdk-system-out 135 | jdk-reflection 136 | 137 | 138 | net.intelie.tinymap.util.SuppressForbidden 139 | 140 | 141 | 142 | 143 | 144 | check 145 | testCheck 146 | 147 | 148 | 149 | 150 | 151 | 152 | org.apache.maven.plugins 153 | maven-compiler-plugin 154 | 3.12.1 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-surefire-plugin 160 | 3.2.5 161 | 162 | false 163 | 164 | 165 | 166 | 167 | maven-clean-plugin 168 | 3.3.2 169 | 170 | 171 | 172 | maven-deploy-plugin 173 | 3.1.1 174 | 175 | 176 | 177 | maven-install-plugin 178 | 3.1.1 179 | 180 | 181 | 182 | maven-jar-plugin 183 | 3.3.0 184 | 185 | 186 | 187 | maven-resources-plugin 188 | 3.3.1 189 | 190 | 191 | 192 | maven-site-plugin 193 | 4.0.0-M13 194 | 195 | 196 | 197 | 198 | 199 | 200 | add-opens 201 | 202 | (1.8,) 203 | 204 | 205 | 206 | 207 | org.apache.maven.plugins 208 | maven-surefire-plugin 209 | 3.2.5 210 | 211 | false 212 | --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | release 221 | 222 | 223 | 224 | ossrh 225 | https://oss.sonatype.org/content/repositories/snapshots 226 | 227 | 228 | 229 | 230 | 231 | 232 | org.apache.maven.plugins 233 | maven-source-plugin 234 | 3.3.0 235 | 236 | 237 | attach-sources 238 | 239 | jar-no-fork 240 | 241 | 242 | 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-javadoc-plugin 248 | 3.6.3 249 | 250 | none 251 | 252 | 253 | 254 | attach-javadocs 255 | 256 | jar 257 | 258 | 259 | 260 | 261 | 262 | 263 | org.apache.maven.plugins 264 | maven-gpg-plugin 265 | 3.1.0 266 | 267 | 268 | sign-artifacts 269 | verify 270 | 271 | sign 272 | 273 | 274 | 275 | 276 | 277 | 278 | org.sonatype.plugins 279 | nexus-staging-maven-plugin 280 | 1.6.13 281 | true 282 | 283 | ossrh 284 | https://oss.sonatype.org/ 285 | true 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ $# -lt 2 ]; then 6 | echo "$0 " 7 | echo "$0 0.1 0.2" 8 | exit 0 9 | fi 10 | 11 | mvn versions:set -DnewVersion=$1 12 | mvn -P release clean deploy -Dgpg.executable=gpg2 13 | 14 | #prune old tags 15 | git fetch origin --prune +refs/tags/*:refs/tags/* 16 | 17 | git commit -am "Release version $1" 18 | git tag $1 19 | mvn versions:set -DnewVersion=$2-SNAPSHOT 20 | git commit -am "Developing version: $2" 21 | 22 | git push --tags 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/CacheAdapter.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | public interface CacheAdapter { 4 | int contentHashCode(B builder); 5 | 6 | T contentEquals(B builder, Object cached); 7 | 8 | T build(B builder, ObjectCache cache); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/CacheableBuilder.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | public interface CacheableBuilder { 4 | T build(); 5 | 6 | CacheAdapter adapter(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/ObjectCache.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | public interface ObjectCache { 4 | Double get(double value); 5 | 6 | String get(CharSequence cs); 7 | 8 | , T> T get(B builder); 9 | 10 | T get(B builder, CacheAdapter adapter); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/TinyList.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedListBase; 4 | 5 | import java.io.Serializable; 6 | 7 | public class TinyList extends IndexedListBase implements Serializable, IndexedListBase.Immutable { 8 | private static final long serialVersionUID = 1L; 9 | 10 | private final Object[] values; 11 | 12 | public TinyList(Object[] values) { 13 | this.values = values; 14 | } 15 | 16 | public static TinyListBuilder builder() { 17 | return new TinyListBuilder<>(); 18 | } 19 | 20 | @Override 21 | public int size() { 22 | return values.length; 23 | } 24 | 25 | @SuppressWarnings("unchecked") 26 | @Override 27 | public T getEntryAt(int index) { 28 | return (T) values[index]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/TinyListBuilder.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedListBase; 4 | import net.intelie.tinymap.util.Preconditions; 5 | 6 | import java.io.Serializable; 7 | import java.util.Arrays; 8 | 9 | public class TinyListBuilder extends IndexedListBase implements CacheableBuilder, TinyList>, Serializable { 10 | private static final long serialVersionUID = 1L; 11 | 12 | private static final Adapter adapter = new Adapter<>(); 13 | private Object[] values = new Object[4]; 14 | private int size = 0; 15 | 16 | @Override 17 | public int addOrGetIndex(T obj) { 18 | int index = size++; 19 | if (index == values.length) 20 | values = Arrays.copyOf(values, values.length * 2); 21 | values[index] = obj; 22 | return ~index; 23 | } 24 | 25 | @Override 26 | public T set(int index, T obj) { 27 | T old = getEntryAt(index); 28 | values[index] = obj; 29 | return old; 30 | } 31 | 32 | @SuppressWarnings("unchecked") 33 | @Override 34 | public T getEntryAt(int index) { 35 | Preconditions.checkElementIndex(index, size); 36 | return (T) values[index]; 37 | } 38 | 39 | public int size() { 40 | return size; 41 | } 42 | 43 | @Override 44 | public T removeLast() { 45 | T old = set(size - 1, null); 46 | size--; 47 | return old; 48 | } 49 | 50 | @Override 51 | public TinyList build() { 52 | return new TinyList<>(Arrays.copyOf(values, size)); 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | @Override 57 | public Adapter adapter() { 58 | return (Adapter) adapter; 59 | } 60 | 61 | public void clear() { 62 | Arrays.fill(values, 0, size, null); 63 | size = 0; 64 | } 65 | 66 | public TinyList buildAndClear() { 67 | TinyList answer = build(); 68 | clear(); 69 | return answer; 70 | } 71 | 72 | public static class Adapter implements CacheAdapter, TinyList> { 73 | @Override 74 | public int contentHashCode(TinyListBuilder builder) { 75 | int hash = 1; 76 | for (int i = 0; i < builder.size; i++) 77 | if (!builder.isRemoved(i)) 78 | hash = 31 * hash + System.identityHashCode(builder.values[i]); 79 | return hash; 80 | } 81 | 82 | @SuppressWarnings("unchecked") 83 | @Override 84 | public TinyList contentEquals(TinyListBuilder builder, Object cached) { 85 | if (!(cached instanceof TinyList) || builder.size != ((TinyList) cached).size()) 86 | return null; 87 | TinyList list = (TinyList) cached; 88 | for (int i = 0; i < builder.size; i++) { 89 | if (builder.values[i] != list.get(i)) 90 | return null; 91 | } 92 | return list; 93 | } 94 | 95 | @Override 96 | public TinyList build(TinyListBuilder builder, ObjectCache cache) { 97 | return builder.build(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/TinyMap.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedMapBase; 4 | import net.intelie.tinymap.util.TinyMapGenerated; 5 | 6 | import java.io.Serializable; 7 | 8 | public abstract class TinyMap extends IndexedMapBase implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | private final TinySet keys; 12 | 13 | protected TinyMap(TinySet keys) { 14 | this.keys = keys; 15 | } 16 | 17 | public static TinyMap createUnsafe(TinySet keys, Object[] values) { 18 | //return new TinyMapGenerated.SizeAny<>(keys, values); 19 | return TinyMapGenerated.createUnsafe(keys, values); 20 | } 21 | 22 | public static TinyMapBuilder builder() { 23 | return new TinyMapBuilder<>(); 24 | } 25 | 26 | public boolean sharesKeysWith(TinyMap other) { 27 | return keys == other.keys; 28 | } 29 | 30 | public long debugCollisions(V key) { 31 | return keys.debugCollisions(key); 32 | } 33 | 34 | @Override 35 | public K getKeyAt(int index) { 36 | return keys.getEntryAt(index); 37 | } 38 | 39 | @Override 40 | public int size() { 41 | return keys.size(); 42 | } 43 | 44 | @Override 45 | public int getIndex(Object key) { 46 | return keys.getIndex(key); 47 | } 48 | 49 | @Override 50 | public TinySet keySet() { 51 | return keys; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/TinyMapBuilder.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedMapBase; 4 | import net.intelie.tinymap.util.Preconditions; 5 | 6 | import java.io.Serializable; 7 | import java.util.Arrays; 8 | 9 | public class TinyMapBuilder extends IndexedMapBase implements CacheableBuilder, TinyMap>, Serializable { 10 | private static final long serialVersionUID = 1L; 11 | 12 | public static final Object TOMBSTONE = new Serializable() { 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Override 16 | public String toString() { 17 | return "TOMBSTONE"; 18 | } 19 | }; 20 | private static final Adapter adapter = new Adapter<>(); 21 | 22 | private final TinySetBuilder keys; 23 | private Object[] values; 24 | 25 | public TinyMapBuilder() { 26 | this(16); 27 | } 28 | 29 | public TinyMapBuilder(int expectedSize) { 30 | values = new Object[expectedSize]; 31 | keys = new TinySetBuilder(expectedSize) { 32 | private static final long serialVersionUID = 1L; 33 | 34 | @Override 35 | public void compact() { 36 | if (size() == rawSize()) return; 37 | int index = 0; 38 | int rawSize = rawSize(); 39 | for (int i = 0; i < rawSize; i++) { 40 | if (values[i] == TOMBSTONE) continue; 41 | values[index++] = values[i]; 42 | } 43 | Arrays.fill(values, index, rawSize, null); 44 | super.compact(); 45 | } 46 | }; 47 | } 48 | 49 | public void compact() { 50 | keys.compact(); 51 | } 52 | 53 | public V put(K key, V value) { 54 | int index = keys.addOrGetIndex(key); 55 | if (index >= 0) 56 | return setValueAt(index, value); 57 | 58 | index = ~index; 59 | if (index >= values.length) 60 | values = Arrays.copyOf(values, values.length + (values.length >> 1)); 61 | 62 | values[index] = value; 63 | return null; 64 | } 65 | 66 | @Override 67 | public int getIndex(Object key) { 68 | return keys.getIndex(key); 69 | } 70 | 71 | @Override 72 | public K getKeyAt(int index) { 73 | return keys.getEntryAt(index); 74 | } 75 | 76 | @SuppressWarnings("unchecked") 77 | @Override 78 | public V getValueAt(int index) { 79 | Preconditions.checkElementIndex(index, rawSize()); 80 | return (V) values[index]; 81 | } 82 | 83 | @SuppressWarnings("unchecked") 84 | @Override 85 | public V setValueAt(int index, V value) { 86 | Preconditions.checkElementIndex(index, rawSize()); 87 | Object old = values[index]; 88 | values[index] = value; 89 | return (V) old; 90 | } 91 | 92 | @SuppressWarnings("unchecked") 93 | @Override 94 | public V removeAt(int index) { 95 | keys.removeAt(index); 96 | Object old = values[index]; 97 | values[index] = TOMBSTONE; 98 | return (V) old; 99 | } 100 | 101 | @Override 102 | public boolean isRemoved(int index) { 103 | return keys.isRemoved(index); 104 | } 105 | 106 | @SuppressWarnings("unchecked") 107 | @Override 108 | public Adapter adapter() { 109 | return (Adapter) adapter; 110 | } 111 | 112 | public int size() { 113 | return keys.size(); 114 | } 115 | 116 | @Override 117 | public int rawSize() { 118 | return keys.rawSize(); 119 | } 120 | 121 | @Override 122 | public TinyMap build() { 123 | return buildWithKeys(buildKeys()); 124 | } 125 | 126 | public TinySet buildKeys() { 127 | return this.keys.build(); 128 | } 129 | 130 | public TinyMap buildWithKeys(TinySet keys) { 131 | compact(); 132 | Preconditions.checkArgument(keys.size() == size(), "Must have same size"); 133 | return TinyMap.createUnsafe(keys, Arrays.copyOf(values, keys.size())); 134 | } 135 | 136 | @Override 137 | public void clear() { 138 | Arrays.fill(values, 0, keys.rawSize(), null); 139 | keys.clear(); 140 | } 141 | 142 | public static class Adapter implements CacheAdapter, TinyMap> { 143 | @Override 144 | public int contentHashCode(TinyMapBuilder builder) { 145 | int hash = 1; 146 | for (int i = 0; i < builder.rawSize(); i++) { 147 | if (builder.isRemoved(i)) continue; 148 | hash = (hash * 31) + System.identityHashCode(builder.getKeyAt(i)); 149 | hash = (hash * 31) + System.identityHashCode(builder.getValueAt(i)); 150 | } 151 | return hash; 152 | } 153 | 154 | @SuppressWarnings("unchecked") 155 | @Override 156 | public TinyMap contentEquals(TinyMapBuilder builder, Object cached) { 157 | if (!(cached instanceof TinyMap) || builder.size() != ((TinyMap) cached).size()) 158 | return null; 159 | TinyMap map = (TinyMap) cached; 160 | int j = 0; 161 | for (int i = 0; i < builder.rawSize(); i++) { 162 | if (builder.isRemoved(i)) continue; 163 | if (builder.getKeyAt(i) != map.getKeyAt(j) || builder.getValueAt(i) != map.getValueAt(j)) 164 | return null; 165 | j++; 166 | } 167 | return (TinyMap) cached; 168 | } 169 | 170 | @Override 171 | public TinyMap build(TinyMapBuilder builder, ObjectCache cache) { 172 | return builder.buildWithKeys(cache.get(builder.keys)); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/TinySet.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedSetBase; 4 | import net.intelie.tinymap.util.Preconditions; 5 | 6 | import java.io.Serializable; 7 | import java.util.Arrays; 8 | import java.util.Objects; 9 | 10 | public abstract class TinySet extends IndexedSetBase implements Serializable { 11 | private static final long serialVersionUID = 1L; 12 | 13 | public static int tableSize(int length) { 14 | return Integer.highestOneBit(length * 2 - 1) * 2; 15 | } 16 | 17 | private static int hash(Object key) { 18 | int h; 19 | return key == null ? 0 : (h = key.hashCode() * 0x85ebca6b) ^ h >>> 16; 20 | } 21 | 22 | public static TinySet createUnsafe(Object[] keys) { 23 | if (keys.length == 0) 24 | return new TinySet.Empty<>(); 25 | else if (keys.length < 0xFF) 26 | return new Small<>(keys); 27 | else if (keys.length < 0xFFFF) 28 | return new Medium<>(keys); 29 | else 30 | return new Large<>(keys); 31 | } 32 | 33 | 34 | public static TinySetBuilder builder() { 35 | return new TinySetBuilder<>(); 36 | } 37 | 38 | public abstract int debugCollisions(Object key); 39 | 40 | public static class Empty extends TinySet implements TinySet.Immutable { 41 | private static final long serialVersionUID = 1L; 42 | 43 | @Override 44 | public int debugCollisions(Object key) { 45 | return 0; 46 | } 47 | 48 | @Override 49 | public int getIndex(Object key) { 50 | return -1; 51 | } 52 | 53 | @Override 54 | public int size() { 55 | return 0; 56 | } 57 | 58 | @Override 59 | public T getEntryAt(int index) { 60 | throw new ArrayIndexOutOfBoundsException(index); 61 | } 62 | } 63 | 64 | private static abstract class ArrayTableSet extends TinySet implements TinySet.Immutable { 65 | private static final long serialVersionUID = 1L; 66 | 67 | protected final Object[] keys; 68 | protected final A table; 69 | 70 | private ArrayTableSet(Object[] keys) { 71 | this.keys = keys; 72 | this.table = newTable(tableSize(keys.length)); 73 | 74 | for (int j = 0; j < keys.length; j++) { 75 | Object key = keys[j]; 76 | int hash = ~getIndex(key); 77 | Preconditions.checkArgument(hash >= 0, "duplicate key: %s", key); 78 | tableSet(table, hash, j); 79 | } 80 | } 81 | 82 | @SuppressWarnings("unchecked") 83 | @Override 84 | public T getEntryAt(int index) { 85 | return (T) keys[index]; 86 | } 87 | 88 | @Override 89 | public int size() { 90 | return keys.length; 91 | } 92 | 93 | protected abstract A newTable(int size); 94 | 95 | protected abstract void tableSet(A table, int index, int value); 96 | } 97 | 98 | public static class Small extends ArrayTableSet { 99 | private static final long serialVersionUID = 1L; 100 | 101 | private Small(Object[] keys) { 102 | super(keys); 103 | } 104 | 105 | @Override 106 | protected byte[] newTable(int size) { 107 | byte[] table = new byte[size]; 108 | Arrays.fill(table, (byte) 0xFF); 109 | return table; 110 | } 111 | 112 | @Override 113 | protected void tableSet(byte[] table, int index, int value) { 114 | table[index] = (byte) value; 115 | } 116 | 117 | @Override 118 | public int debugCollisions(Object key) { 119 | byte[] table = this.table; 120 | int mask = table.length - 1; 121 | int hash = hash(key) & mask; 122 | int collisions = 0; 123 | for (int i = table[hash] & 0xFF; i < 0xFF; i = table[hash = (hash + ++collisions) & mask] & 0xFF) 124 | if (Objects.equals(key, keys[i])) 125 | return collisions; 126 | return collisions; 127 | } 128 | 129 | @Override 130 | public int getIndex(Object key) { 131 | byte[] table = this.table; 132 | int mask = table.length - 1; 133 | int hash = hash(key) & mask; 134 | int collisions = 0; 135 | for (int i = table[hash] & 0xFF; i < 0xFF; i = table[hash = (hash + ++collisions) & mask] & 0xFF) 136 | if (Objects.equals(key, keys[i])) 137 | return i; 138 | return ~hash; 139 | } 140 | } 141 | 142 | public static class Medium extends ArrayTableSet { 143 | private static final long serialVersionUID = 1L; 144 | 145 | private Medium(Object[] keys) { 146 | super(keys); 147 | } 148 | 149 | @Override 150 | protected short[] newTable(int size) { 151 | short[] table = new short[size]; 152 | Arrays.fill(table, (short) 0xFFFF); 153 | return table; 154 | } 155 | 156 | @Override 157 | protected void tableSet(short[] table, int index, int value) { 158 | table[index] = (short) value; 159 | } 160 | 161 | @Override 162 | public int debugCollisions(Object key) { 163 | short[] table = this.table; 164 | int mask = table.length - 1; 165 | int hash = hash(key) & mask; 166 | int collisions = 0; 167 | for (int i = table[hash] & 0xFFFF; i < 0xFFFF; i = table[hash = (hash + ++collisions) & mask] & 0xFFFF) 168 | if (Objects.equals(key, keys[i])) 169 | return collisions; 170 | return collisions; 171 | } 172 | 173 | @Override 174 | public int getIndex(Object key) { 175 | short[] table = this.table; 176 | int mask = table.length - 1; 177 | int hash = hash(key) & mask; 178 | int collisions = 0; 179 | for (int i = table[hash] & 0xFFFF; i < 0xFFFF; i = table[hash = (hash + ++collisions) & mask] & 0xFFFF) 180 | if (Objects.equals(key, keys[i])) 181 | return i; 182 | return ~hash; 183 | } 184 | } 185 | 186 | public static class Large extends ArrayTableSet { 187 | private static final long serialVersionUID = 1L; 188 | 189 | private Large(Object[] keys) { 190 | super(keys); 191 | } 192 | 193 | @Override 194 | protected int[] newTable(int size) { 195 | int[] table = new int[size]; 196 | Arrays.fill(table, -1); 197 | return table; 198 | } 199 | 200 | @Override 201 | protected void tableSet(int[] table, int index, int value) { 202 | table[index] = value; 203 | } 204 | 205 | @Override 206 | public int debugCollisions(Object key) { 207 | int[] table = this.table; 208 | int mask = table.length - 1; 209 | int hash = hash(key) & mask; 210 | int collisions = 0; 211 | for (int i = table[hash]; i >= 0; i = table[hash = (hash + ++collisions) & mask]) 212 | if (Objects.equals(key, keys[i])) 213 | return collisions; 214 | return collisions; 215 | } 216 | 217 | @Override 218 | public int getIndex(Object key) { 219 | int[] table = this.table; 220 | int mask = table.length - 1; 221 | int hash = hash(key) & mask; 222 | int collisions = 0; 223 | for (int i = table[hash]; i >= 0; i = table[hash = (hash + ++collisions) & mask]) 224 | if (Objects.equals(key, keys[i])) 225 | return i; 226 | return ~hash; 227 | } 228 | } 229 | 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/TinySetBuilder.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedCollectionBase; 4 | import net.intelie.tinymap.base.IndexedSetBase; 5 | import net.intelie.tinymap.util.Preconditions; 6 | 7 | import java.io.Serializable; 8 | import java.util.Arrays; 9 | import java.util.Objects; 10 | 11 | public class TinySetBuilder extends IndexedSetBase implements 12 | CacheableBuilder, TinySet>, Serializable, IndexedCollectionBase.NoAdditiveChange { 13 | private static final long serialVersionUID = 1L; 14 | 15 | private static final Object TOMBSTONE = new Serializable() { 16 | private static final long serialVersionUID = 1L; 17 | 18 | @Override 19 | public String toString() { 20 | return "TOMBSTONE"; 21 | } 22 | }; 23 | private static final Adapter adapter = new Adapter<>(); 24 | 25 | private Object[] keys; 26 | //we keep an inverse table to make clear proportional to size even if the builder table has grown way too big 27 | private int[] inverse; 28 | private int[] table; 29 | private int rawSize = 0; 30 | private int size = 0; 31 | 32 | public TinySetBuilder() { 33 | this(16); 34 | } 35 | 36 | public TinySetBuilder(int expectedSize) { 37 | this.keys = new Object[expectedSize]; 38 | this.inverse = new int[expectedSize]; 39 | forceRehash(TinySet.tableSize(expectedSize)); 40 | } 41 | 42 | private static int hash(Object key) { 43 | int h; 44 | return key == null ? 0 : (h = key.hashCode() * 0x85ebca6b) ^ h >>> 16; 45 | } 46 | 47 | private static int[] newTable(int size) { 48 | int[] table = new int[size]; 49 | Arrays.fill(table, -1); 50 | return table; 51 | } 52 | 53 | public void compact() { 54 | if (rawSize == size) return; 55 | softClearTable(); 56 | int index = 0; 57 | for (int i = 0; i < rawSize; i++) { 58 | if (keys[i] == TOMBSTONE) continue; 59 | keys[index] = keys[i]; 60 | 61 | int hash = ~getIndex(keys[index]); 62 | assert hash >= 0; 63 | table[hash] = index; 64 | inverse[index] = hash; 65 | index++; 66 | } 67 | Arrays.fill(keys, index, rawSize, null); 68 | 69 | this.size = index; 70 | this.rawSize = index; 71 | } 72 | 73 | private void forceRehash(int newSize) { 74 | this.table = newTable(newSize); 75 | this.size = 0; 76 | compact(); 77 | } 78 | 79 | private void softClearTable() { 80 | for (int i = 0; i < rawSize; i++) 81 | table[inverse[i]] = -1; 82 | } 83 | 84 | @SuppressWarnings("unchecked") 85 | @Override 86 | public T getEntryAt(int index) { 87 | Preconditions.checkElementIndex(index, rawSize); 88 | return (T) keys[index]; 89 | } 90 | 91 | @Override 92 | public int addOrGetIndex(T key) { 93 | int index = getIndex(key); 94 | if (index >= 0) 95 | return index; 96 | 97 | index = checkOverflow(key, index); 98 | 99 | int hash = ~index; 100 | int newIndex = rawSize++; 101 | keys[newIndex] = key; 102 | table[hash] = newIndex; 103 | inverse[newIndex] = hash; 104 | size++; 105 | 106 | return ~newIndex; 107 | } 108 | 109 | private int checkOverflow(T key, int index) { 110 | if (rawSize == keys.length) { 111 | int newSize = keys.length + (keys.length >> 1); 112 | keys = Arrays.copyOf(keys, newSize); 113 | inverse = Arrays.copyOf(inverse, newSize); 114 | } 115 | if (2 * (rawSize + 1) > table.length) { 116 | forceRehash(table.length * 2); 117 | index = getIndex(key); 118 | } 119 | return index; 120 | } 121 | 122 | @Override 123 | public int getIndex(Object key) { 124 | int collisions = 0; 125 | int mask = table.length - 1; 126 | int hash = hash(key) & mask; 127 | 128 | for (int i = table[hash]; i >= 0; i = table[hash = (hash + ++collisions) & mask]) { 129 | if (Objects.equals(key, keys[i])) 130 | return i; 131 | } 132 | return ~hash; 133 | } 134 | 135 | @Override 136 | public boolean removeAt(int index) { 137 | Preconditions.checkElementIndex(index, rawSize); 138 | keys[index] = TOMBSTONE; 139 | size--; 140 | return false; 141 | } 142 | 143 | @Override 144 | public boolean isRemoved(int index) { 145 | return keys[index] == TOMBSTONE; 146 | } 147 | 148 | @SuppressWarnings("unchecked") 149 | @Override 150 | public Adapter adapter() { 151 | return (Adapter) adapter; 152 | } 153 | 154 | public int size() { 155 | return size; 156 | } 157 | 158 | @Override 159 | public int rawSize() { 160 | return rawSize; 161 | } 162 | 163 | @Override 164 | public TinySet build() { 165 | compact(); 166 | return TinySet.createUnsafe(Arrays.copyOf(keys, size)); 167 | } 168 | 169 | @Override 170 | public void clear() { 171 | Arrays.fill(keys, 0, rawSize, null); 172 | softClearTable(); 173 | size = rawSize = 0; 174 | } 175 | 176 | public static class Adapter implements CacheAdapter, TinySet> { 177 | @Override 178 | public int contentHashCode(TinySetBuilder builder) { 179 | int hash = 1; 180 | for (int i = 0; i < builder.rawSize(); i++) { 181 | if (builder.isRemoved(i)) continue; 182 | hash = (hash * 31) + System.identityHashCode(builder.keys[i]); 183 | } 184 | return hash; 185 | } 186 | 187 | @SuppressWarnings("unchecked") 188 | @Override 189 | public TinySet contentEquals(TinySetBuilder builder, Object cached) { 190 | if (!(cached instanceof TinySet) || builder.size() != ((TinySet) cached).size()) 191 | return null; 192 | TinySet set = (TinySet) cached; 193 | int j = 0; 194 | for (int i = 0; i < builder.rawSize(); i++) { 195 | if (builder.isRemoved(i)) continue; 196 | if (builder.getEntryAt(i) != set.getEntryAt(j)) 197 | return null; 198 | j++; 199 | } 200 | return (TinySet) cached; 201 | } 202 | 203 | @Override 204 | public TinySet build(TinySetBuilder builder, ObjectCache cache) { 205 | return builder.build(); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedCollection.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import java.util.Collection; 4 | import java.util.ListIterator; 5 | 6 | public interface IndexedCollection extends Collection { 7 | int addOrGetIndex(T obj); 8 | 9 | void add(int index, T obj); 10 | 11 | T set(int index, T obj); 12 | 13 | int getIndex(Object key); 14 | 15 | T getEntryAt(int index); 16 | 17 | boolean removeAt(int index); 18 | 19 | boolean isRemoved(int index); 20 | 21 | int rawSize(); 22 | 23 | @Override 24 | ListIterator iterator(); 25 | 26 | ListIterator iterator(int fromIndex); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedCollectionBase.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import net.intelie.tinymap.util.Preconditions; 4 | 5 | import java.util.AbstractCollection; 6 | import java.util.ListIterator; 7 | import java.util.NoSuchElementException; 8 | import java.util.Objects; 9 | import java.util.function.Consumer; 10 | 11 | public abstract class IndexedCollectionBase extends AbstractCollection implements IndexedCollection { 12 | @Override 13 | public int getIndex(Object key) { 14 | for (int i = 0; i < rawSize(); i++) 15 | if (!isRemoved(i) && Objects.equals(key, getEntryAt(i))) 16 | return i; 17 | return -1; 18 | } 19 | 20 | @Override 21 | public void clear() { 22 | throw new UnsupportedOperationException("modification not supported: " + this); 23 | } 24 | 25 | @Override 26 | public boolean isEmpty() { 27 | return size() == 0; 28 | } 29 | 30 | @Override 31 | public boolean contains(Object o) { 32 | return getIndex(o) >= 0; 33 | } 34 | 35 | @Override 36 | public ListIterator iterator() { 37 | return iterator(0); 38 | } 39 | 40 | @Override 41 | public ListIterator iterator(int fromIndex) { 42 | return new CollectionIterator(fromIndex); 43 | } 44 | 45 | @Override 46 | public void forEach(Consumer action) { 47 | for (int i = 0; i < rawSize(); i++) { 48 | if (!isRemoved(i)) 49 | action.accept(getEntryAt(i)); 50 | } 51 | } 52 | 53 | @Override 54 | public boolean add(T obj) { 55 | return addOrGetIndex(obj) < 0; 56 | } 57 | 58 | @Override 59 | public boolean remove(Object o) { 60 | int index = getIndex(o); 61 | if (index < 0) return false; 62 | removeAt(index); 63 | return true; 64 | } 65 | 66 | 67 | public interface NoAdditiveChange extends IndexedCollection { 68 | @Override 69 | default int addOrGetIndex(T obj) { 70 | throw new UnsupportedOperationException("modification not supported: " + this); 71 | } 72 | 73 | @Override 74 | default void add(int index, T obj) { 75 | throw new UnsupportedOperationException("modification not supported: " + this); 76 | } 77 | 78 | @Override 79 | default T set(int index, T obj) { 80 | throw new UnsupportedOperationException("modification not supported: " + this); 81 | } 82 | } 83 | 84 | public interface Immutable extends NoAdditiveChange { 85 | @Override 86 | default boolean removeAt(int index) { 87 | throw new UnsupportedOperationException("modification not supported: " + this); 88 | } 89 | 90 | 91 | @Override 92 | default boolean isRemoved(int index) { 93 | return false; 94 | } 95 | 96 | @Override 97 | default int rawSize() { 98 | return size(); 99 | } 100 | 101 | } 102 | 103 | private class CollectionIterator implements ListIterator { 104 | private int current; 105 | private int next; 106 | private int prev; 107 | 108 | public CollectionIterator(int fromIndex) { 109 | this.current = -1; 110 | this.next = findNext(fromIndex); 111 | this.prev = findPrev(fromIndex - 1); 112 | } 113 | 114 | private int findNext(int index) { 115 | while (index < rawSize() && isRemoved(index)) 116 | index++; 117 | return index; 118 | } 119 | 120 | private int findPrev(int index) { 121 | while (index >= 0 && isRemoved(index)) 122 | index--; 123 | return index; 124 | } 125 | 126 | @Override 127 | public boolean hasNext() { 128 | return next < rawSize(); 129 | } 130 | 131 | @Override 132 | public boolean hasPrevious() { 133 | return prev >= 0; 134 | } 135 | 136 | @Override 137 | public int nextIndex() { 138 | return next; 139 | } 140 | 141 | @Override 142 | public int previousIndex() { 143 | return prev; 144 | } 145 | 146 | @Override 147 | public void set(T obj) { 148 | Preconditions.checkState(current >= 0, "no iteration occurred"); 149 | IndexedCollectionBase.this.set(current, obj); 150 | } 151 | 152 | @Override 153 | public void add(T obj) { 154 | IndexedCollectionBase.this.add(next++, obj); 155 | current = -1; 156 | } 157 | 158 | @Override 159 | public void remove() { 160 | Preconditions.checkState(current >= 0, "no iteration occurred"); 161 | if (removeAt(current)) 162 | next--; 163 | current = -1; 164 | } 165 | 166 | @Override 167 | public T previous() { 168 | if (!hasPrevious()) 169 | throw new NoSuchElementException(); 170 | next = current = prev; 171 | prev = findPrev(prev - 1); 172 | return getEntryAt(current); 173 | } 174 | 175 | @Override 176 | public T next() { 177 | if (!hasNext()) 178 | throw new NoSuchElementException(); 179 | prev = current = next; 180 | next = findNext(next + 1); 181 | return getEntryAt(current); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedList.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import java.util.List; 4 | 5 | public interface IndexedList extends List, IndexedCollection { 6 | T removeLast(); 7 | 8 | @Override 9 | IndexedList subList(int fromIndex, int toIndex); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedListBase.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import net.intelie.tinymap.util.Preconditions; 4 | 5 | import java.io.Serializable; 6 | import java.util.Collection; 7 | import java.util.List; 8 | import java.util.ListIterator; 9 | import java.util.Objects; 10 | import java.util.function.Consumer; 11 | 12 | public abstract class IndexedListBase extends IndexedCollectionBase implements IndexedList { 13 | @Override 14 | public boolean addAll(int index, Collection collection) { 15 | boolean added = false; 16 | for (T obj : collection) { 17 | add(index++, obj); 18 | added = true; 19 | } 20 | return added; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object that) { 25 | if (this == that) return true; 26 | if (!(that instanceof List) || size() != ((List) that).size()) return false; 27 | 28 | List list = (List) that; 29 | int index = 0; 30 | for (Object obj : list) { 31 | if (!Objects.equals(obj, get(index++))) 32 | return false; 33 | } 34 | return true; 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | int hash = 1; 40 | for (int i = 0; i < rawSize(); i++) 41 | hash = 31 * hash + Objects.hashCode(getEntryAt(i)); 42 | return hash; 43 | } 44 | 45 | @Override 46 | public final boolean isRemoved(int index) { 47 | return false; 48 | } 49 | 50 | @Override 51 | public final int rawSize() { 52 | return size(); 53 | } 54 | 55 | @Override 56 | public T get(int index) { 57 | return getEntryAt(index); 58 | } 59 | 60 | @Override 61 | public void add(int index, T obj) { 62 | Preconditions.checkElementIndex(index, size() + 1); 63 | for (int i = index; i < rawSize(); i++) 64 | obj = set(i, obj); 65 | add(obj); 66 | } 67 | 68 | @Override 69 | public boolean removeAt(int index) { 70 | remove(index); 71 | return true; 72 | } 73 | 74 | @Override 75 | public T remove(int index) { 76 | Preconditions.checkElementIndex(index, rawSize()); 77 | T obj = removeLast(); 78 | for (int i = rawSize() - 1; i >= index; i--) 79 | obj = set(i, obj); 80 | return obj; 81 | } 82 | 83 | @Override 84 | public int indexOf(Object o) { 85 | for (int i = 0; i < rawSize(); i++) 86 | if (Objects.equals(o, getEntryAt(i))) 87 | return i; 88 | return -1; 89 | } 90 | 91 | @Override 92 | public int lastIndexOf(Object o) { 93 | for (int i = rawSize() - 1; i >= 0; i--) 94 | if (Objects.equals(o, getEntryAt(i))) 95 | return i; 96 | return -1; 97 | } 98 | 99 | @Override 100 | public ListIterator listIterator() { 101 | return iterator(); 102 | } 103 | 104 | @Override 105 | public ListIterator listIterator(int fromIndex) { 106 | return iterator(fromIndex); 107 | } 108 | 109 | @Override 110 | public IndexedList subList(int fromIndex, int toIndex) { 111 | return new ViewList(fromIndex, toIndex); 112 | } 113 | 114 | @Override 115 | public void forEach(Consumer action) { 116 | for (int i = 0; i < rawSize(); i++) { 117 | action.accept(getEntryAt(i)); 118 | } 119 | } 120 | 121 | public interface Immutable extends IndexedList { 122 | @Override 123 | default T set(int index, T obj) { 124 | throw new UnsupportedOperationException("modification not supported: " + this); 125 | } 126 | 127 | @Override 128 | default T removeLast() { 129 | throw new UnsupportedOperationException("modification not supported: " + this); 130 | } 131 | 132 | @Override 133 | default int addOrGetIndex(T obj) { 134 | throw new UnsupportedOperationException("modification not supported: " + this); 135 | } 136 | } 137 | 138 | public class ViewList extends IndexedListBase implements Serializable { 139 | private static final long serialVersionUID = 1L; 140 | 141 | private final int fromIndex; 142 | private int toIndex; 143 | 144 | public ViewList(int fromIndex, int toIndex) { 145 | this.fromIndex = fromIndex; 146 | this.toIndex = toIndex; 147 | } 148 | 149 | @Override 150 | public int addOrGetIndex(T obj) { 151 | IndexedListBase.this.add(toIndex, obj); 152 | toIndex++; 153 | return -1; 154 | } 155 | 156 | @Override 157 | public int size() { 158 | return toIndex - fromIndex; 159 | } 160 | 161 | @Override 162 | public T set(int index, T obj) { 163 | Preconditions.checkElementIndex(index, toIndex - fromIndex); 164 | return IndexedListBase.this.set(index + fromIndex, obj); 165 | 166 | } 167 | 168 | @Override 169 | public T removeLast() { 170 | return IndexedListBase.this.remove(toIndex - 1); 171 | } 172 | 173 | @Override 174 | public T getEntryAt(int index) { 175 | Preconditions.checkElementIndex(index, toIndex - fromIndex); 176 | return IndexedListBase.this.getEntryAt(index + fromIndex); 177 | } 178 | } 179 | 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedMap.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import java.util.Map; 4 | 5 | public interface IndexedMap extends Map { 6 | int getIndex(Object key); 7 | 8 | K getKeyAt(int index); 9 | 10 | V getValueAt(int index); 11 | 12 | Entry getEntryAt(int index); 13 | 14 | V removeAt(int index); 15 | 16 | V setValueAt(int index, V value); 17 | 18 | boolean isRemoved(int index); 19 | 20 | int rawSize(); 21 | 22 | Object getUnsafe(Object key, Object defaultValue); 23 | 24 | @Override 25 | IndexedSet keySet(); 26 | 27 | @Override 28 | IndexedSet> entrySet(); 29 | 30 | interface Entry extends Map.Entry { 31 | int getIndex(); 32 | 33 | boolean isRemoved(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedMapBase.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import net.intelie.tinymap.util.Preconditions; 4 | 5 | import java.io.Serializable; 6 | import java.util.Collection; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | import java.util.function.BiConsumer; 10 | 11 | public abstract class IndexedMapBase implements IndexedMap { 12 | private static final Object SENTINEL = new Object(); 13 | 14 | @Override 15 | public V getOrDefault(Object key, V defaultValue) { 16 | int index = getIndex(key); 17 | if (index < 0) return defaultValue; 18 | return getValueAt(index); 19 | } 20 | 21 | @Override 22 | public V get(Object key) { 23 | int index = getIndex(key); 24 | if (index < 0) return null; 25 | return getValueAt(index); 26 | } 27 | 28 | @Override 29 | public boolean containsKey(Object key) { 30 | return getUnsafe(key, SENTINEL) != SENTINEL; 31 | } 32 | 33 | @Override 34 | public boolean isEmpty() { 35 | return size() == 0; 36 | } 37 | 38 | @Override 39 | public boolean containsValue(Object value) { 40 | for (int i = 0; i < rawSize(); i++) 41 | if (!isRemoved(i) && Objects.equals(value, getValueAt(i))) 42 | return true; 43 | return false; 44 | } 45 | 46 | @Override 47 | public Entry getEntryAt(int index) { 48 | Preconditions.checkElementIndex(index, rawSize()); 49 | return new IndexedEntry(index); 50 | } 51 | 52 | @Override 53 | public void forEach(BiConsumer action) { 54 | int size = rawSize(); 55 | for (int i = 0; i < size; i++) 56 | if (!isRemoved(i)) 57 | action.accept(getKeyAt(i), getValueAt(i)); 58 | } 59 | 60 | @Override 61 | public V removeAt(int index) { 62 | throw new UnsupportedOperationException("modification not supported: " + this); 63 | } 64 | 65 | @Override 66 | public V setValueAt(int index, V value) { 67 | throw new UnsupportedOperationException("modification not supported: " + this); 68 | } 69 | 70 | @Override 71 | public boolean isRemoved(int index) { 72 | return false; 73 | } 74 | 75 | @Override 76 | public int rawSize() { 77 | return size(); 78 | } 79 | 80 | @Override 81 | public Object getUnsafe(Object key, Object defaultValue) { 82 | int index = getIndex(key); 83 | if (index < 0) return defaultValue; 84 | return getValueAt(index); 85 | } 86 | 87 | @Override 88 | public V put(K key, V value) { 89 | throw new UnsupportedOperationException("modification not supported: " + this); 90 | } 91 | 92 | @Override 93 | public V remove(Object key) { 94 | int index = getIndex(key); 95 | if (index < 0) return null; 96 | return removeAt(index); 97 | } 98 | 99 | @Override 100 | public void clear() { 101 | throw new UnsupportedOperationException("modification not supported: " + this); 102 | } 103 | 104 | @Override 105 | public void putAll(Map m) { 106 | m.forEach(this::put); 107 | } 108 | 109 | @Override 110 | public IndexedSet keySet() { 111 | return new KeysView(); 112 | } 113 | 114 | @Override 115 | public Collection values() { 116 | return new ValuesView(); 117 | } 118 | 119 | @Override 120 | public IndexedSet> entrySet() { 121 | return new EntriesView(); 122 | } 123 | 124 | @Override 125 | public boolean equals(Object o) { 126 | if (this == o) return true; 127 | if (!(o instanceof Map) || size() != ((Map) o).size()) return false; 128 | 129 | for (Map.Entry entry : ((Map) o).entrySet()) 130 | if (!Objects.equals(entry.getValue(), getUnsafe(entry.getKey(), SENTINEL))) 131 | return false; 132 | return true; 133 | } 134 | 135 | @Override 136 | public int hashCode() { 137 | int hash = 0; 138 | for (int i = 0; i < rawSize(); i++) 139 | if (!isRemoved(i)) 140 | hash += Objects.hashCode(getKeyAt(i)) ^ Objects.hashCode(getValueAt(i)); 141 | return hash; 142 | } 143 | 144 | @Override 145 | public String toString() { 146 | StringBuilder sb = new StringBuilder().append('{'); 147 | boolean first = true; 148 | for (int i = 0; i < rawSize(); i++) { 149 | if (isRemoved(i)) continue; 150 | 151 | if (!first) 152 | sb.append(", "); 153 | first = false; 154 | sb.append(getKeyAt(i)).append('=').append(getValueAt(i)); 155 | } 156 | return sb.append('}').toString(); 157 | } 158 | 159 | private class ValuesView extends IndexedCollectionBase implements Serializable, IndexedCollectionBase.NoAdditiveChange { 160 | private static final long serialVersionUID = 1L; 161 | 162 | @Override 163 | public V getEntryAt(int index) { 164 | return getValueAt(index); 165 | } 166 | 167 | @Override 168 | public void clear() { 169 | IndexedMapBase.this.clear(); 170 | } 171 | 172 | @Override 173 | public boolean removeAt(int index) { 174 | IndexedMapBase.this.removeAt(index); 175 | return false; 176 | } 177 | 178 | @Override 179 | public boolean isRemoved(int index) { 180 | return IndexedMapBase.this.isRemoved(index); 181 | } 182 | 183 | @Override 184 | public int rawSize() { 185 | return IndexedMapBase.this.rawSize(); 186 | } 187 | 188 | @Override 189 | public int size() { 190 | return IndexedMapBase.this.size(); 191 | } 192 | } 193 | 194 | private class KeysView extends IndexedSetBase implements Serializable, IndexedCollectionBase.NoAdditiveChange { 195 | private static final long serialVersionUID = 1L; 196 | 197 | @Override 198 | public int getIndex(Object key) { 199 | return IndexedMapBase.this.getIndex(key); 200 | } 201 | 202 | @Override 203 | public K getEntryAt(int index) { 204 | return getKeyAt(index); 205 | } 206 | 207 | @Override 208 | public void clear() { 209 | IndexedMapBase.this.clear(); 210 | } 211 | 212 | @Override 213 | public boolean removeAt(int index) { 214 | IndexedMapBase.this.removeAt(index); 215 | return false; 216 | } 217 | 218 | @Override 219 | public boolean isRemoved(int index) { 220 | return IndexedMapBase.this.isRemoved(index); 221 | } 222 | 223 | @Override 224 | public int rawSize() { 225 | return IndexedMapBase.this.rawSize(); 226 | } 227 | 228 | @Override 229 | public int size() { 230 | return IndexedMapBase.this.size(); 231 | } 232 | } 233 | 234 | private class EntriesView extends IndexedSetBase> implements Serializable, IndexedCollectionBase.NoAdditiveChange> { 235 | private static final long serialVersionUID = 1L; 236 | 237 | @Override 238 | public int getIndex(Object key) { 239 | if (!(key instanceof Map.Entry)) 240 | return -1; 241 | Map.Entry entry = (Map.Entry) key; 242 | int index = IndexedMapBase.this.getIndex(entry.getKey()); 243 | if (index < 0 || Objects.equals(entry.getValue(), getValueAt(index))) 244 | return index; 245 | return -1; 246 | } 247 | 248 | @Override 249 | public Entry getEntryAt(int index) { 250 | return IndexedMapBase.this.getEntryAt(index); 251 | } 252 | 253 | @Override 254 | public void clear() { 255 | IndexedMapBase.this.clear(); 256 | } 257 | 258 | @Override 259 | public boolean removeAt(int index) { 260 | IndexedMapBase.this.removeAt(index); 261 | return false; 262 | } 263 | 264 | @Override 265 | public boolean isRemoved(int index) { 266 | return IndexedMapBase.this.isRemoved(index); 267 | } 268 | 269 | @Override 270 | public int rawSize() { 271 | return IndexedMapBase.this.rawSize(); 272 | } 273 | 274 | @Override 275 | public int size() { 276 | return IndexedMapBase.this.size(); 277 | } 278 | } 279 | 280 | private class IndexedEntry implements IndexedMap.Entry, Serializable { 281 | private static final long serialVersionUID = 1L; 282 | 283 | private final int index; 284 | 285 | public IndexedEntry(int index) { 286 | this.index = index; 287 | } 288 | 289 | @Override 290 | public K getKey() { 291 | return getKeyAt(index); 292 | } 293 | 294 | @Override 295 | public V getValue() { 296 | return getValueAt(index); 297 | } 298 | 299 | @Override 300 | public V setValue(V value) { 301 | return setValueAt(index, value); 302 | } 303 | 304 | @Override 305 | public boolean equals(Object o) { 306 | if (this == o) return true; 307 | if (!(o instanceof Map.Entry)) return false; 308 | Map.Entry that = (Map.Entry) o; 309 | return Objects.equals(that.getKey(), getKey()) && Objects.equals(that.getValue(), getValue()); 310 | } 311 | 312 | @Override 313 | public int hashCode() { 314 | return Objects.hashCode(getKeyAt(index)) ^ Objects.hashCode(getValueAt(index)); 315 | } 316 | 317 | @Override 318 | public String toString() { 319 | return getKey() + "=" + getValue(); 320 | } 321 | 322 | @Override 323 | public int getIndex() { 324 | return index; 325 | } 326 | 327 | @Override 328 | public boolean isRemoved() { 329 | return IndexedMapBase.this.isRemoved(index); 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedSet.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import java.util.Set; 4 | 5 | public interface IndexedSet extends Set, IndexedCollection { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/base/IndexedSetBase.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.base; 2 | 3 | import java.util.Objects; 4 | import java.util.Set; 5 | 6 | public abstract class IndexedSetBase extends IndexedCollectionBase implements IndexedSet { 7 | @Override 8 | public boolean equals(Object o) { 9 | if (this == o) return true; 10 | if (!(o instanceof Set) || size() != ((Set) o).size()) return false; 11 | 12 | for (Object obj : ((Set) o)) 13 | if (getIndex(obj) < 0) 14 | return false; 15 | return true; 16 | } 17 | 18 | @Override 19 | public int hashCode() { 20 | int hash = 0; 21 | for (int i = 0; i < rawSize(); i++) 22 | if (!isRemoved(i)) 23 | hash += Objects.hashCode(getEntryAt(i)); 24 | return hash; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/json/FastDouble.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.json; 2 | 3 | public class FastDouble { 4 | public static final int MAX_DIGITS = 15; 5 | private final static int POW_RANGE = 23; 6 | private final static double[] POW10 = new double[POW_RANGE]; 7 | 8 | static { 9 | for (int i = 0; i < POW_RANGE; i++) 10 | POW10[i] = Math.pow(10., i); 11 | } 12 | 13 | public static double parseDouble(String s) { 14 | return parseDouble(s, 0, s.length()); 15 | } 16 | 17 | private static double finishDouble(boolean negative, long value, int exp) { 18 | double d = exp < 0 ? value / POW10[-exp] : value * POW10[exp]; 19 | return negative ? -d : d; 20 | } 21 | 22 | private static double fallback(CharSequence csq, int offset, int end) { 23 | return Double.parseDouble(csq.subSequence(offset, end).toString()); 24 | } 25 | 26 | public static double parseDouble(CharSequence csq, int offset, int end) { 27 | int i = offset; 28 | i = trim(csq, end, i); 29 | 30 | boolean negative = false; 31 | long value = 0; 32 | int exp = 0; 33 | int digits = 0; 34 | 35 | if (i < end) { 36 | if (csq.charAt(i) == '-') { 37 | negative = true; 38 | i++; 39 | } else if (csq.charAt(i) == '+') { 40 | i++; 41 | } 42 | } 43 | 44 | while (i < end) { 45 | char c = csq.charAt(i); 46 | if (c < '0' || c > '9') break; 47 | value = (value * 10) + (c - '0'); 48 | i++; 49 | digits++; 50 | } 51 | 52 | if (i < end && csq.charAt(i) == '.') { 53 | i++; 54 | while (i < end) { 55 | char c = csq.charAt(i); 56 | if (c < '0' || c > '9') break; 57 | value = (value * 10) + (c - '0'); 58 | i++; 59 | digits++; 60 | exp--; 61 | } 62 | } 63 | 64 | if (digits == 0 || digits > MAX_DIGITS) 65 | return fallback(csq, offset, end); 66 | 67 | 68 | if (i < end && (csq.charAt(i) == 'E' || csq.charAt(i) == 'e')) { 69 | i++; 70 | boolean expNegative = false; 71 | int expExp = 0; 72 | int expDigits = 0; 73 | if (i < end) { 74 | if (csq.charAt(i) == '-') { 75 | expNegative = true; 76 | i++; 77 | } else if (csq.charAt(i) == '+') { 78 | i++; 79 | } 80 | } 81 | while (i < end) { 82 | char c = csq.charAt(i); 83 | if (c < '0' || c > '9') break; 84 | expExp = (expExp * 10) + (c - '0'); 85 | expDigits++; 86 | i++; 87 | } 88 | 89 | if (expDigits == 0 || expDigits > 4) 90 | return fallback(csq, offset, end); 91 | 92 | exp += (expNegative ? -expExp : expExp); 93 | 94 | } 95 | 96 | i = trim(csq, end, i); 97 | 98 | if (Math.abs(exp) >= POW_RANGE || i < end) 99 | return fallback(csq, offset, end); 100 | 101 | return finishDouble(negative, value, exp); 102 | } 103 | 104 | private static int trim(CharSequence csq, int end, int i) { 105 | while (i < end && csq.charAt(i) == ' ') 106 | i++; 107 | return i; 108 | } 109 | 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/json/JsonToken.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.json; 2 | 3 | public enum JsonToken { 4 | BEGIN_ARRAY, 5 | END_ARRAY, 6 | BEGIN_OBJECT, 7 | END_OBJECT, 8 | NAME, 9 | STRING, 10 | NUMBER, 11 | BOOLEAN, 12 | NULL, 13 | END_DOCUMENT 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/json/TinyJsonDecoder.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.json; 2 | 3 | import net.intelie.tinymap.*; 4 | 5 | import java.io.IOException; 6 | import java.io.Reader; 7 | import java.util.ArrayDeque; 8 | import java.util.Deque; 9 | 10 | public class TinyJsonDecoder extends TinyJsonReader { 11 | private final ObjectCache cache; 12 | private final Deque> maps = new ArrayDeque<>(); 13 | private final Deque> lists = new ArrayDeque<>(); 14 | 15 | public TinyJsonDecoder(ObjectCache cache) { 16 | this.cache = cache; 17 | clear(); 18 | setLenient(true); 19 | } 20 | 21 | public TinyJsonDecoder(ObjectCache cache, Reader reader) { 22 | this(cache); 23 | setReader(reader); 24 | } 25 | 26 | public Object nextObject() throws IOException { 27 | JsonToken peeked = peek(); 28 | switch (peeked) { 29 | case BEGIN_ARRAY: 30 | return nextList(); 31 | case BEGIN_OBJECT: 32 | return nextMap(); 33 | case NUMBER: 34 | return cache.get(nextDouble()); 35 | case BOOLEAN: 36 | return nextBoolean(); 37 | case NULL: 38 | nextNull(); 39 | return null; 40 | default: 41 | return cache.get(nextString()); 42 | } 43 | } 44 | 45 | public TinyMap nextMap() throws IOException { 46 | beginObject(); 47 | TinyMapBuilder map = maps.poll(); 48 | if (map == null) map = TinyMap.builder(); 49 | try { 50 | while (hasNext()) { 51 | String name = cache.get(nextName()); 52 | map.put(name, nextObject()); 53 | } 54 | endObject(); 55 | return cache.get(map); 56 | } finally { 57 | map.clear(); 58 | maps.push(map); 59 | } 60 | } 61 | 62 | public TinyList nextList() throws IOException { 63 | beginArray(); 64 | TinyListBuilder list = lists.poll(); 65 | if (list == null) list = TinyList.builder(); 66 | try { 67 | while (hasNext()) 68 | list.add(nextObject()); 69 | endArray(); 70 | return cache.get(list); 71 | } finally { 72 | list.clear(); 73 | lists.push(list); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/util/DefaultDoubleCache.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | public class DefaultDoubleCache { 4 | private static final Double NEG_ZERO = -0.0; 5 | private final int smallCacheAmplitude; 6 | private final Double[] smallCache; 7 | private final Double[] data; 8 | private final int mask; 9 | 10 | public DefaultDoubleCache() { 11 | this((1 << 14), 512); 12 | } 13 | 14 | public DefaultDoubleCache(int bucketCount, int smallCacheAmplitude) { 15 | Preconditions.checkArgument(Integer.bitCount(bucketCount) == 1, "Bucket count must be power of two"); 16 | this.smallCacheAmplitude = smallCacheAmplitude; 17 | this.data = new Double[bucketCount]; 18 | this.mask = bucketCount - 1; 19 | this.smallCache = new Double[smallCacheAmplitude * 2]; 20 | for (int i = 0; i < smallCache.length; i++) { 21 | smallCache[i] = (double) (i - smallCacheAmplitude); 22 | } 23 | } 24 | 25 | public Double get(double value) { 26 | return getCached(value, null); 27 | 28 | } 29 | 30 | public Double get(Double value) { 31 | if (value == null) return null; 32 | return getCached(value, value); 33 | } 34 | 35 | 36 | public static int mix(int h) { 37 | h ^= h >>> 16; 38 | h *= 0x85ebca6b; 39 | h ^= h >>> 13; 40 | h *= 0xc2b2ae35; 41 | h ^= h >>> 16; 42 | return h; 43 | } 44 | 45 | private Double getCached(double value, Double boxed) { 46 | if (value >= -smallCacheAmplitude && value < smallCacheAmplitude && value == (int) value) { 47 | if (Double.doubleToLongBits(value) == 0x8000000000000000L) 48 | return NEG_ZERO; 49 | return smallCache[(int) value + smallCacheAmplitude]; 50 | } 51 | int hash = Double.hashCode(value); 52 | int index = mix(hash) & mask; 53 | Double cached = data[index]; 54 | if (cached != null && Double.doubleToLongBits(cached) == Double.doubleToLongBits(value)) 55 | return cached; 56 | 57 | return data[index] = boxed != null ? boxed : Double.valueOf(value); 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/util/DefaultObjectCache.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import net.intelie.tinymap.CacheAdapter; 4 | import net.intelie.tinymap.CacheableBuilder; 5 | import net.intelie.tinymap.ObjectCache; 6 | 7 | import java.lang.ref.WeakReference; 8 | 9 | public class DefaultObjectCache implements ObjectCache { 10 | private static final StringCacheAdapter STRING_ADAPTER = new StringCacheAdapter(); 11 | private final Bucket[] data; 12 | private final int mask; 13 | private final DefaultDoubleCache doubleCache; 14 | 15 | public DefaultObjectCache() { 16 | this(1 << 16); 17 | } 18 | 19 | public DefaultObjectCache(int bucketCount) { 20 | Preconditions.checkArgument(Integer.bitCount(bucketCount) == 1, "Bucket count must be power of two"); 21 | this.data = new Bucket[bucketCount]; 22 | this.doubleCache = new DefaultDoubleCache(bucketCount, 512); 23 | this.mask = bucketCount - 1; 24 | } 25 | 26 | @Override 27 | public Double get(double value) { 28 | return doubleCache.get(value); 29 | } 30 | 31 | @Override 32 | public String get(CharSequence cs) { 33 | return get(cs, STRING_ADAPTER); 34 | } 35 | 36 | @Override 37 | public , T> T get(B builder) { 38 | return get(builder, builder.adapter()); 39 | } 40 | 41 | @Override 42 | public T get(B builder, CacheAdapter adapter) { 43 | if (builder == null) 44 | return null; 45 | int hash = adapter.contentHashCode(builder); 46 | int index = DefaultDoubleCache.mix(hash) & mask; 47 | Bucket bucket = data[index]; 48 | if (bucket != null && bucket.hash == hash) { 49 | T cached = adapter.contentEquals(builder, data[index].get()); 50 | if (cached != null) 51 | return cached; 52 | } 53 | 54 | T newValue = adapter.build(builder, this); 55 | data[index] = new Bucket(newValue, hash); 56 | return newValue; 57 | } 58 | 59 | private static final class Bucket extends WeakReference { 60 | private final int hash; 61 | 62 | private Bucket(Object value, int hash) { 63 | super(value); 64 | this.hash = hash; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/util/ObjectOptimizer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import net.intelie.tinymap.*; 4 | 5 | import java.util.*; 6 | 7 | @SuppressWarnings("unchecked") 8 | public class ObjectOptimizer { 9 | private final ObjectCache cache; 10 | private final Deque> maps = new ArrayDeque<>(); 11 | private final Deque> lists = new ArrayDeque<>(); 12 | private final Deque> sets = new ArrayDeque<>(); 13 | 14 | public ObjectOptimizer(ObjectCache cache) { 15 | this.cache = cache; 16 | } 17 | 18 | public Object optimize(Object object) { 19 | if (object instanceof CharSequence) 20 | return cache != null ? cache.get((CharSequence) object) : object.toString(); 21 | if (object instanceof Double) 22 | return cache != null ? cache.get(((Double) object)) : (Double) object; 23 | if (object instanceof Set) 24 | return optimizeSet((Iterable) object); 25 | if (object instanceof List) 26 | return optimizeList((Iterable) object); 27 | if (object instanceof Map) 28 | return optimizeMap((Map) object); 29 | return object; 30 | } 31 | 32 | public TinyMap optimizeMap(Map object) { 33 | TinyMapBuilder map = makeMapBuilder(); 34 | try { 35 | object.forEach((k, v) -> map.put((K) optimize(k), (V) optimize(v))); 36 | return cache != null ? cache.get(map) : map.build(); 37 | } finally { 38 | map.clear(); 39 | maps.add(map); 40 | } 41 | } 42 | 43 | private TinyMapBuilder makeMapBuilder() { 44 | TinyMapBuilder map = maps.poll(); 45 | if (map == null) map = TinyMap.builder(); 46 | return (TinyMapBuilder) map; 47 | } 48 | 49 | public TinyList optimizeList(Iterable object) { 50 | TinyListBuilder list = makeListBuilder(); 51 | try { 52 | object.forEach(x -> list.add((T) optimize(x))); 53 | return cache != null ? cache.get(list) : list.build(); 54 | } finally { 55 | list.clear(); 56 | lists.add(list); 57 | } 58 | } 59 | 60 | private TinyListBuilder makeListBuilder() { 61 | TinyListBuilder list = lists.poll(); 62 | if (list == null) list = TinyList.builder(); 63 | return (TinyListBuilder) list; 64 | 65 | } 66 | 67 | public TinySet optimizeSet(Iterable object) { 68 | TinySetBuilder set = makeSetBuilder(); 69 | try { 70 | object.forEach(x -> set.add((T) optimize(x))); 71 | return cache != null ? cache.get(set) : set.build(); 72 | } finally { 73 | set.clear(); 74 | sets.add(set); 75 | } 76 | } 77 | 78 | private TinySetBuilder makeSetBuilder() { 79 | TinySetBuilder set = sets.poll(); 80 | if (set == null) set = TinySet.builder(); 81 | return (TinySetBuilder) set; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/util/Preconditions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package net.intelie.tinymap.util; 16 | 17 | public abstract class Preconditions { 18 | public static void checkArgument(boolean expression) { 19 | if (!expression) { 20 | throw new IllegalArgumentException(); 21 | } 22 | } 23 | 24 | public static void checkArgument(boolean expression, Object errorMessage) { 25 | if (!expression) { 26 | throw new IllegalArgumentException(String.valueOf(errorMessage)); 27 | } 28 | } 29 | 30 | public static void checkArgument(boolean expression, 31 | String errorMessageTemplate, 32 | Object... errorMessageArgs) { 33 | if (!expression) { 34 | throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); 35 | } 36 | } 37 | 38 | public static void checkState(boolean expression) { 39 | if (!expression) { 40 | throw new IllegalStateException(); 41 | } 42 | } 43 | 44 | public static void checkState(boolean expression, Object errorMessage) { 45 | if (!expression) { 46 | throw new IllegalStateException(String.valueOf(errorMessage)); 47 | } 48 | } 49 | 50 | public static void checkState(boolean expression, 51 | String errorMessageTemplate, 52 | Object... errorMessageArgs) { 53 | if (!expression) { 54 | throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs)); 55 | } 56 | } 57 | 58 | public static T checkNotNull(T reference) { 59 | if (reference == null) { 60 | throw new NullPointerException(); 61 | } 62 | return reference; 63 | } 64 | 65 | public static T checkNotNull(T reference, Object errorMessage) { 66 | if (reference == null) { 67 | throw new NullPointerException(String.valueOf(errorMessage)); 68 | } 69 | return reference; 70 | } 71 | 72 | public static T checkNotNull(T reference, 73 | String errorMessageTemplate, 74 | Object... errorMessageArgs) { 75 | if (reference == null) { 76 | // If either of these parameters is null, the right thing happens anyway 77 | throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); 78 | } 79 | return reference; 80 | } 81 | 82 | public static int checkElementIndex(int index, int size) { 83 | return checkElementIndex(index, size, "index"); 84 | } 85 | 86 | public static int checkElementIndex( 87 | int index, int size, String desc) { 88 | // Carefully optimized for execution by hotspot (explanatory comment above) 89 | if (index < 0 || index >= size) { 90 | throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); 91 | } 92 | return index; 93 | } 94 | 95 | private static String badElementIndex(int index, int size, String desc) { 96 | if (index < 0) { 97 | return format("%s (%s) must not be negative", desc, index); 98 | } else if (size < 0) { 99 | throw new IllegalArgumentException("negative size: " + size); 100 | } else { // index >= size 101 | return format("%s (%s) must be less than size (%s)", desc, index, size); 102 | } 103 | } 104 | 105 | public static int checkPositionIndex(int index, int size) { 106 | return checkPositionIndex(index, size, "index"); 107 | } 108 | 109 | public static int checkPositionIndex(int index, int size, String desc) { 110 | // Carefully optimized for execution by hotspot (explanatory comment above) 111 | if (index < 0 || index > size) { 112 | throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); 113 | } 114 | return index; 115 | } 116 | 117 | private static String badPositionIndex(int index, int size, String desc) { 118 | if (index < 0) { 119 | return format("%s (%s) must not be negative", desc, index); 120 | } else if (size < 0) { 121 | throw new IllegalArgumentException("negative size: " + size); 122 | } else { // index > size 123 | return format("%s (%s) must not be greater than size (%s)", desc, index, size); 124 | } 125 | } 126 | 127 | public static void checkPositionIndexes(int start, int end, int size) { 128 | // Carefully optimized for execution by hotspot (explanatory comment above) 129 | if (start < 0 || end < start || end > size) { 130 | throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); 131 | } 132 | } 133 | 134 | private static String badPositionIndexes(int start, int end, int size) { 135 | if (start < 0 || start > size) { 136 | return badPositionIndex(start, size, "start index"); 137 | } 138 | if (end < 0 || end > size) { 139 | return badPositionIndex(end, size, "end index"); 140 | } 141 | // end < start 142 | return format("end index (%s) must not be less than start index (%s)", end, start); 143 | } 144 | 145 | public static String format(String template, Object... args) { 146 | template = String.valueOf(template); // null -> "null" 147 | 148 | // start substituting the arguments into the '%s' placeholders 149 | StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); 150 | int templateStart = 0; 151 | int i = 0; 152 | while (i < args.length) { 153 | int placeholderStart = template.indexOf("%s", templateStart); 154 | if (placeholderStart == -1) { 155 | break; 156 | } 157 | builder.append(template, templateStart, placeholderStart); 158 | builder.append(args[i++]); 159 | templateStart = placeholderStart + 2; 160 | } 161 | builder.append(template.substring(templateStart)); 162 | 163 | // if we run out of placeholders, append the extra args in square braces 164 | if (i < args.length) { 165 | builder.append(" ["); 166 | builder.append(args[i++]); 167 | while (i < args.length) { 168 | builder.append(", "); 169 | builder.append(args[i++]); 170 | } 171 | builder.append(']'); 172 | } 173 | 174 | return builder.toString(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/net/intelie/tinymap/util/StringCacheAdapter.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import net.intelie.tinymap.CacheAdapter; 4 | import net.intelie.tinymap.ObjectCache; 5 | 6 | public class StringCacheAdapter implements CacheAdapter { 7 | @Override 8 | public int contentHashCode(CharSequence cs) { 9 | if (cs instanceof String) 10 | return cs.hashCode(); 11 | int length = cs.length(); 12 | int hash = 0; 13 | for (int i = 0; i < length; i++) 14 | hash = 31 * hash + cs.charAt(i); 15 | return hash; 16 | } 17 | 18 | @Override 19 | public String contentEquals(CharSequence cs, Object cached) { 20 | if (!(cached instanceof String)) return null; 21 | String str = (String) cached; 22 | return str.contentEquals(cs) ? str : null; 23 | } 24 | 25 | @Override 26 | public String build(CharSequence cs, ObjectCache parent) { 27 | return cs.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/ObjectCacheTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.util.DefaultObjectCache; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | public class ObjectCacheTest { 9 | @Test 10 | public void testListCacheHit() { 11 | DefaultObjectCache cache = new DefaultObjectCache(); 12 | 13 | TinyListBuilder builder1 = TinyList.builder(); 14 | TinyListBuilder builder2 = TinyList.builder(); 15 | 16 | assertThat(cache.get(builder1)).isSameAs(cache.get(builder2)).isEqualTo(builder1.build()); 17 | 18 | builder1.add("aaa"); 19 | builder2.add("aaa"); 20 | 21 | assertThat(cache.get(builder1)).isSameAs(cache.get(builder2)).isEqualTo(builder1.build()); 22 | } 23 | 24 | @Test 25 | public void testMapCacheHit() { 26 | ObjectCache cache = new DefaultObjectCache(); 27 | 28 | TinyMapBuilder builder1 = TinyMap.builder(); 29 | TinyMapBuilder builder2 = TinyMap.builder(); 30 | 31 | assertThat(cache.get(builder1)).isSameAs(cache.get(builder2)).isEqualTo(builder1.build()); 32 | 33 | builder1.put("aaa", 123); 34 | builder2.put("aaa", 123); 35 | 36 | assertThat(cache.get(builder1)).isSameAs(cache.get(builder2)).isEqualTo(builder1.build()); 37 | } 38 | 39 | @Test 40 | public void testDoubleCacheHit() { 41 | DefaultObjectCache cache = new DefaultObjectCache(); 42 | Double cached1 = cache.get(Double.parseDouble("123.456")); 43 | Double cached2 = cache.get(Double.parseDouble("123.456")); 44 | 45 | assertThat(cached1).isSameAs(cached2); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/Playground.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.stream.JsonReader; 5 | import net.intelie.tinymap.json.JsonToken; 6 | import net.intelie.tinymap.json.TinyJsonDecoder; 7 | import net.intelie.tinymap.support.JavaOptimizer; 8 | import net.intelie.tinymap.support.TestSizeUtils; 9 | import net.intelie.tinymap.util.DefaultObjectCache; 10 | import net.intelie.tinymap.util.ObjectOptimizer; 11 | import net.intelie.tinymap.util.SuppressForbidden; 12 | import org.junit.Ignore; 13 | import org.junit.Test; 14 | 15 | import java.io.IOException; 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.util.ArrayList; 19 | import java.util.LinkedHashMap; 20 | import java.util.List; 21 | 22 | @Ignore 23 | @SuppressForbidden 24 | public class Playground { 25 | @Test 26 | public void gson() throws IOException { 27 | List objs = new ArrayList<>(); 28 | List objs2 = new ArrayList<>(); 29 | 30 | 31 | ObjectCache cache = new DefaultObjectCache(1 << 20); 32 | 33 | Gson gson = new Gson(); 34 | 35 | long start = System.nanoTime(); 36 | String fileName = "/home/juanplopes/Downloads/dumps/everything50k.json"; 37 | try (TinyJsonDecoder reader = new TinyJsonDecoder(cache, Files.newBufferedReader(Paths.get(fileName))); 38 | JsonReader reader2 = new JsonReader(Files.newBufferedReader(Paths.get(fileName)))) { 39 | reader2.setLenient(true); 40 | while (true) { 41 | if (reader.peek() == JsonToken.END_DOCUMENT) break; 42 | objs.addAll(reader.nextList()); 43 | 44 | if (reader2.peek() == com.google.gson.stream.JsonToken.END_DOCUMENT) break; 45 | List obj = gson.fromJson(reader2, List.class); 46 | objs2.addAll(obj); 47 | } 48 | } 49 | 50 | System.out.println((System.nanoTime() - start) / 1e9); 51 | System.out.println(objs.equals(objs2)); 52 | //System.out.println(TestSizeUtils.formattedSize(objs2)); 53 | TestSizeUtils.dump(objs); 54 | } 55 | 56 | @Test 57 | public void name() throws IOException { 58 | ArrayList list = new ArrayList<>(); 59 | 60 | for (int i = 0; i < 1000; i++) { 61 | LinkedHashMap map = new LinkedHashMap<>(); 62 | map.put("key1", "value" + i); 63 | map.put("key2", i); 64 | map.put("key3", (double) (i / 100)); 65 | list.add(map); 66 | } 67 | 68 | ObjectOptimizer optimizer = new ObjectOptimizer(new DefaultObjectCache()); 69 | TinyList tinyList = optimizer.optimizeList(list); 70 | 71 | 72 | // TestSizeUtils.dump(tinyList); 73 | TestSizeUtils.dump(new JavaOptimizer(null).optimizeList(tinyList)); 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/StringCacheTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.util.DefaultObjectCache; 4 | import org.junit.Test; 5 | 6 | import java.util.stream.Collectors; 7 | import java.util.stream.IntStream; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class StringCacheTest { 12 | 13 | @Test 14 | public void testCacheHit() { 15 | ObjectCache cache = new DefaultObjectCache(); 16 | StringBuilder original = new StringBuilder("abcde"); 17 | String cached1 = cache.get(original); 18 | String cached2 = cache.get(original); 19 | 20 | assertThat(original.toString()).isEqualTo(cached1).isNotSameAs(cached1); 21 | assertThat(original.toString()).isEqualTo(cached2).isNotSameAs(cached2); 22 | 23 | assertThat(cached1).isSameAs(cached2); 24 | } 25 | 26 | @Test 27 | public void testSpecialCases() { 28 | ObjectCache cache = new DefaultObjectCache(); 29 | 30 | String original1 = IntStream.range(0, 1025).mapToObj(x -> "x").collect(Collectors.joining()); 31 | String original2 = IntStream.range(0, 1025).mapToObj(x -> "x").collect(Collectors.joining()); 32 | String cached1 = cache.get(original1); 33 | String cached2 = cache.get(original2); 34 | 35 | assertThat(cached1).isSameAs(cached2); 36 | } 37 | 38 | @Test 39 | public void testEmptyStrings() { 40 | ObjectCache cache = new DefaultObjectCache(); 41 | 42 | assertThat(cache.get("")).isSameAs(cache.get("")); 43 | assertThat(cache.get((CharSequence) null)).isEqualTo(null); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinyListBuilderTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.support.ListAsserts; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.ListIterator; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.IntStream; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 14 | 15 | public class TinyListBuilderTest { 16 | 17 | @Test 18 | public void addIndexedOnLast() throws Exception { 19 | TinyListBuilder builder = new TinyListBuilder<>(); 20 | ArrayList expectedMap = new ArrayList<>(); 21 | 22 | expectedMap.add(0, "aaa0"); 23 | builder.add(0, "aaa0"); 24 | expectedMap.add(0, "aaa1"); 25 | builder.add(0, "aaa1"); 26 | 27 | ListAsserts.assertList(expectedMap, builder); 28 | } 29 | 30 | @Test 31 | public void cantRemoveWithoutIteration() { 32 | ArrayList expected = new ArrayList<>(); 33 | TinyListBuilder builder = new TinyListBuilder<>(); 34 | 35 | for (int i = 0; i < 10; i++) { 36 | expected.add("aaa" + i); 37 | builder.add("aaa" + i); 38 | } 39 | 40 | Iterator expectedIt = expected.listIterator(); 41 | assertThatThrownBy(expectedIt::remove) 42 | .isInstanceOf(IllegalStateException.class); 43 | 44 | ListIterator it = builder.iterator(); 45 | assertThatThrownBy(it::remove) 46 | .isInstanceOf(IllegalStateException.class); 47 | 48 | } 49 | 50 | @Test 51 | public void testRemoveAll() throws Exception { 52 | TinyListBuilder builder = new TinyListBuilder<>(); 53 | ArrayList expected = new ArrayList<>(); 54 | 55 | for (int i = 0; i < 100; i++) { 56 | assertThat(builder.add("aaa" + i)).isTrue(); 57 | assertThat(expected.add("aaa" + i)).isTrue(); 58 | } 59 | 60 | builder.removeAll(IntStream.range(10, 20).mapToObj(x -> "aaa" + x).collect(Collectors.toList())); 61 | expected.removeAll(IntStream.range(10, 20).mapToObj(x -> "aaa" + x).collect(Collectors.toList())); 62 | 63 | ListAsserts.assertList(expected, builder); 64 | } 65 | 66 | @Test 67 | public void testBuildEmpty() throws Exception { 68 | assertListWithCount(0, false); 69 | assertListWithCount(0, true); 70 | } 71 | 72 | @Test 73 | public void testBuildMedium() throws Exception { 74 | assertListWithCount(1000, false); 75 | assertListWithCount(1000, true); 76 | assertListWithCount(1000, true, 200, 300); 77 | } 78 | 79 | @Test 80 | public void testBuildAlmostThere() throws Exception { 81 | assertListWithCount(255, false); 82 | assertListWithCount(255, true); 83 | assertListWithCount(1000, true, 123, 234); 84 | } 85 | 86 | 87 | private void assertListWithCount(int count, boolean withNull) throws Exception { 88 | assertListWithCount(count, withNull, 0, 0); 89 | } 90 | 91 | private void assertListWithCount(int count, boolean withNull, int removeFrom, int removeTo) throws Exception { 92 | TinyListBuilder builder = new TinyListBuilder<>(); 93 | ArrayList expectedMap = new ArrayList<>(); 94 | 95 | listIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 96 | if (count < 1000) { 97 | builder.clear(); 98 | expectedMap.clear(); 99 | listIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 100 | } 101 | } 102 | 103 | private void listIteration(int count, boolean withNull, int removeFrom, int removeTo, TinyListBuilder builder, ArrayList expectedMap) throws Exception { 104 | for (int i = 0; i < count; i++) 105 | expectedMap.add("aaa" + i); 106 | builder.addAll(expectedMap); 107 | 108 | if (withNull) { 109 | builder.add(null); 110 | expectedMap.add(null); 111 | } 112 | 113 | for (int i = removeFrom; i < removeTo; i++) { 114 | builder.remove("aaa" + i); 115 | expectedMap.remove("aaa" + i); 116 | } 117 | 118 | ListAsserts.assertList(expectedMap, builder); 119 | ListAsserts.assertList(expectedMap, builder.build()); 120 | } 121 | 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinyListTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.introspective.reflect.ReflectionCache; 4 | import net.intelie.tinymap.support.SerializationHelper; 5 | import org.junit.Test; 6 | import org.mockito.InOrder; 7 | 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.function.Consumer; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 16 | import static org.mockito.Mockito.inOrder; 17 | import static org.mockito.Mockito.mock; 18 | 19 | public class TinyListTest { 20 | @Test 21 | public void testSizes() { 22 | ReflectionCache reflection = new ReflectionCache(); 23 | assertThat(reflection.get(TinyList.class).size()).isEqualTo(16); 24 | } 25 | 26 | @Test 27 | public void testIndexOfAndLastIndexOf() { 28 | TinyListBuilder builder = TinyList.builder(); 29 | builder.add("aaa"); 30 | builder.add("bbb"); 31 | builder.add("bbb"); 32 | builder.add("bbb"); 33 | builder.add("ccc"); 34 | 35 | assertThat(builder.indexOf("bbb")).isEqualTo(builder.build().indexOf("bbb")).isEqualTo(1); 36 | assertThat(builder.lastIndexOf("bbb")).isEqualTo(builder.build().lastIndexOf("bbb")).isEqualTo(3); 37 | 38 | assertThat(builder.indexOf("xxx")).isEqualTo(builder.build().indexOf("xxx")).isEqualTo(-1); 39 | assertThat(builder.lastIndexOf("xxx")).isEqualTo(builder.build().lastIndexOf("xxx")).isEqualTo(-1); 40 | } 41 | 42 | @Test 43 | public void testBuilderHashCode() { 44 | TinyListBuilder builder1 = TinyList.builder(); 45 | TinyListBuilder builder2 = TinyList.builder(); 46 | 47 | TinyListBuilder.Adapter adapter = builder1.adapter(); 48 | 49 | assertThat(adapter.contentHashCode(builder1)).isEqualTo(adapter.contentHashCode(builder2)); 50 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 51 | 52 | builder1.add("aaa"); 53 | builder2.add("aaa"); 54 | 55 | assertThat(adapter.contentHashCode(builder1)).isEqualTo(adapter.contentHashCode(builder2)); 56 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 57 | 58 | builder1.add("bbb"); 59 | 60 | assertThat(adapter.contentHashCode(builder1)).isNotEqualTo(adapter.contentHashCode(builder2)); 61 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 62 | 63 | builder2.add("ccc"); 64 | 65 | assertThat(adapter.contentHashCode(builder1)).isNotEqualTo(adapter.contentHashCode(builder2)); 66 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 67 | } 68 | 69 | @Test 70 | public void testBuildAndGet() { 71 | TinyListBuilder builder = TinyList.builder(); 72 | assertThat(builder.size()).isEqualTo(0); 73 | builder.add("aaa"); 74 | builder.add(123); 75 | assertThat(builder.size()).isEqualTo(2); 76 | List list = builder.buildAndClear(); 77 | 78 | assertThat(list.get(0)).isEqualTo("aaa"); 79 | assertThat(list.get(1)).isEqualTo(123); 80 | assertThatThrownBy(() -> list.get(2)).isInstanceOf(IndexOutOfBoundsException.class); 81 | assertThat(list.size()).isEqualTo(2); 82 | 83 | assertThat(list).containsExactly("aaa", 123); 84 | } 85 | 86 | @Test 87 | public void testForEach() { 88 | TinyListBuilder builder = TinyList.builder(); 89 | builder.add("aaa"); 90 | builder.add(123); 91 | List map = builder.buildAndClear(); 92 | 93 | @SuppressWarnings("unchecked") 94 | Consumer consumer = mock(Consumer.class); 95 | 96 | map.forEach(consumer); 97 | 98 | InOrder orderly = inOrder(consumer); 99 | orderly.verify(consumer).accept("aaa"); 100 | orderly.verify(consumer).accept(123); 101 | orderly.verifyNoMoreInteractions(); 102 | } 103 | 104 | @Test 105 | public void testBuildEmpty() throws IOException, ClassNotFoundException { 106 | testCount(0); 107 | } 108 | 109 | @Test 110 | public void testBuildGiant() throws IOException, ClassNotFoundException { 111 | testCount(1000); 112 | } 113 | 114 | @Test 115 | public void testBuildAlmostThere() throws IOException, ClassNotFoundException { 116 | testCount(255); 117 | } 118 | 119 | private void testCount(int count) throws IOException, ClassNotFoundException { 120 | TinyListBuilder builder = TinyList.builder(); 121 | ArrayList expected = new ArrayList<>(); 122 | for (int i = 0; i < count; i++) { 123 | builder.add("aaa" + i); 124 | expected.add("aaa" + i); 125 | } 126 | TinyList list = builder.buildAndClear(); 127 | 128 | 129 | assertThat(list.size()).isEqualTo(expected.size()); 130 | for (int i = 0; i < expected.size(); i++) { 131 | assertThat(list.get(i)).isEqualTo(expected.get(i)); 132 | } 133 | assertThat(list.toString()).isEqualTo(expected.toString()); 134 | 135 | assertThat(list).isEqualTo(expected); 136 | assertThat(expected).isEqualTo(list); 137 | assertThat(list.hashCode()).isEqualTo(expected.hashCode()); 138 | 139 | byte[] serialized = SerializationHelper.testSerialize(list); 140 | byte[] serializedExpected = SerializationHelper.testSerialize(expected); 141 | if (expected.size() > 0) 142 | assertThat(serialized.length).isLessThan(2 * serializedExpected.length); 143 | 144 | List deserialized = SerializationHelper.testDeserialize(serialized); 145 | assertThat(deserialized).isEqualTo(list); 146 | } 147 | 148 | @Test 149 | public void immutableIsImmutable() { 150 | TinyListBuilder builder = TinyList.builder(); 151 | builder.add("aaa"); 152 | TinyList map = builder.build(); 153 | 154 | assertThatThrownBy(() -> map.clear()).isInstanceOf(UnsupportedOperationException.class); 155 | assertThatThrownBy(() -> map.remove("aaa")).isInstanceOf(UnsupportedOperationException.class); 156 | assertThatThrownBy(() -> map.addAll(Collections.singleton("abc"))).isInstanceOf(UnsupportedOperationException.class); 157 | assertThatThrownBy(() -> map.add("abc")).isInstanceOf(UnsupportedOperationException.class); 158 | assertThatThrownBy(() -> map.set(42, "abc")).isInstanceOf(UnsupportedOperationException.class); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinyMapBuilderAdapterTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.util.DefaultObjectCache; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | public class TinyMapBuilderAdapterTest { 9 | @Test 10 | public void testBuilderHashCode() { 11 | TinyMapBuilder builder1 = new TinyMapBuilder<>(); 12 | TinyMapBuilder builder2 = new TinyMapBuilder<>(); 13 | 14 | TinyMapBuilder.Adapter adapter = builder1.adapter(); 15 | 16 | assertThat(adapter.contentHashCode(builder1)).isEqualTo(adapter.contentHashCode(builder2)); 17 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 18 | 19 | builder1.put("aaa", 111); 20 | builder2.put("aaa", 111); 21 | 22 | assertThat(adapter.contentHashCode(builder1)).isEqualTo(adapter.contentHashCode(builder2)); 23 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 24 | 25 | builder1.put("bbb", 222); 26 | 27 | assertThat(adapter.contentHashCode(builder1)).isNotEqualTo(adapter.contentHashCode(builder2)); 28 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 29 | 30 | builder2.put("bbb", 333); 31 | 32 | assertThat(adapter.contentHashCode(builder1)).isNotEqualTo(adapter.contentHashCode(builder2)); 33 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 34 | } 35 | 36 | @Test 37 | public void testContentEqualWithDuplicateKeys() { 38 | TinyMapBuilder builder1 = new TinyMapBuilder<>(); 39 | TinyMapBuilder builder2 = new TinyMapBuilder<>(); 40 | TinyMapBuilder.Adapter adapter = builder1.adapter(); 41 | 42 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 43 | String aaa1 = "aaa"; 44 | String aaa2 = "aaa"; 45 | String bbb = "bbb"; 46 | 47 | Integer v111 = 111; 48 | Integer v222 = 222; 49 | Integer v333 = 333; 50 | 51 | builder1.put(aaa1, v111); 52 | builder2.put(aaa1, v111); 53 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 54 | 55 | builder1.put(bbb, v222); 56 | builder2.put(bbb, v222); 57 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 58 | 59 | builder1.put(aaa2, v333); 60 | builder2.put(aaa2, v333); 61 | 62 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 63 | } 64 | 65 | @Test 66 | public void testBuildSmallWithCache() { 67 | ObjectCache cache = new DefaultObjectCache(); 68 | 69 | TinyMapBuilder builder1 = new TinyMapBuilder<>(); 70 | builder1.put("aaa", 111); 71 | builder1.put("bbb", 222); 72 | TinyMap map1 = cache.get(builder1); 73 | 74 | TinyMapBuilder builder2 = new TinyMapBuilder<>(); 75 | builder2.put("aaa", 333); 76 | builder2.put("bbb", 444); 77 | TinyMap map2 = cache.get(builder2); 78 | 79 | assertThat(map1.sharesKeysWith(map2)).isTrue(); 80 | } 81 | 82 | @Test 83 | public void testBuildExactlySameWithCache() { 84 | ObjectCache cache = new DefaultObjectCache(); 85 | 86 | TinyMapBuilder builder1 = new TinyMapBuilder<>(); 87 | for (int i = 0; i < 100; i++) 88 | builder1.put(cache.get("aaa" + i), cache.get(1000 * i)); 89 | TinyMap map1 = cache.get(builder1); 90 | 91 | TinyMapBuilder builder2 = new TinyMapBuilder<>(); 92 | for (int i = 0; i < 100; i++) 93 | builder2.put(cache.get("aaa" + i), cache.get(1000 * i)); 94 | TinyMap map2 = cache.get(builder2); 95 | 96 | assertThat(map1).isSameAs(map2); 97 | } 98 | 99 | @Test 100 | public void testBuildMediumWithCache() { 101 | ObjectCache cache = new DefaultObjectCache(1 << 20); 102 | 103 | TinyMapBuilder builder1 = new TinyMapBuilder<>(); 104 | for (int i = 0; i < 1000; i++) 105 | builder1.put(cache.get("aaa" + i), 1000 * i); 106 | TinyMap map1 = cache.get(builder1); 107 | 108 | TinyMapBuilder builder2 = new TinyMapBuilder<>(); 109 | for (int i = 0; i < 1000; i++) 110 | builder2.put(cache.get("aaa" + i), 2000 * i); 111 | TinyMap map2 = cache.get(builder2); 112 | 113 | assertThat(map1.sharesKeysWith(map2)).isTrue(); 114 | } 115 | } -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinyMapBuilderTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.base.IndexedMap; 4 | import net.intelie.tinymap.support.MapAsserts; 5 | import net.intelie.tinymap.support.SerializationHelper; 6 | import org.junit.Test; 7 | 8 | import java.util.Collections; 9 | import java.util.Iterator; 10 | import java.util.LinkedHashMap; 11 | import java.util.Map; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | public class TinyMapBuilderTest { 16 | @Test 17 | public void testAddAndGet() { 18 | TinyMapBuilder builder = TinyMap.builder(); 19 | 20 | assertThat(builder.put("abc", 123)).isNull(); 21 | assertThat(builder.put("abc", 456)).isEqualTo(123); 22 | 23 | assertThat(builder.size()).isEqualTo(1); 24 | assertThat(builder.containsKey("abc")).isTrue(); 25 | assertThat(builder.get("abc")).isEqualTo(456); 26 | 27 | assertThat(builder.build()).isEqualTo(Collections.singletonMap("abc", 456)); 28 | assertThat(builder.build()).isEqualTo(Collections.singletonMap("abc", 456)); 29 | } 30 | 31 | @Test 32 | public void testRemovedEntry() { 33 | TinyMapBuilder builder = TinyMap.builder(); 34 | for (int i = 0; i < 10; i++) { 35 | builder.put("aaa" + i, i); 36 | } 37 | 38 | builder.removeAt(5); 39 | 40 | IndexedMap.Entry entry = builder.getEntryAt(5); 41 | assertThat(entry.getKey().toString()).isEqualTo("TOMBSTONE"); 42 | assertThat(entry.getValue().toString()).isEqualTo("TOMBSTONE"); 43 | assertThat(entry.isRemoved()).isTrue(); 44 | } 45 | 46 | @Test 47 | public void testAddAndRemove() { 48 | TinyMapBuilder builder = new TinyMapBuilder<>(); 49 | 50 | for (int i = 0; i < 100; i++) 51 | assertThat(builder.put("aaa" + i, i)).isNull(); 52 | for (int i = 0; i < 100; i++) 53 | assertThat(builder.containsKey("aaa" + i)).isTrue(); 54 | 55 | assertThat(builder.size()).isEqualTo(100); 56 | 57 | for (int i = 0; i < 100; i += 2) 58 | assertThat(builder.remove("aaa" + i)).isEqualTo(i); 59 | 60 | for (int i = 0; i < 100; i++) { 61 | assertThat(builder.containsKey("aaa" + i)).isEqualTo(i % 2 != 0); 62 | } 63 | 64 | assertThat(builder.size()).isEqualTo(50); 65 | assertThat(builder.build().size()).isEqualTo(50); 66 | } 67 | 68 | @Test 69 | public void testIteratorChanges() throws Exception { 70 | TinyMapBuilder builder = new TinyMapBuilder<>(); 71 | LinkedHashMap expected = new LinkedHashMap<>(); 72 | 73 | for (int i = 0; i < 100; i++) { 74 | assertThat(builder.put("aaa" + i, i)).isNull(); 75 | assertThat(expected.put("aaa" + i, i)).isNull(); 76 | } 77 | 78 | Iterator> it1 = builder.entrySet().iterator(); 79 | Iterator it2 = builder.values().iterator(); 80 | Iterator it3 = builder.keySet().iterator(); 81 | for (int i = 0; i < 10; i++) { 82 | it1.next(); 83 | it2.next(); 84 | it3.next(); 85 | } 86 | for (int i = 10; i < 20; i++) { 87 | it1.next(); 88 | it2.next(); 89 | it3.next(); 90 | it1.remove(); 91 | expected.remove("aaa" + i); 92 | } 93 | for (int i = 20; i < 30; i++) { 94 | it1.next(); 95 | it2.next(); 96 | it3.next(); 97 | it2.remove(); 98 | expected.remove("aaa" + i); 99 | } 100 | for (int i = 30; i < 40; i++) { 101 | it1.next(); 102 | it2.next(); 103 | it3.next(); 104 | it3.remove(); 105 | expected.remove("aaa" + i); 106 | } 107 | for (int i = 40; i < 50; i++) { 108 | it1.next().setValue("x" + i); 109 | expected.put("aaa" + i, "x" + i); 110 | } 111 | 112 | MapAsserts.assertMap(expected, builder, 10, 40); 113 | } 114 | 115 | @Test 116 | public void testBuildEmpty() throws Exception { 117 | assertMapWithCount(0, false); 118 | assertMapWithCount(0, true); 119 | } 120 | 121 | @Test 122 | public void testBuildMedium() throws Exception { 123 | assertMapWithCount(1000, false); 124 | assertMapWithCount(1000, true); 125 | assertMapWithCount(1000, true, 200, 500); 126 | } 127 | 128 | @Test 129 | public void testBuildAlmostThere() throws Exception { 130 | assertMapWithCount(255, false); 131 | assertMapWithCount(255, true); 132 | assertMapWithCount(255, true, 100, 200); 133 | } 134 | 135 | @Test 136 | public void testTombstone() throws Exception { 137 | assertThat(TinyMapBuilder.TOMBSTONE.toString()).isEqualTo("TOMBSTONE"); 138 | SerializationHelper.roundTrip(TinyMapBuilder.TOMBSTONE); 139 | } 140 | 141 | private void assertMapWithCount(int count, boolean withNull) throws Exception { 142 | assertMapWithCount(count, withNull, 0, 0); 143 | } 144 | 145 | private void assertMapWithCount(int count, boolean withNull, int removeFrom, int removeTo) throws Exception { 146 | TinyMapBuilder builder = new TinyMapBuilder<>(); 147 | LinkedHashMap expectedMap = new LinkedHashMap<>(); 148 | 149 | mapIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 150 | if (count < 1000) { 151 | builder.clear(); 152 | expectedMap.clear(); 153 | mapIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 154 | 155 | builder.entrySet().clear(); 156 | expectedMap.entrySet().clear(); 157 | mapIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 158 | 159 | builder.values().clear(); 160 | expectedMap.values().clear(); 161 | mapIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 162 | 163 | builder.keySet().clear(); 164 | expectedMap.keySet().clear(); 165 | mapIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 166 | } 167 | } 168 | 169 | private void mapIteration(int count, boolean withNull, int removeFrom, int removeTo, TinyMapBuilder builder, LinkedHashMap expectedMap) throws Exception { 170 | for (int i = 0; i < count; i++) 171 | expectedMap.put("aaa" + i, i); 172 | builder.putAll(expectedMap); 173 | 174 | if (withNull) { 175 | builder.put(null, null); 176 | expectedMap.put(null, null); 177 | } 178 | 179 | for (int i = removeFrom; i < removeTo; i++) { 180 | builder.remove("aaa" + i); 181 | expectedMap.remove("aaa" + i); 182 | } 183 | 184 | MapAsserts.assertMap(expectedMap, builder, removeFrom, removeTo); 185 | MapAsserts.assertMap(expectedMap, builder.build(), 0, 0); 186 | builder.compact(); 187 | MapAsserts.assertMap(expectedMap, builder, 0, 0); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinyMapTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import net.intelie.introspective.reflect.ReflectionCache; 5 | import net.intelie.tinymap.support.MapAsserts; 6 | import org.junit.Test; 7 | import org.mockito.InOrder; 8 | 9 | import java.util.AbstractMap; 10 | import java.util.Collections; 11 | import java.util.LinkedHashMap; 12 | import java.util.function.BiConsumer; 13 | 14 | import static org.assertj.core.api.Assertions.*; 15 | import static org.mockito.Mockito.inOrder; 16 | import static org.mockito.Mockito.mock; 17 | 18 | public class TinyMapTest { 19 | @Test 20 | public void testSizes() { 21 | ReflectionCache reflection = new ReflectionCache(); 22 | assertThat(reflection.get(TinyMap.class).size()).isEqualTo(16); 23 | } 24 | 25 | @Test 26 | public void testBuildAndGet() { 27 | TinyMapBuilder builder = TinyMap.builder(); 28 | assertThat(builder.size()).isEqualTo(0); 29 | builder.put("aaa", 333); 30 | builder.put("bbb", 456.0); 31 | builder.put("aaa", 123); 32 | assertThat(builder.size()).isEqualTo(2); 33 | TinyMap map = builder.build(); 34 | 35 | assertThat(map.get("aaa")).isEqualTo(123); 36 | assertThat(map.get("bbb")).isEqualTo(456.0); 37 | assertThat(map.get("ccc")).isNull(); 38 | assertThat(map.getOrDefault("ccc", "def")).isEqualTo("def"); 39 | assertThat(map.getOrDefault(null, "def")).isEqualTo("def"); 40 | 41 | assertThat(map.size()).isEqualTo(2); 42 | assertThat(map).containsExactly( 43 | entry("aaa", 123), 44 | entry("bbb", 456.0) 45 | ); 46 | assertThat(map.values()).containsExactly(123, 456.0); 47 | assertThat(map.keySet()).containsExactly("aaa", "bbb"); 48 | } 49 | 50 | @Test 51 | public void canBuildWithDuplicateKeys() { 52 | TinyMapBuilder builder = TinyMap.builder(); 53 | builder.put("aaa", 123); 54 | builder.put("aaa", 456.0); 55 | builder.put("bbb", 789.0); 56 | 57 | assertThat(builder.size()).isEqualTo(2); 58 | assertThat(builder.build()).isEqualTo(ImmutableMap.of("aaa", 456.0, "bbb", 789.0)); 59 | 60 | assertThat(builder.size()).isEqualTo(2); 61 | assertThat(builder.build()).isEqualTo(ImmutableMap.of("aaa", 456.0, "bbb", 789.0)); 62 | } 63 | 64 | 65 | @Test 66 | public void canBuildWithNull() { 67 | TinyMapBuilder builder = TinyMap.builder(); 68 | builder.put(null, 123); 69 | assertThat(builder.build()).isEqualTo(Collections.singletonMap(null, 123)); 70 | } 71 | 72 | @Test 73 | public void canBuildMediumWithDuplicateKeys() { 74 | TinyMapBuilder builder = TinyMap.builder(); 75 | builder.put("aaa", 123); 76 | builder.put("aaa", 456.0); 77 | for (int i = 0; i < 1000; i++) { 78 | builder.put("aaa" + i, i); 79 | } 80 | assertThat(builder.build().size()).isEqualTo(1001); 81 | } 82 | 83 | @Test 84 | public void canBuildLargeWithDuplicateKeys() { 85 | TinyMapBuilder builder = TinyMap.builder(); 86 | builder.put("aaa", 123); 87 | builder.put("aaa", 456.0); 88 | for (int i = 0; i < 0x10000; i++) { 89 | builder.put("aaa" + i, i); 90 | } 91 | assertThat(builder.build().size()).isEqualTo(65537); 92 | } 93 | 94 | @Test 95 | public void testForEach() { 96 | TinyMapBuilder builder = TinyMap.builder(); 97 | builder.put("aaa", 123); 98 | builder.put("bbb", 456.0); 99 | TinyMap map = builder.build(); 100 | 101 | @SuppressWarnings("unchecked") 102 | BiConsumer consumer = mock(BiConsumer.class); 103 | 104 | map.forEach(consumer); 105 | 106 | InOrder orderly = inOrder(consumer); 107 | orderly.verify(consumer).accept("aaa", 123); 108 | orderly.verify(consumer).accept("bbb", 456.0); 109 | orderly.verifyNoMoreInteractions(); 110 | } 111 | 112 | @Test 113 | public void testContains() { 114 | TinyMapBuilder builder = TinyMap.builder(); 115 | builder.put("aaa", null); 116 | builder.put("bbb", 456.0); 117 | 118 | TinyMap map = builder.build(); 119 | 120 | assertThat(map.containsKey("aaa")).isTrue(); 121 | assertThat(map.containsKey("ccc")).isFalse(); 122 | 123 | assertThat(map.containsValue(null)).isTrue(); 124 | assertThat(map.containsValue(123.0)).isFalse(); 125 | 126 | assertThat(map.keySet().contains("aaa")).isTrue(); 127 | assertThat(map.keySet().contains("ccc")).isFalse(); 128 | 129 | assertThat(map.values().contains(null)).isTrue(); 130 | assertThat(map.values().contains(123.0)).isFalse(); 131 | 132 | assertThat(map.entrySet().contains(new AbstractMap.SimpleEntry<>("aaa", null))).isTrue(); 133 | assertThat(map.entrySet().contains(new AbstractMap.SimpleEntry<>("aaa", 123.0))).isFalse(); 134 | assertThat(map.entrySet().contains(new AbstractMap.SimpleEntry<>("ccc", null))).isFalse(); 135 | assertThat(map.entrySet().contains(new Object())).isFalse(); 136 | } 137 | 138 | @Test 139 | public void testBuildSmallEnough() throws Exception { 140 | testCount(0, true); 141 | 142 | for (int i = 0; i <= 16; i++) { 143 | testCount(i, false); 144 | } 145 | } 146 | 147 | @Test 148 | public void testBuildMedium() throws Exception { 149 | testCount(1000, false); 150 | testCount(1000, true); 151 | } 152 | 153 | @Test 154 | public void testBuildAlmostThere() throws Exception { 155 | testCount(255, false); 156 | testCount(255, true); 157 | } 158 | 159 | @Test 160 | public void testBuildSmall() throws Exception { 161 | testCount(123, false); 162 | testCount(123, true); 163 | } 164 | 165 | @Test 166 | public void testValueArrayTwoDifferentMaps() { 167 | TinyMapBuilder builder1 = TinyMap.builder(); 168 | TinyMapBuilder builder2 = TinyMap.builder(); 169 | for (int i = 0; i < 100; i++) { 170 | builder1.put("aaa" + i, i); 171 | builder2.put("aaa" + i, i); 172 | } 173 | 174 | TinyMap map1 = builder1.build(); 175 | TinyMap map2 = builder2.build(); 176 | 177 | assertThat(map1.values()).containsExactlyElementsOf(map2.values()); 178 | assertThat(map1.keySet()).isEqualTo(map2.keySet()); 179 | } 180 | 181 | @Test 182 | public void testGiantShortProblem() { 183 | TinyMapBuilder builder = TinyMap.builder(); 184 | 185 | for (int i = 0; i < 100000; i++) { 186 | builder.put("aaa" + i, i); 187 | } 188 | 189 | TinyMap map = builder.build(); 190 | 191 | assertThat(map.get("aaa99999")).isEqualTo(99999); 192 | } 193 | 194 | @Test 195 | public void testMaxCollisions() { 196 | TinyMapBuilder builder = TinyMap.builder(); 197 | 198 | for (int count = 0; count < 1000; count += 20) 199 | testCollisions(builder, count); 200 | } 201 | 202 | private void testCollisions(TinyMapBuilder builder, int count) { 203 | while (builder.size() < count) 204 | builder.put("aaa" + builder.size(), builder.size()); 205 | 206 | TinyMap map = builder.build(); 207 | map.debugCollisions("abcdef"); 208 | 209 | long total = 0; 210 | for (int i = 0; i < count; i++) { 211 | total += map.debugCollisions("aaa" + i); 212 | } 213 | long totalNonExisting = 0; 214 | for (int i = 0; i < count; i++) { 215 | totalNonExisting += map.debugCollisions("bbb" + i); 216 | } 217 | //System.out.println(count + "\t" + (total / (double) count) + "\t" + (totalNonExisting / (double) count)); 218 | assertThat(count == 0 ? 0 : total / (double) count).isLessThan(1); 219 | assertThat(count == 0 ? 0 : totalNonExisting / (double) count).isLessThan(5); 220 | } 221 | 222 | private void testCount(int count, boolean withNull) throws Exception { 223 | TinyMapBuilder builder = TinyMap.builder(); 224 | LinkedHashMap expectedMap = new LinkedHashMap<>(); 225 | 226 | for (int i = 0; i < count; i++) { 227 | if (count < 1000) 228 | builder.build(); 229 | 230 | builder.putAll(Collections.singletonMap("aaa" + i, i)); 231 | expectedMap.put("aaa" + i, i); 232 | } 233 | if (withNull) { 234 | builder.put(null, null); 235 | expectedMap.put(null, null); 236 | count++; 237 | } 238 | 239 | TinyMap map = builder.build(); 240 | 241 | MapAsserts.assertMap(expectedMap, map, 0, 0); 242 | } 243 | 244 | @Test 245 | public void immutableIsImmutable() { 246 | TinyMapBuilder builder = TinyMap.builder(); 247 | builder.put("aaa", 111); 248 | TinyMap map = builder.build(); 249 | 250 | assertThatThrownBy(() -> map.clear()).isInstanceOf(UnsupportedOperationException.class); 251 | assertThatThrownBy(() -> map.remove("aaa")).isInstanceOf(UnsupportedOperationException.class); 252 | assertThatThrownBy(() -> map.putAll(Collections.singletonMap("abc", 123))).isInstanceOf(UnsupportedOperationException.class); 253 | assertThatThrownBy(() -> map.put("abc", 123)).isInstanceOf(UnsupportedOperationException.class); 254 | assertThatThrownBy(() -> map.setValueAt(0, 123)).isInstanceOf(UnsupportedOperationException.class); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinySetBuilderAdapterTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.util.DefaultObjectCache; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | public class TinySetBuilderAdapterTest { 9 | @Test 10 | public void testBuilderHashCode() { 11 | TinySetBuilder builder1 = new TinySetBuilder<>(); 12 | TinySetBuilder builder2 = new TinySetBuilder<>(); 13 | 14 | TinySetBuilder.Adapter adapter = builder1.adapter(); 15 | 16 | assertThat(adapter.contentHashCode(builder1)).isEqualTo(adapter.contentHashCode(builder2)); 17 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 18 | 19 | builder1.add("aaa"); 20 | builder2.add("aaa"); 21 | 22 | assertThat(adapter.contentHashCode(builder1)).isEqualTo(adapter.contentHashCode(builder2)); 23 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 24 | 25 | builder1.add("bbb"); 26 | 27 | assertThat(adapter.contentHashCode(builder1)).isNotEqualTo(adapter.contentHashCode(builder2)); 28 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 29 | 30 | builder2.add("ccc"); 31 | 32 | assertThat(adapter.contentHashCode(builder1)).isNotEqualTo(adapter.contentHashCode(builder2)); 33 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 34 | } 35 | 36 | @Test 37 | public void testContentEqualWithDuplicateKeys() { 38 | TinySetBuilder builder1 = new TinySetBuilder<>(); 39 | TinySetBuilder builder2 = new TinySetBuilder<>(); 40 | TinySetBuilder.Adapter adapter = builder1.adapter(); 41 | 42 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 43 | String aaa = "aaa"; 44 | String bbb1 = "bbb"; 45 | String bbb2 = new String("bbb"); 46 | 47 | 48 | builder1.add(aaa); 49 | builder2.add(aaa); 50 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNotNull(); 51 | 52 | builder1.add(bbb1); 53 | builder2.add(bbb2); 54 | assertThat(adapter.contentEquals(builder1, builder2.build())).isNull(); 55 | } 56 | 57 | @Test 58 | public void testBuildSmallWithCache() { 59 | ObjectCache cache = new DefaultObjectCache(); 60 | 61 | TinySetBuilder builder1 = new TinySetBuilder<>(); 62 | builder1.add("aaa"); 63 | builder1.add("bbb"); 64 | TinySet map1 = cache.get(builder1); 65 | 66 | TinySetBuilder builder2 = new TinySetBuilder<>(); 67 | builder2.add("aaa"); 68 | builder2.add("bbb"); 69 | TinySet map2 = cache.get(builder2); 70 | 71 | assertThat(map1).isSameAs(map2); 72 | } 73 | 74 | @Test 75 | public void testBuildExactlySameWithCache() { 76 | ObjectCache cache = new DefaultObjectCache(1 << 20); 77 | 78 | TinySetBuilder builder1 = new TinySetBuilder<>(); 79 | for (int i = 0; i < 1000; i++) 80 | builder1.add(cache.get("aaa" + i)); 81 | TinySet map1 = cache.get(builder1); 82 | 83 | TinySetBuilder builder2 = new TinySetBuilder<>(); 84 | for (int i = 0; i < 1000; i++) 85 | builder2.add(cache.get("aaa" + i)); 86 | TinySet map2 = cache.get(builder2); 87 | 88 | assertThat(map1).isSameAs(map2); 89 | } 90 | } -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinySetBuilderTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import net.intelie.tinymap.support.SetAsserts; 4 | import org.junit.Test; 5 | 6 | import java.util.Collections; 7 | import java.util.Iterator; 8 | import java.util.LinkedHashSet; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.IntStream; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 14 | 15 | public class TinySetBuilderTest { 16 | @Test 17 | public void testAddAndGet() { 18 | TinySetBuilder builder = TinySet.builder(); 19 | 20 | assertThat(builder.add("abc")).isTrue(); 21 | assertThat(builder.add("abc")).isFalse(); 22 | 23 | assertThat(builder.size()).isEqualTo(1); 24 | assertThat(builder.contains("abc")).isTrue(); 25 | assertThat(builder.getIndex("abc")).isEqualTo(0); 26 | 27 | assertThat(builder.build()).isEqualTo(Collections.singleton("abc")); 28 | assertThat(builder.build()).isEqualTo(Collections.singleton("abc")); 29 | } 30 | 31 | @Test 32 | public void testAddAndRemove() { 33 | TinySetBuilder builder = new TinySetBuilder<>(); 34 | 35 | for (int i = 0; i < 100; i++) 36 | assertThat(builder.add("aaa" + i)).isTrue(); 37 | for (int i = 0; i < 100; i++) 38 | assertThat(builder.contains("aaa" + i)).isTrue(); 39 | 40 | assertThat(builder.size()).isEqualTo(100); 41 | 42 | for (int i = 0; i < 100; i += 2) 43 | assertThat(builder.remove("aaa" + i)).isTrue(); 44 | 45 | for (int i = 0; i < 100; i++) { 46 | assertThat(builder.contains("aaa" + i)).isEqualTo(i % 2 != 0); 47 | } 48 | 49 | assertThat(builder.size()).isEqualTo(50); 50 | assertThat(builder.build().size()).isEqualTo(50); 51 | } 52 | 53 | @Test 54 | public void testIteratorChanges() throws Exception { 55 | TinySetBuilder builder = new TinySetBuilder<>(); 56 | LinkedHashSet expected = new LinkedHashSet<>(); 57 | 58 | for (int i = 0; i < 100; i++) { 59 | assertThat(builder.add("aaa" + i)).isTrue(); 60 | assertThat(expected.add("aaa" + i)).isTrue(); 61 | } 62 | 63 | Iterator it = builder.iterator(); 64 | for (int i = 0; i < 10; i++) 65 | it.next(); 66 | for (int i = 10; i < 20; i++) { 67 | it.next(); 68 | it.remove(); 69 | expected.remove("aaa" + i); 70 | } 71 | 72 | SetAsserts.assertSet(expected, builder, 10, 20); 73 | } 74 | 75 | @Test 76 | public void testRemoveAll() throws Exception { 77 | TinySetBuilder builder = new TinySetBuilder<>(); 78 | LinkedHashSet expected = new LinkedHashSet<>(); 79 | 80 | for (int i = 0; i < 100; i++) { 81 | assertThat(builder.add("aaa" + i)).isTrue(); 82 | assertThat(expected.add("aaa" + i)).isTrue(); 83 | } 84 | 85 | builder.removeAll(IntStream.range(10, 20).mapToObj(x -> "aaa" + x).collect(Collectors.toList())); 86 | expected.removeAll(IntStream.range(10, 20).mapToObj(x -> "aaa" + x).collect(Collectors.toList())); 87 | 88 | SetAsserts.assertSet(expected, builder, 10, 20); 89 | } 90 | 91 | @Test 92 | public void testIteratorChangesInBeginning() throws Exception { 93 | TinySetBuilder builder = new TinySetBuilder<>(); 94 | LinkedHashSet expected = new LinkedHashSet<>(); 95 | 96 | for (int i = 0; i < 100; i++) { 97 | assertThat(builder.add("aaa" + i)).isTrue(); 98 | assertThat(expected.add("aaa" + i)).isTrue(); 99 | } 100 | 101 | Iterator it = builder.iterator(); 102 | for (int i = 0; i < 20; i++) { 103 | it.next(); 104 | it.remove(); 105 | expected.remove("aaa" + i); 106 | } 107 | 108 | SetAsserts.assertSet(expected, builder, 0, 20); 109 | } 110 | 111 | @Test 112 | public void testBuildEmpty() throws Exception { 113 | assertSetWithCount(0, false); 114 | assertSetWithCount(0, true); 115 | } 116 | 117 | @Test 118 | public void testBuildMedium() throws Exception { 119 | assertSetWithCount(1000, false); 120 | assertSetWithCount(1000, true); 121 | assertSetWithCount(1000, true, 200, 500); 122 | } 123 | 124 | @Test 125 | public void testBuildAlmostThere() throws Exception { 126 | assertSetWithCount(255, false); 127 | assertSetWithCount(255, true); 128 | assertSetWithCount(255, true, 100, 200); 129 | } 130 | 131 | private void assertSetWithCount(int count, boolean withNull) throws Exception { 132 | assertSetWithCount(count, withNull, 0, 0); 133 | } 134 | 135 | private void assertSetWithCount(int count, boolean withNull, int removeFrom, int removeTo) throws Exception { 136 | TinySetBuilder builder = new TinySetBuilder<>(); 137 | LinkedHashSet expectedMap = new LinkedHashSet<>(); 138 | 139 | setIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 140 | if (count < 1000) { 141 | builder.clear(); 142 | expectedMap.clear(); 143 | setIteration(count, withNull, removeFrom, removeTo, builder, expectedMap); 144 | } 145 | } 146 | 147 | private void setIteration(int count, boolean withNull, int removeFrom, int removeTo, TinySetBuilder builder, LinkedHashSet expectedMap) throws Exception { 148 | for (int i = 0; i < count; i++) 149 | expectedMap.add("aaa" + i); 150 | builder.addAll(expectedMap); 151 | 152 | if (withNull) { 153 | builder.add(null); 154 | expectedMap.add(null); 155 | } 156 | 157 | for (int i = removeFrom; i < removeTo; i++) { 158 | builder.remove("aaa" + i); 159 | expectedMap.remove("aaa" + i); 160 | } 161 | 162 | SetAsserts.assertSet(expectedMap, builder, removeFrom, removeTo); 163 | SetAsserts.assertSet(expectedMap, builder.build(), 0, 0); 164 | } 165 | 166 | @Test 167 | public void testDoesNotSupportSomeOperations() { 168 | TinySetBuilder builder = TinySet.builder(); 169 | builder.add("aaa"); 170 | 171 | assertThatThrownBy(() -> builder.add(1, "abc")).isInstanceOf(UnsupportedOperationException.class); 172 | assertThatThrownBy(() -> builder.set(1, "abc")).isInstanceOf(UnsupportedOperationException.class); 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/TinySetTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import net.intelie.introspective.reflect.ReflectionCache; 5 | import net.intelie.tinymap.support.SetAsserts; 6 | import org.junit.Test; 7 | 8 | import java.util.Collections; 9 | import java.util.LinkedHashSet; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 13 | 14 | public class TinySetTest { 15 | @Test 16 | public void testSizes() { 17 | ReflectionCache reflection = new ReflectionCache(); 18 | assertThat(reflection.get(TinySet.Empty.class).size()).isEqualTo(12); 19 | assertThat(reflection.get(TinySet.Small.class).size()).isEqualTo(20); 20 | assertThat(reflection.get(TinySet.Medium.class).size()).isEqualTo(20); 21 | assertThat(reflection.get(TinySet.Large.class).size()).isEqualTo(20); 22 | } 23 | 24 | @Test 25 | public void testBuildAndGet() { 26 | TinySetBuilder builder = TinySet.builder(); 27 | assertThat(builder.size()).isEqualTo(0); 28 | builder.add("aaa"); 29 | builder.add("bbb"); 30 | builder.add("aaa"); 31 | assertThat(builder.size()).isEqualTo(2); 32 | TinySet set = builder.build(); 33 | 34 | assertThat(set.getIndex("aaa")).isEqualTo(0); 35 | assertThat(set.getIndex("bbb")).isEqualTo(1); 36 | assertThat(set.getIndex("ccc")).isLessThan(0); 37 | 38 | assertThat(set.size()).isEqualTo(2); 39 | assertThat(set).containsExactly( 40 | "aaa", "bbb" 41 | ); 42 | 43 | } 44 | 45 | @Test 46 | public void canBuildWithDuplicateKeys() { 47 | TinySetBuilder builder = TinySet.builder(); 48 | builder.add("aaa"); 49 | builder.add("aaa"); 50 | builder.add("bbb"); 51 | 52 | assertThat(builder.size()).isEqualTo(2); 53 | assertThat(builder.build()).isEqualTo(ImmutableSet.of("aaa", "bbb")); 54 | 55 | assertThat(builder.size()).isEqualTo(2); 56 | assertThat(builder.build()).isEqualTo(ImmutableSet.of("aaa", "bbb")); 57 | } 58 | 59 | 60 | @Test 61 | public void canBuildWithNull() { 62 | TinySetBuilder builder = TinySet.builder(); 63 | builder.add(null); 64 | assertThat(builder.build()).isEqualTo(Collections.singleton(null)); 65 | } 66 | 67 | @Test 68 | public void canBuildMediumWithDuplicateKeys() { 69 | TinySetBuilder builder = TinySet.builder(); 70 | builder.add("aaa"); 71 | builder.add("aaa"); 72 | for (int i = 0; i < 1000; i++) { 73 | builder.add("aaa" + i); 74 | } 75 | assertThat(builder.build().size()).isEqualTo(1001); 76 | } 77 | 78 | @Test 79 | public void canBuildLargeWithDuplicateKeys() { 80 | TinySetBuilder builder = TinySet.builder(); 81 | builder.add("aaa"); 82 | builder.add("aaa"); 83 | for (int i = 0; i < 0x10000; i++) { 84 | builder.add("aaa" + i); 85 | } 86 | assertThat(builder.build().size()).isEqualTo(65537); 87 | } 88 | 89 | 90 | @Test 91 | public void testBuildEmpty() throws Exception { 92 | testCount(0, false); 93 | testCount(0, true); 94 | } 95 | 96 | @Test 97 | public void testBuildMedium() throws Exception { 98 | testCount(1000, false); 99 | testCount(1000, true); 100 | } 101 | 102 | @Test 103 | public void testBuildLarge() throws Exception { 104 | testCount(0x10000, true); 105 | } 106 | 107 | @Test 108 | public void testBuildAlmostThere() throws Exception { 109 | testCount(255, false); 110 | testCount(255, true); 111 | } 112 | 113 | @Test 114 | public void testBuildSmall() throws Exception { 115 | testCount(123, false); 116 | testCount(123, true); 117 | } 118 | 119 | 120 | @Test 121 | public void testMaxCollisions() { 122 | TinySetBuilder builder = TinySet.builder(); 123 | 124 | for (int count = 0; count < 1000; count += 20) 125 | testCollisions(builder, count); 126 | for (int count = 1000; count < 100000; count += 5000) 127 | testCollisions(builder, count); 128 | } 129 | 130 | private void testCollisions(TinySetBuilder builder, int count) { 131 | while (builder.size() < count) 132 | builder.add("aaa" + builder.size()); 133 | 134 | TinySet map = builder.build(); 135 | map.debugCollisions("abcdef"); 136 | 137 | long total = 0; 138 | for (int i = 0; i < count; i++) { 139 | total += map.debugCollisions("aaa" + i); 140 | } 141 | long totalNonExisting = 0; 142 | for (int i = 0; i < count; i++) { 143 | totalNonExisting += map.debugCollisions("bbb" + i); 144 | } 145 | //System.out.println(count + "\t" + (total / (double) count) + "\t" + (totalNonExisting / (double) count)); 146 | assertThat(count == 0 ? 0 : total / (double) count).isLessThan(1); 147 | assertThat(count == 0 ? 0 : totalNonExisting / (double) count).isLessThan(2); 148 | } 149 | 150 | private void testCount(int count, boolean withNull) throws Exception { 151 | TinySetBuilder builder = TinySet.builder(); 152 | LinkedHashSet expectedMap = new LinkedHashSet<>(); 153 | 154 | for (int i = 0; i < count; i++) { 155 | if (count < 1000) 156 | builder.build(); 157 | 158 | builder.addAll(Collections.singleton("aaa" + i)); 159 | expectedMap.add("aaa" + i); 160 | } 161 | if (withNull) { 162 | builder.add(null); 163 | expectedMap.add(null); 164 | count++; 165 | } 166 | 167 | TinySet map = builder.build(); 168 | 169 | SetAsserts.assertSet(expectedMap, map, 0, 0); 170 | } 171 | 172 | @Test 173 | public void immutableIsImmutable() { 174 | TinySetBuilder builder = TinySet.builder(); 175 | builder.add("aaa"); 176 | TinySet map = builder.build(); 177 | 178 | assertThatThrownBy(() -> map.clear()).isInstanceOf(UnsupportedOperationException.class); 179 | assertThatThrownBy(() -> map.remove("aaa")).isInstanceOf(UnsupportedOperationException.class); 180 | assertThatThrownBy(() -> map.addAll(Collections.singleton("abc"))).isInstanceOf(UnsupportedOperationException.class); 181 | assertThatThrownBy(() -> map.add("abc")).isInstanceOf(UnsupportedOperationException.class); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/benchmark/MapAccessTime.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.benchmark; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import net.intelie.introspective.ThreadResources; 5 | import net.intelie.tinymap.TinyMap; 6 | import net.intelie.tinymap.TinyMapBuilder; 7 | import net.intelie.tinymap.support.TestSizeUtils; 8 | import net.intelie.tinymap.util.SuppressForbidden; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | 12 | import java.util.HashMap; 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | @Ignore 17 | public class MapAccessTime { 18 | @Test 19 | public void main() { 20 | ImmutableMap.Builder guava = ImmutableMap.builder(); 21 | Map map = new HashMap<>(); 22 | Map linked = new LinkedHashMap<>(); 23 | TinyMapBuilder tiny = TinyMap.builder(); 24 | TinyMapBuilder mutable = new TinyMapBuilder<>(); 25 | 26 | String[] keys = new String[100]; 27 | String[] nkeys = new String[100]; 28 | 29 | //Random random = new Random(); 30 | 31 | for (int i = 0; i < keys.length; i++) { 32 | keys[i] = "key" + i; 33 | nkeys[i] = "non" + i; 34 | map.put(keys[i], "value" + i); 35 | linked.put(keys[i], "value" + i); 36 | guava.put(keys[i], "value" + i); 37 | tiny.put(keys[i], "value" + i); 38 | mutable.put(keys[i], "value" + i); 39 | } 40 | // keys = nkeys; 41 | 42 | // test("Tiny", keys, tiny.build()); 43 | // test("LinkedHashMap", keys, linked); 44 | // test("MutableTiny", keys, mutable); 45 | // test("HashMap", keys, map); 46 | // test("Guava", keys, guava.build()); 47 | } 48 | 49 | @SuppressForbidden 50 | private void test(String name, String[] keys, Map map) { 51 | for (int i = 0; i < 100000; i++) 52 | for (String key : keys) 53 | map.get(key); 54 | 55 | if (map instanceof TinyMap) { 56 | for (String key : keys) 57 | ((TinyMap) map).debugCollisions(key); 58 | } 59 | 60 | 61 | long startTime = System.nanoTime(); 62 | long startMem = ThreadResources.allocatedBytes(); 63 | for (int i = 0; i < 10000000; i++) 64 | for (String key : keys) 65 | map.get(key); 66 | 67 | long endMem = ThreadResources.allocatedBytes() - startMem; 68 | long endTime = System.nanoTime() - startTime; 69 | 70 | System.out.println(name); 71 | System.out.println(" alloc: " + TestSizeUtils.formatBytes(endMem)); 72 | System.out.println(" time: " + endTime / 1e9); 73 | 74 | if (map instanceof TinyMap) { 75 | long total = 0; 76 | for (String key : keys) { 77 | total += ((TinyMap) map).debugCollisions(key); 78 | } 79 | System.out.println(" collisions: " + (total / (double) keys.length)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/benchmark/MapSize.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.benchmark; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.stream.JsonReader; 5 | import net.intelie.tinymap.support.JavaOptimizer; 6 | import net.intelie.tinymap.support.TestSizeUtils; 7 | import net.intelie.tinymap.util.ObjectOptimizer; 8 | import net.intelie.tinymap.util.SuppressForbidden; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | @Ignore 21 | public class MapSize { 22 | @Test 23 | public void gson() throws IOException { 24 | List objs = new ArrayList<>(); 25 | Gson gson = new Gson(); 26 | String fileName = "/home/juanplopes/Downloads/dumps/everything50k.json"; 27 | try (JsonReader reader = new JsonReader(Files.newBufferedReader(Paths.get(fileName)))) { 28 | reader.setLenient(true); 29 | while (reader.peek() != com.google.gson.stream.JsonToken.END_DOCUMENT) { 30 | objs.addAll(gson.>fromJson(reader, List.class)); 31 | } 32 | } 33 | 34 | int step = objs.size() / 100; 35 | List gsonList = new ArrayList<>(); 36 | for (int i = 0; i < objs.size(); i += step) { 37 | for (int j = 0; j < Math.min(step, objs.size() - i); j++) { 38 | gsonList.add(objs.get(i + j)); 39 | } 40 | 41 | List javaList = new JavaOptimizer(null).optimizeList(gsonList); 42 | List tinyList = new ObjectOptimizer(null).optimizeList(gsonList); 43 | 44 | print(i + step, gsonList, javaList, tinyList); 45 | } 46 | 47 | } 48 | 49 | @SuppressForbidden 50 | private void print(int i, Object... values) { 51 | System.out.println(i + "\t" + Arrays.stream(values) 52 | .mapToLong(TestSizeUtils::sizeOnlyStructure) 53 | .mapToObj(String::valueOf) 54 | .collect(Collectors.joining("\t"))); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/benchmark/MapSizeReal.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.benchmark; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.stream.JsonReader; 5 | import net.intelie.tinymap.support.ImmutableOptimizer; 6 | import net.intelie.tinymap.support.JavaOptimizer; 7 | import net.intelie.tinymap.support.TestSizeUtils; 8 | import net.intelie.tinymap.util.DefaultObjectCache; 9 | import net.intelie.tinymap.util.ObjectOptimizer; 10 | import net.intelie.tinymap.util.SuppressForbidden; 11 | import org.junit.Ignore; 12 | import org.junit.Test; 13 | 14 | import java.io.IOException; 15 | import java.nio.file.Files; 16 | import java.nio.file.Paths; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | @Ignore 23 | public class MapSizeReal { 24 | @Test 25 | public void gson() throws IOException { 26 | List objs = new ArrayList<>(); 27 | Gson gson = new Gson(); 28 | String fileName = "/home/juanplopes/Downloads/rtolive.json"; 29 | try (JsonReader reader = new JsonReader(Files.newBufferedReader(Paths.get(fileName)))) { 30 | reader.setLenient(true); 31 | while (reader.peek() != com.google.gson.stream.JsonToken.END_DOCUMENT) { 32 | objs.addAll(gson.>fromJson(reader, List.class)); 33 | } 34 | } 35 | 36 | int step = objs.size() / 100; 37 | List gsonList = new ArrayList<>(); 38 | 39 | for (int i = 0; i < objs.size(); i += step) { 40 | for (int j = 0; j < Math.min(step, objs.size() - i); j++) { 41 | gsonList.add(objs.get(i + j)); 42 | } 43 | 44 | List javaList = new JavaOptimizer(null).optimizeList(gsonList); 45 | List javaOptList = new JavaOptimizer(new DefaultObjectCache()).optimizeList(gsonList); 46 | 47 | List immutableList = new ImmutableOptimizer(null).optimizeList(gsonList); 48 | List immutableOptList = new ImmutableOptimizer(new DefaultObjectCache()).optimizeList(gsonList); 49 | 50 | List tinyList = new ObjectOptimizer(null).optimizeList(gsonList); 51 | List tinyOptList = new ObjectOptimizer(new DefaultObjectCache()).optimizeList(gsonList); 52 | 53 | print(i + step, 54 | javaList, 55 | immutableList, 56 | tinyList, 57 | javaOptList, 58 | immutableOptList, 59 | tinyOptList); 60 | } 61 | 62 | } 63 | 64 | @SuppressForbidden 65 | private void print(int i, Object... values) { 66 | System.out.println(i + "\t" + Arrays.stream(values) 67 | .mapToLong(TestSizeUtils::size) 68 | .mapToObj(String::valueOf) 69 | .collect(Collectors.joining("\t"))); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/json/FastDoubleTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.json; 2 | 3 | import net.intelie.introspective.ThreadResources; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 8 | import static org.junit.Assert.fail; 9 | 10 | public class FastDoubleTest { 11 | @Test 12 | public void testBasics() { 13 | assertParseFast("1"); 14 | assertParseFast("0.1"); 15 | assertParseFast(" -0.1 "); 16 | assertParseFast(" +0.1 "); 17 | assertParseFast("1E2"); 18 | assertParseFast("1E+2"); 19 | assertParseFast("1E-2"); 20 | assertParseFast("1e-2"); 21 | assertParseFast(".1"); 22 | assertParseFast("-0"); 23 | assertParseFast("-1.1"); 24 | assertParseFast("0.00000000000001"); 25 | assertParseFast("0.3"); 26 | assertParseFast("1.14"); 27 | assertParseFast("123456789"); 28 | assertParseFast("999999999999999"); 29 | assertParseFast("-999999999999999"); 30 | } 31 | 32 | @Test 33 | public void testFallbacks() { 34 | assertParseFallback("3.3e-256"); 35 | assertParseFallback("0.3e-256"); 36 | assertParseFallback("0.0000000000000000000000000001"); 37 | assertParseFallback("NaN"); 38 | assertParseFallback("Infinity"); 39 | assertParseFallback("-Infinity"); 40 | assertParseFallback("4.198562687463195E38"); 41 | assertParseFallback("1.0999999999999998E38"); 42 | assertParseFallback("123456789123456789"); 43 | assertParseFallback("123456789123456789123"); 44 | } 45 | 46 | @Test 47 | public void testErrors() { 48 | assertException("-"); 49 | assertException("."); 50 | assertException(""); 51 | assertException("aaa"); 52 | assertException("-e"); 53 | assertException("1e"); 54 | assertException("1e1.0"); 55 | assertException("1a2"); 56 | } 57 | 58 | @Test 59 | public void lab() { 60 | assertParseFallback("5.0000000000000004E36"); 61 | } 62 | 63 | @Test 64 | public void testRanges() { 65 | for (int k = -40; k < 40; k++) { 66 | for (int i = 0; i < 1000; i++) { 67 | assertParseFallback(Double.toString(i / Math.pow(10, k))); 68 | } 69 | } 70 | } 71 | 72 | @Test 73 | public void testLargeRanges() { 74 | for (int i = -100000; i < 100000; i++) { 75 | assertParseFallback(Double.toString(i * 100000000000000L)); 76 | } 77 | } 78 | 79 | private void assertParseFast(String s) { 80 | for (int i = 0; i < 1000; i++) 81 | FastDouble.parseDouble(s); 82 | 83 | long startMem = ThreadResources.allocatedBytes(); 84 | for (int i = 0; i < 1000; i++) 85 | FastDouble.parseDouble(s); 86 | assertThat(ThreadResources.allocatedBytes() - startMem).describedAs(s).isZero(); 87 | 88 | assertParseFallback(s); 89 | } 90 | 91 | private void assertException(String s) { 92 | try { 93 | Double.parseDouble(s); 94 | fail("must throw"); 95 | } catch (Exception e) { 96 | assertThatThrownBy(() -> FastDouble.parseDouble(s)) 97 | .isInstanceOf(e.getClass()) 98 | .hasMessage(e.getMessage()); 99 | assertThatThrownBy(() -> FastDouble.parseDouble(new StringBuilder().append("xxx").append(s).append("xxx"), 3, s.length() + 3)) 100 | .isInstanceOf(e.getClass()) 101 | .hasMessage(e.getMessage()); 102 | } 103 | } 104 | 105 | private void assertParseFallback(String s) { 106 | Double d1 = Double.parseDouble(s); 107 | Double d2 = FastDouble.parseDouble(s); 108 | Double d3 = FastDouble.parseDouble(new StringBuilder().append("xxx").append(s).append("xxx"), 3, s.length() + 3); 109 | 110 | assertThat(d2).describedAs(s).isEqualTo(d1); 111 | assertThat(d3).describedAs(s).isEqualTo(d1); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/json/TinyJsonDecoderTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.json; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.stream.JsonReader; 5 | import com.google.gson.stream.JsonToken; 6 | import net.intelie.tinymap.ObjectCache; 7 | import net.intelie.tinymap.util.DefaultObjectCache; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | import java.io.StringReader; 13 | 14 | import static org.assertj.core.api.Assertions.*; 15 | 16 | public class TinyJsonDecoderTest { 17 | 18 | private Gson gson; 19 | private ObjectCache cache; 20 | 21 | @Before 22 | public void setUp() { 23 | gson = new Gson(); 24 | cache = new DefaultObjectCache(); 25 | } 26 | 27 | @Test 28 | public void testDecoderIsLenientByDefaultButReaderIsNot() { 29 | TinyJsonDecoder decoder = new TinyJsonDecoder(cache, new StringReader("{a:1}")); 30 | assertThat(decoder.isLenient()).isTrue(); 31 | 32 | TinyJsonReader reader = new TinyJsonReader(new StringReader("{a:1}")); 33 | assertThat(reader.isLenient()).isFalse(); 34 | 35 | assertThat(decoder.toString()).isEqualTo("TinyJsonDecoder at line 1 column 1 path $"); 36 | assertThat(reader.toString()).isEqualTo("TinyJsonReader at line 1 column 1 path $"); 37 | } 38 | 39 | @Test 40 | public void testInnerInvalidJson() throws IOException { 41 | TinyJsonDecoder decoder = new TinyJsonDecoder(cache, new StringReader("{a:[{c:/}]}}")); 42 | assertThatThrownBy(decoder::nextObject) 43 | .isInstanceOf(IOException.class) 44 | .hasMessageContaining("Expected value"); 45 | } 46 | 47 | @Test 48 | public void testValid() throws IOException { 49 | assertValidJson(1, "{a:1}"); 50 | assertValidJson(2, "{a:1}{b:3}"); 51 | assertValidJson(2, "{a:1}\n\t{b:3}"); 52 | assertValidJson(1, "{'a':[1, '2', true, null]}"); 53 | } 54 | 55 | @Test 56 | public void lab() throws IOException { 57 | 58 | } 59 | 60 | @Test 61 | public void testDumpBuffer() throws IOException { 62 | TinyJsonDecoder decoder = new TinyJsonDecoder(cache, new StringReader("{a:1}")); 63 | 64 | assertThat(decoder.nextMap()).isNotNull(); 65 | assertThat(decoder.dumpBuffer().toString()).isEqualTo("{a:1}"); 66 | 67 | 68 | } 69 | 70 | @Test 71 | public void testInvalid() throws IOException { 72 | assertInvalidJson(false, "Use JsonReader.setLenient(true)", "1-", JsonReader::peek, TinyJsonReader::peek); 73 | assertInvalidJson(false, "Use JsonReader.setLenient(true)", "1+", JsonReader::peek, TinyJsonReader::peek); 74 | assertInvalidJson(false, "Use JsonReader.setLenient(true)", "01", JsonReader::peek, TinyJsonReader::peek); 75 | assertInvalidJson(false, "Use JsonReader.setLenient(true)", "f", JsonReader::nextBoolean, TinyJsonReader::nextBoolean); 76 | assertInvalidJson(false, "Use JsonReader.setLenient(true)", "felse", JsonReader::nextBoolean, TinyJsonReader::nextBoolean); 77 | 78 | assertInvalidJson(true, "Expected a double but was BOOLEAN", "true", JsonReader::nextDouble, TinyJsonReader::nextDouble); 79 | assertInvalidJson(false, "JSON forbids NaN and infinities", "\"NaN\"", JsonReader::nextDouble, TinyJsonReader::nextDouble); 80 | assertInvalidJson(true, "Unterminated string", "\"NaN", JsonReader::nextString, TinyJsonReader::nextString); 81 | assertInvalidJson(true, "Unterminated string", "\"NaN", JsonReader::skipValue, TinyJsonReader::skipValue); 82 | assertInvalidJson(true, "Unterminated string", "'NaN", JsonReader::skipValue, TinyJsonReader::skipValue); 83 | assertInvalidJson(true, "Use JsonReader.setLenient(true)", "{test;", x -> { 84 | x.beginObject(); 85 | x.peek(); 86 | x.setLenient(false); 87 | x.skipValue(); 88 | }, x -> { 89 | x.beginObject(); 90 | x.peek(); 91 | x.setLenient(false); 92 | x.skipValue(); 93 | }); 94 | assertInvalidJson(true, "Use JsonReader.setLenient(true)", "{test;", x -> { 95 | x.beginObject(); 96 | x.peek(); 97 | x.setLenient(false); 98 | x.nextName(); 99 | }, x -> { 100 | x.beginObject(); 101 | x.peek(); 102 | x.setLenient(false); 103 | x.nextName(); 104 | }); 105 | 106 | assertInvalidJson(true, "Unterminated array ", "[1[", x -> { 107 | x.beginArray(); 108 | x.nextDouble(); 109 | x.nextDouble(); 110 | }, x -> { 111 | x.beginArray(); 112 | x.nextDouble(); 113 | x.nextDouble(); 114 | }); 115 | 116 | assertInvalidJson(true, "Unterminated comment", "/*", JsonReader::skipValue, TinyJsonReader::skipValue); 117 | } 118 | 119 | @Test 120 | public void testLists() throws Exception { 121 | assertListOf("[1, 2, 3, 0.3e+5, 0.3e-5]", JsonReader::nextDouble, TinyJsonReader::nextDouble); 122 | assertListOf("[1, '2', \"3\", 4.5, NaN]", JsonReader::nextDouble, TinyJsonReader::nextDouble); 123 | assertListOf("[1, '2', \"3\", 4.5, NaN]", JsonReader::nextString, tinyJsonReader -> { 124 | return tinyJsonReader.nextString().toString(); 125 | }); 126 | assertListOf("[true, false]", JsonReader::nextBoolean, TinyJsonReader::nextBoolean); 127 | assertListOf("[null, null]", JsonReader::nextNull, TinyJsonReader::nextNull); 128 | } 129 | 130 | private void assertInvalidJson(boolean lenient, String exception, String s, VoidCheckedFunction expectedFn, VoidCheckedFunction actualFn) throws IOException { 131 | assertInvalidJson(lenient, exception, s, x -> { 132 | expectedFn.apply(x); 133 | return null; 134 | }, x -> { 135 | actualFn.apply(x); 136 | return null; 137 | }); 138 | } 139 | 140 | private void assertInvalidJson(boolean lenient, String exception, String s, CheckedFunction expectedFn, CheckedFunction actualFn) throws IOException { 141 | try (JsonReader expectedReader = new JsonReader(new StringReader(s)); 142 | TinyJsonDecoder reader = new TinyJsonDecoder(cache, new StringReader(s))) { 143 | reader.setLenient(lenient); 144 | expectedReader.setLenient(lenient); 145 | 146 | try { 147 | expectedFn.apply(expectedReader); 148 | fail("must throw"); 149 | } catch (Exception e) { 150 | assertThatThrownBy(() -> actualFn.apply(reader)) 151 | .isInstanceOf(IOException.class) 152 | .hasMessage(e.getMessage()) 153 | .hasMessageContaining(exception); 154 | } 155 | } 156 | 157 | } 158 | 159 | private void assertListOf(String s, VoidCheckedFunction expectedFn, VoidCheckedFunction actualFn) throws Exception { 160 | assertListOf(s, x -> { 161 | expectedFn.apply(x); 162 | return null; 163 | }, x -> { 164 | actualFn.apply(x); 165 | return null; 166 | }); 167 | } 168 | 169 | private void assertListOf(String s, CheckedFunction expectedFn, CheckedFunction actualFn) throws Exception { 170 | try (JsonReader expectedReader = new JsonReader(new StringReader(s)); 171 | TinyJsonDecoder reader = new TinyJsonDecoder(cache, new StringReader(s))) { 172 | expectedReader.setLenient(true); 173 | 174 | expectedReader.beginArray(); 175 | reader.beginArray(); 176 | 177 | while (expectedReader.hasNext()) { 178 | T expectedValue = expectedFn.apply(expectedReader); 179 | assertThat(actualFn.apply(reader)).isEqualTo(expectedValue); 180 | } 181 | assertThat(reader.hasNext()).isFalse(); 182 | 183 | assertThat(reader.peek()).isEqualTo(net.intelie.tinymap.json.JsonToken.END_ARRAY); 184 | 185 | expectedReader.endArray(); 186 | reader.endArray(); 187 | 188 | assertThat(reader.peek().name()).isEqualTo(JsonToken.END_DOCUMENT.name()); 189 | assertThat(expectedReader.peek().name()).isEqualTo(JsonToken.END_DOCUMENT.name()); 190 | } 191 | } 192 | 193 | private void assertValidJson(int expectedDocs, String s) throws IOException { 194 | int actualDocs = 0; 195 | try (JsonReader expectedReader = new JsonReader(new StringReader(s)); 196 | TinyJsonDecoder reader = new TinyJsonDecoder(cache, new StringReader(s))) { 197 | expectedReader.setLenient(true); 198 | while (expectedReader.peek() != JsonToken.END_DOCUMENT) { 199 | Object expectedValue = gson.fromJson(expectedReader, Object.class); 200 | assertThat(reader.nextObject()).isEqualTo(expectedValue); 201 | actualDocs++; 202 | } 203 | } 204 | assertThat(actualDocs).isEqualTo(expectedDocs); 205 | } 206 | 207 | @FunctionalInterface 208 | public interface CheckedFunction { 209 | R apply(T t) throws Exception; 210 | } 211 | 212 | @FunctionalInterface 213 | public interface VoidCheckedFunction { 214 | void apply(T t) throws Exception; 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/lazytests/LazyTester.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.lazytests; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | public class LazyTester { 6 | private final B builder; 7 | private final T expected; 8 | private final Assert assertion; 9 | 10 | public LazyTester(B builder, T expected, Assert assertion) { 11 | this.builder = builder; 12 | this.expected = expected; 13 | this.assertion = assertion; 14 | } 15 | 16 | public void itv(VoidFn fn) throws Exception { 17 | fn.apply(expected); 18 | fn.apply(builder); 19 | assertion.apply(builder, expected); 20 | } 21 | 22 | public V it(Fn fn) throws Exception { 23 | V expectedResult = fn.apply(expected); 24 | V builderResult = fn.apply(builder); 25 | assertThat(builderResult).isEqualTo(expectedResult); 26 | assertion.apply(builder, expected); 27 | return builderResult; 28 | } 29 | 30 | public B getBuilder() { 31 | return builder; 32 | } 33 | 34 | public T getExpected() { 35 | return expected; 36 | } 37 | 38 | interface VoidFn { 39 | void apply(T obj) throws Exception; 40 | } 41 | 42 | interface Fn { 43 | Q apply(T obj) throws Exception; 44 | } 45 | 46 | interface Assert { 47 | void apply(T expected, B builder) throws Exception; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/lazytests/ListLazyTests.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.lazytests; 2 | 3 | import net.intelie.tinymap.TinyList; 4 | import net.intelie.tinymap.TinyListBuilder; 5 | import net.intelie.tinymap.base.IndexedList; 6 | import net.intelie.tinymap.support.ListAsserts; 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.ListIterator; 12 | import java.util.stream.Collectors; 13 | import java.util.stream.IntStream; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 17 | 18 | public class ListLazyTests { 19 | @Test 20 | public void testALotOfOperations() throws Exception { 21 | LazyTester, TinyListBuilder, TinyList> tester = new LazyTester<>( 22 | TinyList.builder(), new ArrayList<>(), (a, b) -> ListAsserts.assertList(b, a)); 23 | 24 | tester.it(x -> x.addAll(range(50, 100))); 25 | tester.it(x -> x.addAll(0, range(0, 20))); 26 | tester.it(x -> x.addAll(20, range(20, 50))); 27 | tester.it(x -> x.removeAll(range(30, 70))); 28 | tester.it(x -> x.addAll(30, range(30, 70))); 29 | tester.itv(x -> { 30 | ListIterator it = x.listIterator(); 31 | for (int i = 0; i < 30; i++) 32 | it.next(); 33 | 34 | it.next(); 35 | it.remove(); 36 | assertThatThrownBy(it::remove).isInstanceOf(IllegalStateException.class); 37 | it.next(); 38 | it.remove(); 39 | assertThat(it.next()).isEqualTo("aaa32"); 40 | assertThat(it.previous()).isEqualTo("aaa32"); 41 | assertThat(it.previous()).isEqualTo("aaa29"); 42 | }); 43 | tester.itv(x -> { 44 | ListIterator it = x.listIterator(30); 45 | it.add("bbb30"); 46 | it.add("bbb31"); 47 | }); 48 | tester.itv(x -> { 49 | ListIterator it = x.listIterator(); 50 | int i = 0; 51 | while (it.hasNext()) { 52 | it.next(); 53 | it.set("aaa" + i++); 54 | } 55 | }); 56 | 57 | assertThat(tester.getBuilder()).containsExactlyElementsOf( 58 | range(0, 100) 59 | ); 60 | } 61 | 62 | @Test 63 | public void testInsertInSubList() throws Exception { 64 | LazyTester, TinyListBuilder, TinyList> tester = new LazyTester<>( 65 | TinyList.builder(), new ArrayList<>(), (a, b) -> ListAsserts.assertList(b, a)); 66 | 67 | tester.it(x -> x.addAll(range(0, 10))); 68 | tester.it(x -> x.addAll(range(90, 100))); 69 | tester.itv(x -> { 70 | List list = x.subList(5, 15); 71 | assertThat(list.get(0)).isEqualTo("aaa5"); 72 | 73 | list.addAll(5, range(10, 80)); 74 | }); 75 | tester.it(x -> { 76 | List list = x.subList(10, 80); 77 | if (x instanceof IndexedList) 78 | return ((IndexedList) list).removeLast(); 79 | else 80 | return list.remove(69); 81 | }); 82 | tester.itv(x -> x.subList(0, 79).addAll(range(79, 90))); 83 | 84 | assertThat(tester.getBuilder()).containsExactlyElementsOf( 85 | range(0, 100) 86 | ); 87 | } 88 | 89 | @Test 90 | public void testIndexOf() throws Exception { 91 | LazyTester, TinyListBuilder, TinyList> tester = new LazyTester<>( 92 | TinyList.builder(), new ArrayList<>(), (a, b) -> ListAsserts.assertList(b, a)); 93 | 94 | tester.it(x -> x.addAll(range(0, 100))); 95 | tester.it(x -> x.indexOf("aaa30")); 96 | tester.it(x -> x.lastIndexOf("aaa30")); 97 | 98 | } 99 | 100 | private List range(int start, int end) { 101 | return IntStream.range(start, end).mapToObj(i -> "aaa" + i).collect(Collectors.toList()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/lazytests/SerialVersionUIDTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.lazytests; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import com.google.common.reflect.ClassPath; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | import java.io.Serializable; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | public class SerialVersionUIDTest { 13 | @Test 14 | public void testSerialVersionUid() throws IOException { 15 | ClassPath cp = ClassPath.from(getClass().getClassLoader()); 16 | ImmutableSet classes = cp.getAllClasses(); 17 | 18 | for (ClassPath.ClassInfo info : classes) { 19 | if (!info.getPackageName().startsWith("net.intelie.tinymap")) continue; 20 | Class clazz = info.load(); 21 | 22 | boolean shouldHaveField = Serializable.class.isAssignableFrom(clazz) && !clazz.isEnum(); 23 | 24 | boolean hasField = false; 25 | try { 26 | clazz.getDeclaredField("serialVersionUID"); 27 | hasField = true; 28 | } catch (NoSuchFieldException ignored) { 29 | } 30 | 31 | assertThat(hasField) 32 | .describedAs(clazz.getName()) 33 | .isEqualTo(shouldHaveField); 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/ImmutableOptimizer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import net.intelie.tinymap.ObjectCache; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | @SuppressWarnings("unchecked") 11 | public class ImmutableOptimizer { 12 | private final ObjectCache cache; 13 | 14 | public ImmutableOptimizer(ObjectCache cache) { 15 | this.cache = cache; 16 | } 17 | 18 | public Object optimize(Object object) { 19 | if (object instanceof CharSequence) 20 | return cache != null ? cache.get((CharSequence) object) : object.toString(); 21 | if (object instanceof Double) 22 | return cache != null ? cache.get(((Double) object)) : (Double) object; 23 | if (object instanceof List) 24 | return optimizeList((Iterable) object); 25 | if (object instanceof Map) 26 | return optimizeMap((Map) object); 27 | return object; 28 | } 29 | 30 | public Map optimizeMap(Map object) { 31 | ImmutableMap.Builder map = ImmutableMap.builder(); 32 | object.forEach((k, v) -> map.put((K) optimize(k), (V) optimize(v))); 33 | return map.build(); 34 | } 35 | 36 | public List optimizeList(Iterable object) { 37 | ImmutableList.Builder list = ImmutableList.builder(); 38 | object.forEach(x -> list.add((T) optimize(x))); 39 | return list.build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/JavaOptimizer.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import net.intelie.tinymap.ObjectCache; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | @SuppressWarnings("unchecked") 11 | public class JavaOptimizer { 12 | private final ObjectCache cache; 13 | 14 | public JavaOptimizer(ObjectCache cache) { 15 | this.cache = cache; 16 | } 17 | 18 | public Object optimize(Object object) { 19 | if (object instanceof CharSequence) 20 | return cache != null ? cache.get((CharSequence) object) : new StringBuilder().append(object).toString(); 21 | if (object instanceof Double) 22 | return cache != null ? cache.get(((Double) object)) : Double.valueOf((Double) object); 23 | if (object instanceof List) 24 | return optimizeList((Iterable) object); 25 | if (object instanceof Map) 26 | return optimizeMap((Map) object); 27 | return object; 28 | } 29 | 30 | public LinkedHashMap optimizeMap(Map object) { 31 | LinkedHashMap map = new LinkedHashMap<>(); 32 | object.forEach((k, v) -> map.put((K) optimize(k), (V) optimize(v))); 33 | return map; 34 | } 35 | 36 | public ArrayList optimizeList(Iterable object) { 37 | ArrayList list = new ArrayList<>(); 38 | object.forEach(x -> list.add((T) optimize(x))); 39 | return list; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/ListAsserts.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import net.intelie.tinymap.base.IndexedCollection; 4 | 5 | import java.io.IOException; 6 | import java.util.*; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 10 | 11 | public class ListAsserts { 12 | public static void assertList(List expected, IndexedCollection actual) throws Exception { 13 | assertSizes(expected, actual); 14 | assertElements(expected, actual); 15 | assertElementsInverse(expected, actual); 16 | 17 | assertInvalidIndex(actual, -1); 18 | assertInvalidIndex(actual, actual.rawSize()); 19 | assertForEach(expected, actual); 20 | 21 | assertThat(actual.contains("bbb")).isFalse(); 22 | assertThat(actual.getIndex("bbb")).isLessThan(0); 23 | 24 | assertCommonProperties(expected, actual); 25 | 26 | assertSerialization(actual); 27 | } 28 | 29 | private static void assertSerialization(IndexedCollection actual) throws IOException, ClassNotFoundException { 30 | byte[] serialized = SerializationHelper.testSerialize(actual); 31 | // byte[] serializedExpected = SerializationHelper.testSerialize(expectedSet); 32 | // if (expectedSet.size() > 10 && removeTo - removeFrom == 0) 33 | // assertThat(serialized.length).isLessThan(2 * serializedExpected.length); 34 | 35 | List deserialized = SerializationHelper.testDeserialize(serialized); 36 | assertThat(deserialized).isEqualTo(actual); 37 | } 38 | 39 | private static void assertCommonProperties(List expectedSet, IndexedCollection actual) { 40 | assertThat(actual.isEmpty()).isEqualTo(expectedSet.isEmpty()); 41 | 42 | assertThat(expectedSet).isEqualTo(actual); 43 | assertThat(actual.toString()).isEqualTo(expectedSet.toString()); 44 | 45 | assertThat(actual).isEqualTo(expectedSet); 46 | assertThat(actual.hashCode()).isEqualTo(expectedSet.hashCode()); 47 | 48 | ArrayList unordered = new ArrayList<>(expectedSet); 49 | assertThat(actual).isEqualTo(unordered); 50 | assertThat(actual.hashCode()).isEqualTo(unordered.hashCode()); 51 | 52 | unordered.remove("aaa0"); 53 | ((List) unordered).add("bbb0"); 54 | assertThat(actual).isNotEqualTo(unordered); 55 | assertThat(actual.hashCode()).isNotEqualTo(unordered.hashCode()); 56 | } 57 | 58 | private static void assertForEach(List expectedSet, IndexedCollection actual) { 59 | Iterator expectedIterator = expectedSet.iterator(); 60 | actual.forEach(obj -> { 61 | assertThat(expectedIterator).hasNext(); 62 | Object expectedEntry = expectedIterator.next(); 63 | assertThat(obj).isEqualTo(expectedEntry); 64 | }); 65 | } 66 | 67 | private static void assertInvalidIndex(IndexedCollection actual, int index) { 68 | assertThatThrownBy(() -> actual.getEntryAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 69 | assertThatThrownBy(() -> actual.removeAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 70 | } 71 | 72 | private static void assertElements(List expectedSet, IndexedCollection actual) { 73 | ListIterator keysIterator = actual.iterator(); 74 | 75 | int index = 0; 76 | for (ListIterator iterator = expectedSet.listIterator(); iterator.hasNext(); ) { 77 | Object entry = iterator.next(); 78 | 79 | 80 | assertThat(keysIterator.hasNext()).isTrue(); 81 | assertThat(keysIterator.next()).isEqualTo(entry); 82 | 83 | assertThat(actual.getIndex(entry)).isEqualTo(index); 84 | assertThat(actual.getEntryAt(index)).isEqualTo(entry); 85 | 86 | 87 | assertThat(keysIterator.nextIndex()).isEqualTo(iterator.nextIndex()); 88 | assertThat(keysIterator.previousIndex()).isEqualTo(iterator.previousIndex()); 89 | 90 | 91 | index++; 92 | } 93 | 94 | assertThat(keysIterator.hasNext()).isFalse(); 95 | assertThatThrownBy(keysIterator::next).isInstanceOf(NoSuchElementException.class); 96 | } 97 | 98 | private static void assertElementsInverse(List expectedSet, IndexedCollection actual) { 99 | ListIterator keysIterator = actual.iterator(actual.rawSize()); 100 | ListIterator expectedIterator = expectedSet.listIterator(actual.size()); 101 | while (keysIterator.hasNext()) keysIterator.next(); 102 | while (expectedIterator.hasNext()) expectedIterator.next(); 103 | 104 | assertThat(keysIterator.nextIndex()).isEqualTo(expectedIterator.nextIndex()); 105 | assertThat(keysIterator.previousIndex()).isEqualTo(expectedIterator.previousIndex()); 106 | 107 | int index = expectedSet.size() - 1; 108 | while (expectedIterator.hasPrevious()) { 109 | Object entry = expectedIterator.previous(); 110 | assertThat(keysIterator.hasPrevious()).isTrue(); 111 | assertThat(keysIterator.previous()).isEqualTo(entry); 112 | 113 | assertThat(actual.getIndex(entry)).isEqualTo(index); 114 | assertThat(actual.getEntryAt(index)).isEqualTo(entry); 115 | 116 | assertThat(keysIterator.nextIndex()).isEqualTo(expectedIterator.nextIndex()); 117 | assertThat(keysIterator.previousIndex()).isEqualTo(expectedIterator.previousIndex()); 118 | 119 | index--; 120 | } 121 | 122 | assertThat(keysIterator.hasPrevious()).isFalse(); 123 | assertThatThrownBy(keysIterator::previous).isInstanceOf(NoSuchElementException.class); 124 | } 125 | 126 | private static void assertSizes(List expectedSet, IndexedCollection actual) { 127 | assertThat(actual.size()).isEqualTo(expectedSet.size()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/MapAsserts.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import net.intelie.tinymap.base.IndexedMap; 4 | 5 | import java.io.IOException; 6 | import java.util.HashMap; 7 | import java.util.Iterator; 8 | import java.util.Map; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | 12 | public class MapAsserts { 13 | public static void assertMap(Map expected, IndexedMap actual, int removeFrom, int removeTo) throws Exception { 14 | assertSizes(expected, actual); 15 | assertElements(expected, actual, removeFrom, removeTo); 16 | 17 | assertInvalidIndex(actual, -1); 18 | assertInvalidIndex(actual, actual.rawSize()); 19 | assertForEach(expected, actual); 20 | 21 | assertThat(actual.get("bbb")).isNull(); 22 | assertThat(actual.getOrDefault("bbb", "xxx")).isEqualTo("xxx"); 23 | assertThat(actual.getIndex("bbb")).isLessThan(0); 24 | 25 | assertCommonProperties(expected, actual); 26 | 27 | assertSerialization(actual); 28 | 29 | SetAsserts.assertSet(expected.keySet(), actual.keySet(), removeFrom, removeTo); 30 | } 31 | 32 | private static void assertSerialization(IndexedMap actual) throws IOException, ClassNotFoundException { 33 | byte[] serialized = SerializationHelper.testSerialize(actual); 34 | // byte[] serializedExpected = SerializationHelper.testSerialize(expected); 35 | // if (expected.size() > 10 && removeTo - removeFrom == 0) 36 | // assertThat(serialized.length).isLessThan(2 * serializedExpected.length); 37 | 38 | Map deserialized = SerializationHelper.testDeserialize(serialized); 39 | assertThat(deserialized).isEqualTo(actual); 40 | } 41 | 42 | private static void assertCommonProperties(Map expected, IndexedMap actual) { 43 | assertThat(actual.isEmpty()).isEqualTo(expected.isEmpty()); 44 | 45 | assertThat(expected).isEqualTo(actual); 46 | assertThat(actual.toString()).isEqualTo(expected.toString()); 47 | 48 | assertThat(actual).isEqualTo(expected); 49 | assertThat(actual.hashCode()).isEqualTo(expected.hashCode()); 50 | 51 | HashMap unordered = new HashMap<>(expected); 52 | assertThat(actual).isEqualTo(unordered); 53 | assertThat(actual.hashCode()).isEqualTo(unordered.hashCode()); 54 | 55 | unordered.put("aaa0", "different"); 56 | assertThat(actual).isNotEqualTo(unordered); 57 | assertThat(actual.hashCode()).isNotEqualTo(unordered.hashCode()); 58 | } 59 | 60 | private static void assertForEach(Map expected, IndexedMap actual) { 61 | Iterator> expectedIterator = expected.entrySet().iterator(); 62 | actual.forEach((k, v) -> { 63 | assertThat(expectedIterator).hasNext(); 64 | Map.Entry expectedEntry = expectedIterator.next(); 65 | assertThat(k).isEqualTo(expectedEntry.getKey()); 66 | assertThat(v).isEqualTo(expectedEntry.getValue()); 67 | }); 68 | } 69 | 70 | private static void assertInvalidIndex(IndexedMap actual, int index) { 71 | assertThatThrownBy(() -> actual.getEntryAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 72 | assertThatThrownBy(() -> actual.getKeyAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 73 | assertThatThrownBy(() -> actual.getValueAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 74 | assertThatThrownBy(() -> actual.removeAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 75 | assertThatThrownBy(() -> actual.setValueAt(index, 123)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 76 | } 77 | 78 | private static void assertElements(Map expected, IndexedMap actual, int removeFrom, int removeTo) { 79 | Iterator keysIterator = actual.keySet().iterator(); 80 | Iterator valuesIterator = actual.values().iterator(); 81 | Iterator> entriesIterator = actual.entrySet().iterator(); 82 | 83 | int index = 0; 84 | for (Map.Entry entry : expected.entrySet()) { 85 | if (index == removeFrom) index = removeTo; 86 | assertThat(actual.get(entry.getKey())).isEqualTo(entry.getValue()); 87 | assertThat(actual.getOrDefault(entry.getKey(), null)).isEqualTo(entry.getValue()); 88 | assertThat(actual.getIndex(entry.getKey())).isEqualTo(index); 89 | assertThat(actual.getKeyAt(index)).isEqualTo(entry.getKey()); 90 | assertThat(actual.getValueAt(index)).isEqualTo(entry.getValue()); 91 | IndexedMap.Entry actualEntry = actual.getEntryAt(index); 92 | assertThat(actualEntry).isEqualTo(entry); 93 | assertThat(actualEntry.getIndex()).isEqualTo(index); 94 | assertThat(actualEntry.isRemoved()).isEqualTo(false); 95 | 96 | assertThat(actual.containsKey(entry.getKey())).isTrue(); 97 | 98 | assertThat(keysIterator.hasNext()).isTrue(); 99 | assertThat(keysIterator.next()).isEqualTo(entry.getKey()); 100 | 101 | assertThat(valuesIterator.hasNext()).isTrue(); 102 | assertThat(valuesIterator.next()).isEqualTo(entry.getValue()); 103 | 104 | assertThat(entriesIterator.hasNext()).isTrue(); 105 | Map.Entry nextEntry = entriesIterator.next(); 106 | assertThat(nextEntry).isEqualTo(entry); 107 | assertThat(nextEntry.hashCode()).isEqualTo(entry.hashCode()); 108 | assertThat(nextEntry.toString()).isEqualTo(entry.toString()); 109 | index++; 110 | } 111 | 112 | assertThat(keysIterator.hasNext()).isFalse(); 113 | assertThat(valuesIterator.hasNext()).isFalse(); 114 | assertThat(entriesIterator.hasNext()).isFalse(); 115 | } 116 | 117 | private static void assertSizes(Map expected, IndexedMap actual) { 118 | assertThat(actual.size()).isEqualTo(expected.size()); 119 | assertThat(actual.keySet().size()).isEqualTo(expected.size()); 120 | assertThat(actual.values().size()).isEqualTo(expected.size()); 121 | assertThat(actual.entrySet().size()).isEqualTo(expected.size()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/SerializationHelper.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import java.io.*; 4 | 5 | public class SerializationHelper { 6 | public static T roundTrip(T obj) throws Exception { 7 | byte[] serialized = testSerialize(obj); 8 | return testDeserialize(serialized); 9 | } 10 | 11 | public static T testDeserialize(byte[] serialized) throws IOException, ClassNotFoundException { 12 | ByteArrayInputStream bais = new ByteArrayInputStream(serialized); 13 | ObjectInputStream istream = new ObjectInputStream(bais); 14 | return (T) istream.readObject(); 15 | } 16 | 17 | public static byte[] testSerialize(Object obj) throws IOException { 18 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 19 | ObjectOutputStream ostream = new ObjectOutputStream(baos); 20 | 21 | ostream.writeObject(obj); 22 | ostream.flush(); 23 | return baos.toByteArray(); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/SetAsserts.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import net.intelie.tinymap.base.IndexedSet; 4 | 5 | import java.io.IOException; 6 | import java.util.*; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 10 | 11 | public class SetAsserts { 12 | public static void assertSet(Set expected, IndexedSet actual, int removeFrom, int removeTo) throws Exception { 13 | assertSizes(expected, actual); 14 | assertElements(expected, actual, removeFrom, removeTo); 15 | assertElementsInverse(expected, actual, removeFrom, removeTo); 16 | 17 | assertInvalidIndex(actual, -1); 18 | assertInvalidIndex(actual, actual.rawSize()); 19 | assertForEach(expected, actual); 20 | 21 | assertThat(actual.contains("bbb")).isFalse(); 22 | assertThat(actual.getIndex("bbb")).isLessThan(0); 23 | 24 | assertCommonProperties(expected, actual); 25 | 26 | assertSerialization(actual); 27 | } 28 | 29 | private static void assertSerialization(IndexedSet actual) throws IOException, ClassNotFoundException { 30 | byte[] serialized = SerializationHelper.testSerialize(actual); 31 | // byte[] serializedExpected = SerializationHelper.testSerialize(expected); 32 | // if (expected.size() > 10 && removeTo - removeFrom == 0) 33 | // assertThat(serialized.length).isLessThan(2 * serializedExpected.length); 34 | 35 | Set deserialized = SerializationHelper.testDeserialize(serialized); 36 | assertThat(deserialized).isEqualTo(actual); 37 | } 38 | 39 | private static void assertCommonProperties(Set expected, IndexedSet actual) { 40 | assertThat(actual.isEmpty()).isEqualTo(expected.isEmpty()); 41 | 42 | assertThat(expected).isEqualTo(actual); 43 | assertThat(actual.toString()).isEqualTo(expected.toString()); 44 | 45 | assertThat(actual).isEqualTo(expected); 46 | assertThat(actual.hashCode()).isEqualTo(expected.hashCode()); 47 | 48 | HashSet unordered = new HashSet<>(expected); 49 | assertThat(actual).isEqualTo(unordered); 50 | assertThat(actual.hashCode()).isEqualTo(unordered.hashCode()); 51 | 52 | unordered.remove("aaa0"); 53 | unordered.add("bbb0"); 54 | assertThat(actual).isNotEqualTo(unordered); 55 | assertThat(actual.hashCode()).isNotEqualTo(unordered.hashCode()); 56 | } 57 | 58 | private static void assertForEach(Set expected, IndexedSet actual) { 59 | Iterator expectedIterator = expected.iterator(); 60 | actual.forEach(obj -> { 61 | assertThat(expectedIterator).hasNext(); 62 | String expectedEntry = expectedIterator.next(); 63 | assertThat(obj).isEqualTo(expectedEntry); 64 | }); 65 | } 66 | 67 | private static void assertInvalidIndex(IndexedSet actual, int index) { 68 | assertThatThrownBy(() -> actual.getEntryAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 69 | assertThatThrownBy(() -> actual.removeAt(index)).isInstanceOfAny(UnsupportedOperationException.class, IndexOutOfBoundsException.class); 70 | } 71 | 72 | private static void assertElements(Set expected, IndexedSet actual, int removeFrom, int removeTo) { 73 | Iterator keysIterator = actual.iterator(); 74 | 75 | int index = 0; 76 | for (String entry : expected) { 77 | if (index == removeFrom) index = removeTo; 78 | assertThat(actual.getIndex(entry)).isEqualTo(index); 79 | assertThat(actual.getEntryAt(index)).isEqualTo(entry); 80 | 81 | assertThat(keysIterator.hasNext()).isTrue(); 82 | assertThat(keysIterator.next()).isEqualTo(entry); 83 | 84 | index++; 85 | } 86 | 87 | assertThat(keysIterator.hasNext()).isFalse(); 88 | } 89 | 90 | private static void assertElementsInverse(Set expectedSet, IndexedSet actual, int removeFrom, int removeTo) { 91 | ListIterator keysIterator = actual.iterator(actual.rawSize()); 92 | ListIterator revIterator = new ArrayList<>(expectedSet).listIterator(actual.size()); 93 | while (keysIterator.hasNext()) keysIterator.next(); 94 | while (revIterator.hasNext()) revIterator.next(); 95 | 96 | assertThat(keysIterator.nextIndex()).isEqualTo(translateIndex(revIterator.nextIndex(), removeFrom, removeTo)); 97 | assertThat(keysIterator.previousIndex()).isEqualTo(translateIndex(revIterator.previousIndex(), removeFrom, removeTo)); 98 | 99 | int index = expectedSet.size() - 1; 100 | while (revIterator.hasPrevious()) { 101 | Object entry = revIterator.previous(); 102 | 103 | assertThat(actual.getIndex(entry)).isEqualTo(translateIndex(index, removeFrom, removeTo)); 104 | assertThat(actual.getEntryAt(translateIndex(index, removeFrom, removeTo))).isEqualTo(entry); 105 | 106 | assertThat(keysIterator.hasPrevious()).isTrue(); 107 | assertThat(keysIterator.previous()).isEqualTo(entry); 108 | 109 | assertThat(keysIterator.nextIndex()).isEqualTo(translateIndex(revIterator.nextIndex(), removeFrom, removeTo)); 110 | assertThat(keysIterator.previousIndex()).isEqualTo(translateIndex(revIterator.previousIndex(), removeFrom, removeTo)); 111 | 112 | index--; 113 | } 114 | 115 | assertThat(keysIterator.hasPrevious()).isFalse(); 116 | assertThatThrownBy(keysIterator::previous).isInstanceOf(NoSuchElementException.class); 117 | } 118 | 119 | private static void assertSizes(Set expected, IndexedSet actual) { 120 | assertThat(actual.size()).isEqualTo(expected.size()); 121 | } 122 | 123 | private static int translateIndex(int index, int removeFrom, int removeTo) { 124 | if (index >= removeFrom) 125 | index += (removeTo - removeFrom); 126 | return index; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/support/TestSizeUtils.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.support; 2 | 3 | import net.intelie.introspective.ObjectSizer; 4 | import net.intelie.introspective.reflect.ReflectionCache; 5 | import net.intelie.introspective.util.IdentityVisitedSet; 6 | import net.intelie.tinymap.util.SuppressForbidden; 7 | 8 | import java.lang.ref.WeakReference; 9 | import java.util.*; 10 | import java.util.concurrent.atomic.AtomicLong; 11 | 12 | public class TestSizeUtils { 13 | public static String formatBytes(double value) { 14 | String[] NAMES = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; 15 | int name = 0; 16 | while (Math.abs(value) >= 1024) { 17 | value /= 1024; 18 | name++; 19 | } 20 | 21 | return String.format(Locale.US, 22 | name == 0 ? "%,.0f" : "%,.2f", value) + " " + NAMES[name]; 23 | } 24 | 25 | public static String formattedSize(Object obj) { 26 | return formatBytes(size(obj)); 27 | } 28 | 29 | public static String formattedSizeOnlyStructure(Object obj) { 30 | return formatBytes(sizeOnlyStructure(obj)); 31 | } 32 | 33 | public static long size(Object obj) { 34 | ObjectSizer sizer = new ObjectSizer(); 35 | sizer.resetTo(obj); 36 | long total = 0; 37 | long skipped = 0; 38 | while (sizer.moveNext()) { 39 | total += sizer.bytes(); 40 | 41 | if (WeakReference.class.isAssignableFrom(sizer.type())) { 42 | sizer.skipChildren(); 43 | skipped++; 44 | } 45 | } 46 | assert sizer.skipped() == skipped; 47 | return total; 48 | } 49 | 50 | public static long sizeOnlyStructure(Object obj) { 51 | ObjectSizer sizer = new ObjectSizer(new ReflectionCache(), new IdentityVisitedSet(), 1 << 20); 52 | sizer.resetTo(obj); 53 | long total = 0; 54 | while (sizer.moveNext()) { 55 | if (!sizer.type().equals(String.class) && !sizer.type().equals(Double.class)) 56 | total += sizer.bytes(); 57 | } 58 | assert sizer.skipped() == 0; 59 | return total; 60 | } 61 | 62 | @SuppressForbidden 63 | public static void dump(Object obj) { 64 | ObjectSizer sizer = new ObjectSizer(); 65 | sizer.resetTo(obj); 66 | 67 | Map, AtomicLong> counts = new HashMap<>(); 68 | Map, AtomicLong> total = new HashMap<>(); 69 | Map doubleCount = new HashMap<>(); 70 | long skipped = 0; 71 | long totalCount = 0, uniqueCount = 0, totalBytes = 0, uniqueBytes = 0; 72 | 73 | Map mapCounts = new HashMap<>(); 74 | 75 | while (sizer.moveNext()) { 76 | totalCount++; 77 | totalBytes += sizer.bytes(); 78 | 79 | counts.computeIfAbsent(sizer.type(), x -> new AtomicLong()).incrementAndGet(); 80 | total.computeIfAbsent(sizer.type(), x -> new AtomicLong()).addAndGet(sizer.bytes()); 81 | 82 | if (doubleCount.computeIfAbsent(new UniqueWrapper(sizer.current()), x -> new AtomicLong()).incrementAndGet() == 1) { 83 | uniqueCount++; 84 | uniqueBytes += sizer.bytes(); 85 | } 86 | 87 | if (sizer.current() instanceof Map) 88 | mapCounts.computeIfAbsent(((Map) sizer.current()).size(), x -> new AtomicLong()).incrementAndGet(); 89 | 90 | if (WeakReference.class.isAssignableFrom(sizer.type())) { 91 | sizer.skipChildren(); 92 | skipped++; 93 | } 94 | } 95 | System.out.println(mapCounts); 96 | System.out.printf((Locale) null, "total %10d %10s\n", totalCount, formatBytes(totalBytes)); 97 | System.out.printf((Locale) null, "unique %10d %10s\n", uniqueCount, formatBytes(uniqueBytes)); 98 | System.out.printf((Locale) null, "duplicate %10d %10s\n", (totalCount - uniqueCount), formatBytes(totalBytes - uniqueBytes)); 99 | 100 | System.out.println("histogram:"); 101 | total.entrySet() 102 | .stream() 103 | .sorted(Comparator.comparing(x -> -x.getValue().get())) 104 | .forEach(entry -> System.out.printf((Locale) null, " %6d %10s %s\n", 105 | counts.get(entry.getKey()).get(), 106 | TestSizeUtils.formatBytes(entry.getValue().get()), 107 | entry.getKey().getCanonicalName())); 108 | assert sizer.skipped() == skipped; 109 | 110 | sizer.clear(); 111 | 112 | System.out.println("top 10 duplicates: "); 113 | 114 | doubleCount.entrySet().stream() 115 | .filter(x -> x.getValue().get() > 1) 116 | .sorted((Comparator.comparing(x -> -x.getValue().get()))) 117 | .limit(10) 118 | .forEach(entry -> System.out.println(" " + entry.getKey().obj + " \t" + entry.getValue().get())); 119 | } 120 | 121 | private static class UniqueWrapper { 122 | private final Object obj; 123 | 124 | public UniqueWrapper(Object obj) { 125 | this.obj = obj; 126 | } 127 | 128 | @Override 129 | public boolean equals(Object o) { 130 | if (!(o instanceof UniqueWrapper)) return false; 131 | Object that = ((UniqueWrapper) o).obj; 132 | if (that == null) return obj == null; 133 | if (obj == that) return true; 134 | Class clazz = obj.getClass(); 135 | if (!clazz.isAssignableFrom(that.getClass())) return false; 136 | if (byte[].class.equals(clazz)) 137 | return Arrays.equals((byte[]) obj, (byte[]) obj); 138 | if (short[].class.equals(clazz)) 139 | return Arrays.equals((short[]) obj, (short[]) obj); 140 | if (int[].class.equals(clazz)) 141 | return Arrays.equals((int[]) obj, (int[]) obj); 142 | if (long[].class.equals(clazz)) 143 | return Arrays.equals((long[]) obj, (long[]) obj); 144 | if (float[].class.equals(clazz)) 145 | return Arrays.equals((float[]) obj, (float[]) obj); 146 | if (double[].class.equals(clazz)) 147 | return Arrays.equals((double[]) obj, (double[]) obj); 148 | if (char[].class.equals(clazz)) 149 | return Arrays.equals((char[]) obj, (char[]) obj); 150 | if (boolean[].class.equals(clazz)) 151 | return Arrays.equals((boolean[]) obj, (boolean[]) obj); 152 | return Objects.equals(obj, that); 153 | } 154 | 155 | @Override 156 | public int hashCode() { 157 | Class clazz = obj.getClass(); 158 | if (byte[].class.equals(clazz)) 159 | return Arrays.hashCode((byte[]) obj); 160 | if (short[].class.equals(clazz)) 161 | return Arrays.hashCode((short[]) obj); 162 | if (int[].class.equals(clazz)) 163 | return Arrays.hashCode((int[]) obj); 164 | if (long[].class.equals(clazz)) 165 | return Arrays.hashCode((long[]) obj); 166 | if (float[].class.equals(clazz)) 167 | return Arrays.hashCode((float[]) obj); 168 | if (double[].class.equals(clazz)) 169 | return Arrays.hashCode((double[]) obj); 170 | if (char[].class.equals(clazz)) 171 | return Arrays.hashCode((char[]) obj); 172 | if (boolean[].class.equals(clazz)) 173 | return Arrays.hashCode((boolean[]) obj); 174 | return Objects.hashCode(obj); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/util/DefaultDoubleCacheTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class DefaultDoubleCacheTest { 8 | @Test 9 | public void testCacheHit() { 10 | DefaultDoubleCache cache = new DefaultDoubleCache(); 11 | Double cached1 = cache.get(Double.parseDouble("123.456")); 12 | Double cached2 = cache.get((Double)Double.parseDouble("123.456")); 13 | 14 | assertThat(cached1).isSameAs(cached2); 15 | } 16 | 17 | @Test 18 | public void testNegativeZero() { 19 | DefaultDoubleCache cache = new DefaultDoubleCache(); 20 | Double original1 = Double.parseDouble("0.0"); 21 | Double original2 = Double.parseDouble("-0.0"); 22 | 23 | Double cached1 = cache.get(original1); 24 | Double cached2 = cache.get(original2); 25 | 26 | assertThat(cached1).isNotEqualTo(cached2); 27 | assertThat(cached1.hashCode()).isNotEqualTo(cached2.hashCode()); 28 | } 29 | 30 | 31 | @Test 32 | public void testSmallCache() { 33 | DefaultDoubleCache cache = new DefaultDoubleCache(); 34 | Double original1 = Double.parseDouble("123"); 35 | Double cached1 = cache.get(original1); 36 | Double cached2 = cache.get(original1); 37 | 38 | assertThat(cached1).isEqualTo(original1) 39 | .isNotSameAs(original1) 40 | .isSameAs(cached2); 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/util/GenerateClasses.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import freemarker.cache.ClassTemplateLoader; 4 | import freemarker.template.Configuration; 5 | import freemarker.template.Template; 6 | import freemarker.template.TemplateException; 7 | 8 | import java.io.*; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Paths; 11 | import java.util.Collections; 12 | 13 | public class GenerateClasses { 14 | public static void main(String[] args) throws Exception { 15 | Configuration configuration = new Configuration(Configuration.VERSION_2_3_23); 16 | configuration.setTemplateLoader(new ClassTemplateLoader(GenerateClasses.class.getClassLoader(), "/templates")); 17 | 18 | runFor(configuration, "TinyMapGenerated.template", "TinyMapGenerated.java"); 19 | 20 | } 21 | 22 | private static void runFor(Configuration configuration, String templateName, String fileName) throws IOException, TemplateException { 23 | File file = Paths.get(System.getProperty("user.dir"), "src/main/java/net/intelie/tinymap/util/" + fileName).toFile(); 24 | try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { 25 | Template template = configuration.getTemplate(templateName); 26 | template.process(Collections.emptyMap(), writer); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/util/ObjectOptimizerTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.collect.ImmutableSet; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 14 | import static org.mockito.Mockito.*; 15 | 16 | public class ObjectOptimizerTest { 17 | @Test 18 | public void testOptimizeSimpleMap() { 19 | LinkedHashMap obj = new LinkedHashMap<>(); 20 | obj.put("aaa", Arrays.asList(123, "456")); 21 | obj.put("bbb", Collections.singletonMap("ccc", 111)); 22 | obj.put("ddd", ImmutableMap.of("eee", 222, "fff", 333.0)); 23 | obj.put("ggg", ImmutableSet.of("eee", 222, "fff", 333.0)); 24 | 25 | ObjectOptimizer optimizer = new ObjectOptimizer(new DefaultObjectCache()); 26 | Object optimized = optimizer.optimize(obj); 27 | 28 | assertThat(optimized).isEqualTo(obj); 29 | } 30 | 31 | @Test 32 | public void testOptimizeSimpleMapNoCache() { 33 | LinkedHashMap obj = new LinkedHashMap<>(); 34 | obj.put("aaa", Arrays.asList(123, "456")); 35 | obj.put("bbb", Collections.singletonMap("ccc", 111)); 36 | obj.put("ddd", ImmutableMap.of("eee", 222, "fff", 333.0)); 37 | obj.put("ggg", ImmutableSet.of("eee", 222, "fff", 333.0)); 38 | 39 | ObjectOptimizer optimizer = new ObjectOptimizer(null); 40 | Object optimized = optimizer.optimize(obj); 41 | 42 | assertThat(optimized).isEqualTo(obj); 43 | } 44 | 45 | @Test 46 | public void testOptimizeSimpleMapWithException() { 47 | RuntimeException ex = new RuntimeException("abc"); 48 | Map map = mock(Map.class); 49 | doThrow(ex).when(map).forEach(any()); 50 | 51 | LinkedHashMap obj = new LinkedHashMap<>(); 52 | obj.put("aaa", Arrays.asList(123, "456", Collections.singleton(map))); 53 | obj.put("bbb", Collections.singletonMap("ccc", "ddd")); 54 | obj.put("ddd", ImmutableMap.of("eee", 222, "fff", 333.0)); 55 | 56 | ObjectOptimizer optimizer = new ObjectOptimizer(new DefaultObjectCache()); 57 | assertThatThrownBy(() -> optimizer.optimize(obj)).isSameAs(ex); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/util/StringCacheAdapterTest.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class StringCacheAdapterTest { 8 | @Test 9 | public void testSameHashCodeStringAndStringBuilder() { 10 | StringBuilder builder = new StringBuilder("abcdef"); 11 | 12 | StringCacheAdapter adapter = new StringCacheAdapter(); 13 | assertThat(adapter.contentHashCode(builder)).isEqualTo(adapter.contentHashCode("abcdef")); 14 | } 15 | } -------------------------------------------------------------------------------- /src/test/java/net/intelie/tinymap/util/SuppressForbidden.java: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.CLASS) 9 | @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) 10 | public @interface SuppressForbidden { 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/templates/TinyMapGenerated.template: -------------------------------------------------------------------------------- 1 | package net.intelie.tinymap.util; 2 | 3 | import net.intelie.tinymap.TinyMap; 4 | import net.intelie.tinymap.TinySet; 5 | 6 | import java.io.Serializable; 7 | 8 | 9 | //AUTO-GENERATED SOURCE. See GenerateClasses 10 | public abstract class TinyMapGenerated { 11 | public static TinyMap createUnsafe(TinySet keys, Object[] values) { 12 | switch (values.length) { 13 | <#list 1..16 as n> 14 | case ${n}: 15 | return new Size${n}<>(keys<#list 0..n-1 as i>, values[${i}]); 16 | 17 | default: 18 | return new SizeAny<>(keys, values); 19 | } 20 | } 21 | 22 | <#list 1..16 as n> 23 | public static class Size${n} extends TinyMap implements Serializable { 24 | private static final long serialVersionUID = 1L; 25 | 26 | <#list 0..n-1 as i> 27 | private final Object v${i}; 28 | 29 | 30 | public Size${n}(TinySet keys<#list 0..n-1 as i>, Object v${i}) { 31 | super(keys); 32 | Preconditions.checkArgument(keys.size() == ${n}, "keys and values must have same size"); 33 | <#list 0..n-1 as i> 34 | this.v${i} = v${i}; 35 | 36 | } 37 | 38 | @SuppressWarnings("unchecked") 39 | @Override 40 | public V getValueAt(int index) { 41 | switch (index) { 42 | <#list 0..n-1 as i> 43 | case ${i}: 44 | return (V) v${i}; 45 | 46 | default: 47 | throw new ArrayIndexOutOfBoundsException(index); 48 | } 49 | } 50 | } 51 | 52 | 53 | 54 | public static class SizeAny extends TinyMap { 55 | private static final long serialVersionUID = 1L; 56 | 57 | public final Object[] values; 58 | 59 | public SizeAny(TinySet keys, Object[] values) { 60 | super(keys); 61 | Preconditions.checkArgument(keys.size() == values.length, "keys and values must have same size"); 62 | this.values = values; 63 | } 64 | 65 | @SuppressWarnings("unchecked") 66 | @Override 67 | public V getValueAt(int index) { 68 | return (V) values[index]; 69 | } 70 | } 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | testwith() { 3 | echo Testing in $1 4 | 5 | docker run -it --rm \ 6 | --mount "type=bind,source=$HOME/.m2,destination=/root/.m2,readonly" \ 7 | --mount "type=bind,source=$(pwd),destination=/root/source,readonly" \ 8 | -w /root/work \ 9 | $1 \ 10 | /bin/bash -c "cp -r /root/source/* /root/work/ && mvn clean test" 11 | } 12 | 13 | set -e 14 | testwith maven:3-jdk-8 15 | testwith maven:3-jdk-9 16 | testwith maven:3-jdk-10 17 | testwith maven:3-jdk-11 18 | testwith maven:3-jdk-12 19 | testwith maven:3-jdk-13 20 | testwith maven:3-amazoncorretto-8 21 | testwith maven:3-amazoncorretto-11 22 | 23 | --------------------------------------------------------------------------------