├── .gitignore ├── README.md ├── build.gradle └── src ├── main └── java │ └── de │ └── rwhq │ ├── btree │ ├── AdjustmentAction.java │ ├── BTree.java │ ├── InnerNode.java │ ├── InnerNodeManager.java │ ├── LeafNode.java │ ├── LeafPageManager.java │ ├── MultiMap.java │ ├── Node.java │ ├── Range.java │ └── run │ │ ├── PrintTree.java │ │ ├── commons-logging.properties │ │ └── log4j.xml │ ├── comparator │ ├── IntegerComparator.java │ ├── LongComparator.java │ └── StringComparator.java │ ├── io │ ├── AbstractMustInitializeOrLoad.java │ ├── MustBeOpened.java │ ├── MustInitializeOrLoad.java │ ├── NoSpaceException.java │ └── rm │ │ ├── AbstractPageManager.java │ │ ├── CachedResourceManager.java │ │ ├── ComplexPage.java │ │ ├── DataPage.java │ │ ├── DataPageManager.java │ │ ├── DuplicatePageIdException.java │ │ ├── DynamicDataPage.java │ │ ├── FileResourceManager.java │ │ ├── InvalidPageException.java │ │ ├── PageManager.java │ │ ├── PageNotFoundException.java │ │ ├── PagePointer.java │ │ ├── PageSize.java │ │ ├── RawPage.java │ │ ├── ReferenceCachedResourceManager.java │ │ ├── ResourceHeader.java │ │ ├── ResourceHeaderOverflowPage.java │ │ ├── ResourceManager.java │ │ ├── ResourceManagerBuilder.java │ │ └── WrongPageSizeException.java │ └── serializer │ ├── FixLengthSerializer.java │ ├── FixedStringSerializer.java │ ├── IntegerSerializer.java │ ├── LongSerializer.java │ ├── PagePointSerializer.java │ ├── Serializer.java │ ├── StringCutSerializer.java │ └── StringSerializer.java └── test └── java ├── commons-logging.properties ├── de └── rwhq │ ├── btree │ ├── BTreeTest.java │ ├── LeafNodeTest.java │ ├── LeafNodeUnitTest.java │ ├── PageManagerTest.java │ └── RangeTest.java │ ├── io │ └── rm │ │ ├── CachedResourceManagerTest.java │ │ ├── DynamicDataPageTest.java │ │ ├── FileResourceManagerTest.java │ │ ├── ReferenceCachedResourceManagerTest.java │ │ ├── ResourceHeaderTest.java │ │ ├── ResourceManagerBuilderTest.java │ │ └── ResourceManagerTest.java │ └── serializer │ ├── FixedStringSerializerTest.java │ ├── PagePointerSerializerTest.java │ └── StringCutSerializerTest.java └── log4j.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .classpath 3 | tpch/ 4 | .project 5 | .settings/ 6 | build.properties 7 | build/ 8 | .externalToolBuilders/ 9 | bin/ 10 | target/ 11 | .gradle/ 12 | libs/ 13 | *.iml 14 | *.ipr 15 | *.iws 16 | out/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This Project provides two cool things: 4 | 5 | - A flexible way of creating and handling pages on different resources (like the file system) 6 | - An implementation of a B+-Tree that can 7 | - be ordered arbitrarily by providing a comparator 8 | - serializers can be handed in as dependency 9 | - the resource manager to which the B-Tree is persisted is handed in as interface. 10 | 11 | # Dev Requirements 12 | 13 | - gradle >= 1.0-milestone-6 (install on mac with homebrew: `brew install gradle --HEAD`) 14 | 15 | # Getting started 16 | 17 | ## This Repository 18 | 19 | get started by cloning the repository: 20 | 21 | git clone git://github.com/rweng/multimap.git 22 | cd index 23 | # look at all tasks you can do with the build manager 24 | gradle tasks 25 | # if you are in eclipse, generate the eclipse files to be able to open the repository as eclipse project 26 | gradle eclipse 27 | # run the tests 28 | gradle test 29 | # generate javadoc 30 | gradle javadoc 31 | open build/doc/javadoc/index.html 32 | 33 | ## Creating a ResourceManager 34 | 35 | // creates a cached resource manager. All values are the default values and can be ommited (except file()) 36 | new ResourceManagerBuilder().file("/tmp/test").useLock(true).useCache(true).cacheSize(100) 37 | .pageSize(PageSize.DEFAULT_PAGE_SIZE).build(); 38 | 39 | // creates a plain FileResourceManager without Caching 40 | new ResourceManagerBuilder().file("/tmp/test").useCache(false).build(); 41 | 42 | ## Creating a BTree Instance 43 | 44 | @Test 45 | public void staticMethodConstructor() throws IOException { 46 | final BTree btree = 47 | BTree.create(createResourceManager(true), IntegerSerializer.INSTANCE, FixedStringSerializer.INSTANCE, 48 | IntegerComparator.INSTANCE); 49 | btree.initialize(); 50 | } 51 | 52 | private AutoSaveResourceManager createResourceManager(final boolean reset) { 53 | if (reset) 54 | file.delete(); 55 | return new ResourceManagerBuilder().file(file).buildAutoSave(); 56 | } 57 | 58 | # Documentation 59 | 60 | You can find the Javadoc [here](http://rweng.github.com/jb-tree/doc/) - 61 | If you click on Network, you can see a branch called gh-pages on which you can see the commit the Javadocs are about (not always completely up-to-date). 62 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'eclipse' 13 | apply plugin: 'idea' 14 | 15 | // java 16 | sourceCompatibility = 1.6 17 | version = '0.0.3' 18 | 19 | jar { 20 | // include all dependency jars in this jars lib/ folder 21 | from configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } 22 | 23 | manifest { 24 | attributes 'Implementation-Title': 'jb-tree', 'Implementation-Version': version 25 | } 26 | 27 | // add source code to distribution jar 28 | from sourceSets.main.allJava 29 | } 30 | 31 | sourceSets { 32 | main { 33 | java { 34 | srcDir 'src/main/java' 35 | } 36 | } 37 | test { 38 | java { 39 | srcDir 'src/test/java' 40 | } 41 | } 42 | } 43 | 44 | repositories { 45 | mavenCentral() 46 | mavenRepo urls: "http://guice-maven.googlecode.com/svn/trunk" 47 | mavenRepo urls: "http://repository.codehaus.org" 48 | } 49 | 50 | dependencies { 51 | compile group: 'com.google.guava', name: 'guava', version: '10.0.1' 52 | 53 | compile "commons-logging:commons-logging:1.1.1" 54 | 55 | // TEST-COMPILE 56 | testCompile "log4j:log4j:1.2.16" 57 | 58 | testCompile "junit:junit:4.10" 59 | testCompile "org.mockito:mockito-all:1.8.5" 60 | testCompile "org.easytesting:fest-assert:1.4" 61 | } 62 | 63 | test { 64 | testLogging.showStandardStreams = true 65 | jvmArgs '-Xms1024m', '-Xmx1024m' 66 | } 67 | 68 | sourceSets.test.runtimeClasspath += files(sourceSets.test.java.srcDirs) 69 | 70 | 71 | //************* OWN TASKS ******************** 72 | 73 | task publishJar(dependsOn: jar) << { 74 | run("cp -f " + jar.archivePath + " /Users/robin/Dropbox/public/jb-tree-0.0.3.jar") 75 | } 76 | 77 | //************* HELPER METHODS ******************** 78 | 79 | // run a command with direct process output 80 | def run(command) { 81 | def process = command.execute() 82 | process.consumeProcessOutput(System.out, System.err) 83 | process.waitForOrKill(0) 84 | return process // use to get exit code et cetera 85 | } -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/AdjustmentAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree; 11 | 12 | class AdjustmentAction { 13 | public enum ACTION {INSERT_NEW_NODE, UPDATE_KEY} 14 | 15 | private ACTION action; 16 | private byte[] serializedKey; 17 | private Integer pageId; 18 | 19 | protected AdjustmentAction(final ACTION action, final byte[] serializedKey, final Integer pageId){ 20 | this.setAction(action); 21 | this.setSerializedKey(serializedKey); 22 | this.setPageId(pageId); 23 | } 24 | 25 | /** 26 | * @param action the action to set 27 | */ 28 | public void setAction(final ACTION action) { 29 | this.action = action; 30 | } 31 | 32 | /** 33 | * @return the action 34 | */ 35 | public ACTION getAction() { 36 | return action; 37 | } 38 | 39 | /** 40 | * @param pageId the node to set 41 | */ 42 | public void setPageId(final Integer pageId) { 43 | this.pageId = pageId; 44 | } 45 | 46 | /** 47 | * @return the pageId 48 | */ 49 | public Integer getPageId() { 50 | return pageId; 51 | } 52 | 53 | /** 54 | * @param serializedKey the serializedKey to set 55 | */ 56 | public void setSerializedKey(final byte[] serializedKey) { 57 | this.serializedKey = serializedKey; 58 | } 59 | 60 | /** 61 | * @return the serializedKey 62 | */ 63 | public byte[] getSerializedKey() { 64 | return serializedKey; 65 | } 66 | 67 | public String toString(){ 68 | return "AdjustmentAction(type: " + action + ", pageId: " + pageId + ")"; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/BTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import com.google.common.annotations.VisibleForTesting; 14 | import com.google.common.base.Objects; 15 | import com.google.common.collect.Lists; 16 | import de.rwhq.btree.AdjustmentAction.ACTION; 17 | import de.rwhq.io.MustInitializeOrLoad; 18 | import de.rwhq.io.rm.DataPageManager; 19 | import de.rwhq.io.rm.FileResourceManager; 20 | import de.rwhq.io.rm.RawPage; 21 | import de.rwhq.io.rm.ResourceManager; 22 | import de.rwhq.serializer.FixLengthSerializer; 23 | import de.rwhq.serializer.PagePointSerializer; 24 | import org.apache.commons.logging.Log; 25 | import org.apache.commons.logging.LogFactory; 26 | import org.w3c.dom.ranges.RangeException; 27 | 28 | import java.io.IOException; 29 | import java.util.AbstractMap.SimpleEntry; 30 | import java.util.*; 31 | 32 | import static com.google.common.base.Preconditions.*; 33 | 34 | 35 | /** 36 | * The Btree page, all leafs and innernodes have to be stored in the same RawPageManager. We used to have it differently 37 | * but it is simpler this way. Now The BTree can make sure that all use the same serializers and comparators. 38 | *

39 | * Header: NUM_OF_ENTRIES ROOT_ID (here comes serializers etc) 40 | * 41 | * @param 42 | * @param 43 | */ 44 | public class BTree implements MultiMap, MustInitializeOrLoad { 45 | 46 | private static final Log LOG = LogFactory.getLog(BTree.class); 47 | 48 | 49 | /** 50 | * This is the probably least verbose method for creating BTrees. It accepts a file versus the FileResourceManager of 51 | * the constructor. In addition, one does not have to repeat the generic on the right hand side of the creation 52 | * assignment. 53 | * 54 | * @param rm 55 | * resourceManager 56 | * @param keySerializer 57 | * @param valueSerializer 58 | * @param comparator 59 | * @param 60 | * @param 61 | * @return a new BTree instance 62 | * 63 | * @throws IOException 64 | */ 65 | public static BTree create(final ResourceManager rm, final FixLengthSerializer keySerializer, 66 | final FixLengthSerializer valueSerializer, 67 | final Comparator comparator) throws IOException { 68 | 69 | checkNotNull(rm); 70 | checkNotNull(keySerializer); 71 | checkNotNull(valueSerializer); 72 | checkNotNull(comparator); 73 | 74 | if (!rm.isOpen()) 75 | rm.open(); 76 | 77 | return new BTree(rm, keySerializer, valueSerializer, comparator); 78 | } 79 | 80 | private final LeafPageManager leafPageManager; 81 | private final InnerNodeManager innerNodeManager; 82 | private final Comparator comparator; 83 | private final ResourceManager rm; 84 | private RawPage rawPage; 85 | 86 | private Node root; 87 | 88 | private boolean valid = false; 89 | private int numberOfEntries = 0; 90 | private FixLengthSerializer keySerializer; 91 | private FixLengthSerializer valueSerializer; 92 | 93 | /* (non-Javadoc) 94 | * @see MultiMap#size() 95 | */ 96 | @Override 97 | public int getNumberOfEntries() { 98 | ensureValid(); 99 | return numberOfEntries; 100 | } 101 | 102 | /* (non-Javadoc) 103 | * @see com.rwhq.btree.MultiMap#containsKey(java.lang.Object) 104 | */ 105 | @Override 106 | public boolean containsKey(final K key) { 107 | ensureValid(); 108 | 109 | return root.containsKey(key); 110 | } 111 | 112 | /* (non-Javadoc) 113 | * @see MultiMap#get(java.lang.Object) 114 | */ 115 | @Override 116 | public List get(final K key) { 117 | ensureValid(); 118 | 119 | return root.get(key); 120 | } 121 | 122 | 123 | /* (non-Javadoc) 124 | * @see MultiMap#add(java.lang.Object, java.lang.Object) 125 | */ 126 | @Override 127 | public void add(final K key, final V value) { 128 | ensureValid(); 129 | 130 | setNumberOfEntries(getNumberOfEntries() + 1); 131 | 132 | final AdjustmentAction result = root.insert(key, value); 133 | 134 | // insert was successful 135 | if (result == null){ 136 | rawPage.sync(); 137 | return; 138 | } 139 | 140 | // a new root must be created 141 | if (result.getAction() == ACTION.INSERT_NEW_NODE) { 142 | // new root 143 | final InnerNode newRoot = innerNodeManager.createPage(); 144 | newRoot.initRootState(root.getId(), result.getSerializedKey(), result.getPageId()); 145 | setRoot(newRoot); 146 | } 147 | 148 | rawPage.sync(); 149 | } 150 | 151 | /* (non-Javadoc) 152 | * @see MultiMap#remove(java.lang.Object) 153 | */ 154 | @Override 155 | public void remove(final K key) { 156 | ensureValid(); 157 | 158 | numberOfEntries -= root.remove(key); 159 | } 160 | 161 | /* (non-Javadoc) 162 | * @see MultiMap#remove(java.lang.Object, java.lang.Object) 163 | */ 164 | @Override 165 | public void remove(final K key, final V value) { 166 | ensureValid(); 167 | 168 | setNumberOfEntries(getNumberOfEntries() - root.remove(key, value)); 169 | rawPage.sync(); 170 | } 171 | 172 | /* (non-Javadoc) 173 | * @see MultiMap#clear() 174 | */ 175 | @Override 176 | public void clear() throws IOException { 177 | ensureValid(); 178 | rm.clear(); 179 | valid = false; 180 | initialize(); 181 | // just set another root, the other pages stay in the file 182 | // LOG.info("BTree#clear() is not fully implemented yet because" + 183 | // " it is not possible to remove entries from the FileResourceManager"); 184 | } 185 | 186 | /* (non-Javadoc) 187 | * @see MultiMap#getIterator() 188 | */ 189 | @Override 190 | public Iterator getIterator() { 191 | return getIterator(root.getFirstLeafKey(), root.getLastLeafKey()); 192 | } 193 | 194 | /* (non-Javadoc) 195 | * @see MultiMap#getIterator(java.lang.Object, java.lang.Object) 196 | */ 197 | @Override 198 | public Iterator getIterator(final K from, final K to) { 199 | ensureValid(); 200 | 201 | final Iterator result = root.getIterator(from, to); 202 | return result; 203 | } 204 | 205 | /* (non-Javadoc) 206 | * @see ComplexPage#initialize() 207 | */ 208 | @Override 209 | public void initialize() throws IOException { 210 | checkState(!valid, "tree is already valid: %s", this); 211 | 212 | preInitialize(); 213 | setRoot(leafPageManager.createPage()); 214 | setNumberOfEntries(0); 215 | rawPage.sync(); 216 | } 217 | 218 | /* (non-Javadoc) 219 | * @see ComplexPage#load() 220 | */ 221 | @Override 222 | public void load() throws IOException { 223 | checkState(!valid, "BTree is already loaded: %s", this); 224 | 225 | if (LOG.isDebugEnabled()) 226 | LOG.debug("loading BTree"); 227 | 228 | if (!rm.isOpen()) 229 | rm.open(); 230 | 231 | if (!rm.hasPage(1)) { 232 | throw new IOException("Page 1 could not be found. Ensure that the BTree is initialized"); 233 | } 234 | 235 | 236 | rawPage = rm.getPage(1); 237 | numberOfEntries = rawPage.bufferForReading(0).getInt(); 238 | 239 | final int rootId = rawPage.bufferForReading(4).getInt(); 240 | if (leafPageManager.hasPage(rootId)) { 241 | root = leafPageManager.getPage(rootId); 242 | } else if (innerNodeManager.hasPage(rootId)) { 243 | root = innerNodeManager.getPage(rootId); 244 | } else { 245 | throw new IllegalStateException( 246 | "Page 1 does exist, but is neither a leafPage nor a innerNodePage. This could be the result of an unclosed B-Tree."); 247 | } 248 | 249 | valid = true; 250 | 251 | if (LOG.isDebugEnabled()) { 252 | LOG.debug("BTree loaded: "); 253 | LOG.debug("Number of Values: " + numberOfEntries); 254 | LOG.debug("root (id: " + root.getId() + "): " + root); 255 | } 256 | } 257 | 258 | /* (non-Javadoc) 259 | * @see ComplexPage#isValid() 260 | */ 261 | @Override 262 | public boolean isValid() { 263 | return valid; 264 | } 265 | 266 | @Override 267 | public void loadOrInitialize() throws IOException { 268 | try { 269 | load(); 270 | } catch (IOException e) { 271 | initialize(); 272 | } 273 | } 274 | 275 | /** 276 | * sync, close the ResourceManager and set to invalid 277 | * 278 | * @throws IOException 279 | */ 280 | public void close() throws IOException { 281 | rm.close(); 282 | valid = false; 283 | } 284 | 285 | public int getMaxInnerKeys() { 286 | final int realSize = rm.getPageSize() - InnerNode.Header.size() - Integer.SIZE / 8; 287 | return realSize / (Integer.SIZE / 8 + keySerializer.getSerializedLength()); 288 | } 289 | 290 | public int getMaxLeafKeys() { 291 | final int realSize = rm.getPageSize() - LeafNode.Header.size(); 292 | return realSize / (keySerializer.getSerializedLength() + valueSerializer.getSerializedLength()); 293 | } 294 | 295 | public FixLengthSerializer getKeySerializer() { 296 | return keySerializer; 297 | } 298 | 299 | public FixLengthSerializer getValueSerializer() { 300 | return valueSerializer; 301 | } 302 | 303 | @VisibleForTesting 304 | public LeafPageManager getLeafPageManager() { 305 | return leafPageManager; 306 | } 307 | 308 | @VisibleForTesting 309 | public InnerNodeManager getInnerNodeManager() { 310 | return innerNodeManager; 311 | } 312 | 313 | public int getDepth() { 314 | return root.getDepth(); 315 | } 316 | 317 | public Comparator getKeyComparator() { 318 | return this.comparator; 319 | } 320 | 321 | public void bulkInitialize(final SimpleEntry[] kvs, final boolean sorted) throws IOException { 322 | bulkInitialize(kvs, 0, kvs.length - 1, sorted); 323 | } 324 | 325 | /** 326 | * Bulk initialize first creates all leafs, then goes the tree up toIndex create the InnerNodes. 327 | * 328 | * @param kvs 329 | * @param fromIndex 330 | * including 331 | * @param toIndex 332 | * including 333 | * @param sorted 334 | * @throws IOException 335 | */ 336 | public void bulkInitialize(final SimpleEntry[] kvs, final int fromIndex, final int toIndex, final boolean sorted) throws IOException { 337 | LOG.info("bulkInitializing BTree: " + this); 338 | 339 | checkState(!valid, "BTree is already loaded: %s", this); 340 | 341 | for(int i=fromIndex;i<=toIndex;i++){ 342 | checkNotNull(kvs[i], "array given to bulkInitialize must not contain null values"); 343 | } 344 | 345 | final int count = toIndex - fromIndex + 1; 346 | if (count < 0) 347 | throw new IllegalArgumentException( 348 | "fromIndex(" + fromIndex + ") must be smaller or equal to toIndex(" + toIndex + ")"); 349 | 350 | // sort if not already sorted 351 | if (!sorted) { 352 | Arrays.sort(kvs, fromIndex, toIndex + 1, // +1 because excluding toIndex 353 | new Comparator>() { 354 | @Override 355 | public int compare(final SimpleEntry kvSimpleEntry, final SimpleEntry kvSimpleEntry1) { 356 | return comparator.compare(kvSimpleEntry.getKey(), kvSimpleEntry1.getKey()); 357 | } 358 | }); 359 | } 360 | 361 | // initialize but do not create a root page or set the number of keys 362 | preInitialize(); 363 | setNumberOfEntries(count); 364 | 365 | if (getNumberOfEntries() == 0) { 366 | rawPage.sync(); 367 | return; 368 | } 369 | 370 | LeafNode leafPage; 371 | ArrayList keysForNextLayer = new ArrayList(); 372 | ArrayList pageIds = new ArrayList(); 373 | final HashMap pageIdToSmallestKeyMap = new HashMap(); 374 | 375 | 376 | // first insert all leafs and remember the insertedLastKeys 377 | int inserted = 0; 378 | LeafNode previousLeaf = null; 379 | while (inserted < getNumberOfEntries()) { 380 | leafPage = leafPageManager.createPage(false); 381 | 382 | inserted += leafPage.bulkInitialize(kvs, inserted + fromIndex, toIndex); 383 | 384 | pageIdToSmallestKeyMap.put(leafPage.getId(), leafPage.getFirstLeafKeySerialized()); 385 | 386 | // set nextLeafId of previous leaf 387 | // dont store the first key 388 | if (previousLeaf != null) { 389 | // next layer doesn't need the first key 390 | keysForNextLayer.add(leafPage.getFirstLeafKeySerialized()); 391 | previousLeaf.setNextLeafId(leafPage.getId()); 392 | } 393 | 394 | previousLeaf = leafPage; 395 | pageIds.add(leafPage.getId()); 396 | leafPage.rawPage().sync(); 397 | } 398 | 399 | // we are done if everything fits in one leaf 400 | if (pageIds.size() == 1) { 401 | setRoot(leafPageManager.getPage(pageIds.get(0))); 402 | rawPage.sync(); 403 | return; 404 | } 405 | 406 | // if not, build up tree 407 | InnerNode node; 408 | 409 | // for each layer, if pageId == 1, this page becomes the root 410 | while (pageIds.size() > 1) { 411 | if (LOG.isDebugEnabled()) 412 | LOG.debug("next inner node layer"); 413 | 414 | final ArrayList newPageIds = new ArrayList(); 415 | final ArrayList newKeysForNextLayer = new ArrayList(); 416 | inserted = 0; // page ids 417 | 418 | // we assume that fromIndex each pageId the smallest key was stored, we need to remove the last one for InnerNode#bulkinsert() 419 | if (LOG.isDebugEnabled()) { 420 | LOG.debug("new pageIds.size: " + pageIds.size()); 421 | LOG.debug("new keysForNextLayer.size: " + keysForNextLayer.size()); 422 | } 423 | 424 | // fill the layer row while we have pageIds to insert left 425 | while (inserted < pageIds.size()) { 426 | 427 | // create a inner node and store the smallest key 428 | node = innerNodeManager.createPage(false); 429 | newPageIds.add(node.getId()); 430 | final byte[] smallestKey = pageIdToSmallestKeyMap.get(pageIds.get(inserted)); 431 | pageIdToSmallestKeyMap.put(node.getId(), smallestKey); 432 | 433 | // dont insert the first small key to the keys for the next layer 434 | if (inserted > 0) 435 | newKeysForNextLayer.add(smallestKey); 436 | 437 | inserted += node.bulkInitialize(keysForNextLayer, pageIds, inserted); 438 | 439 | if (LOG.isDebugEnabled()) 440 | LOG.debug("inserted " + inserted + " in inner node, pageIds.size()=" + pageIds.size()); 441 | } 442 | 443 | // next turn, insert the ids of the pages we just created 444 | pageIds = newPageIds; 445 | keysForNextLayer = newKeysForNextLayer; 446 | } 447 | 448 | // here, pageIds should be 1, and the page should be an inner node 449 | if (pageIds.size() == 1) { 450 | setRoot(innerNodeManager.getPage(pageIds.get(0))); 451 | rawPage.sync(); 452 | return; 453 | } 454 | } 455 | 456 | public String toString(){ 457 | final Objects.ToStringHelper helper = Objects.toStringHelper(this); 458 | if(isValid()){ 459 | helper.add("numberOfEntries", getNumberOfEntries()); 460 | helper.add("root", root); 461 | } 462 | helper.add("resourceManager", rm); 463 | helper.add("valid", valid); 464 | return helper.toString(); 465 | } 466 | 467 | public void checkStructure() throws IllegalStateException { 468 | root.checkStructure(); 469 | } 470 | 471 | 472 | public Iterator getIterator(final Collection> ranges) { 473 | return new BTreeIterator(ranges); 474 | } 475 | 476 | ResourceManager getResourceManager() { 477 | return rm; 478 | } 479 | 480 | static enum Header { 481 | NUM_OF_ENTRIES(0), 482 | ROOT_ID(Integer.SIZE / 8); 483 | 484 | static int size() { 485 | return (2 * Integer.SIZE) / 8; 486 | } // 8 487 | 488 | private int offset; 489 | 490 | int getOffset() { 491 | return offset; 492 | } 493 | 494 | private Header(final int offset) { 495 | this.offset = offset; 496 | } 497 | } 498 | 499 | /** 500 | * This enum is used to make it possible for all nodes in the BTree to serialize and deserialize in a unique fashion 501 | * 502 | * @author Robin Wenglewski 503 | */ 504 | static enum NodeType { 505 | LEAF_NODE('L'), INNER_NODE('I'); 506 | 507 | public static NodeType deserialize(final char serialized) { 508 | for (final NodeType nt : values()) 509 | if (nt.serialized == serialized) 510 | return nt; 511 | 512 | return null; 513 | } 514 | 515 | private final char serialized; 516 | 517 | public char serialize() { 518 | return serialized; 519 | } 520 | 521 | NodeType(final char value) { 522 | this.serialized = value; 523 | } 524 | } 525 | 526 | private class BTreeIterator implements Iterator { 527 | 528 | private Collection> ranges; 529 | 530 | private Iterator currentIterator = null; 531 | private Iterator> rangesIterator; 532 | 533 | 534 | @Override public boolean hasNext() { 535 | if (currentIterator == null) { 536 | if (!rangesIterator.hasNext()) 537 | return false; 538 | else { 539 | final Range range = rangesIterator.next(); 540 | currentIterator = root.getIterator(range.getFrom(), range.getTo()); 541 | } 542 | } 543 | 544 | if (currentIterator.hasNext()) 545 | return true; 546 | 547 | currentIterator = null; 548 | return hasNext(); 549 | } 550 | 551 | @Override public V next() { 552 | if (!hasNext()) 553 | return null; 554 | else 555 | return currentIterator.next(); 556 | } 557 | 558 | @Override public void remove() { 559 | throw new UnsupportedOperationException(); 560 | } 561 | 562 | public BTreeIterator(final Collection> ranges) { 563 | this.ranges = Range.merge(ranges, comparator); 564 | 565 | if(ranges.isEmpty()){ 566 | this.ranges.add(new Range(null, null)); 567 | } 568 | 569 | this.rangesIterator = this.ranges.iterator(); 570 | } 571 | } 572 | 573 | /** 574 | * This constructor is for manual construction. 575 | * 576 | * @param rm 577 | * @param keySerializer 578 | * @param valueSerializer 579 | * @param comparator 580 | */ 581 | private BTree(final ResourceManager rm, 582 | final FixLengthSerializer keySerializer, final FixLengthSerializer valueSerializer, 583 | final Comparator comparator) { 584 | 585 | this.rm = rm; 586 | this.keySerializer = keySerializer; 587 | this.valueSerializer = valueSerializer; 588 | this.comparator = comparator; 589 | 590 | final DataPageManager keyPageManager = new DataPageManager(rm, PagePointSerializer.INSTANCE, keySerializer); 591 | final DataPageManager valuePageManager = 592 | new DataPageManager(rm, PagePointSerializer.INSTANCE, valueSerializer); 593 | 594 | leafPageManager = new LeafPageManager(rm, valueSerializer, keySerializer, comparator); 595 | innerNodeManager = 596 | new InnerNodeManager(rm, keyPageManager, valuePageManager, leafPageManager, keySerializer, comparator); 597 | 598 | if (LOG.isDebugEnabled()) { 599 | LOG.debug("BTree created: "); 600 | LOG.debug("key serializer: " + keySerializer); 601 | LOG.debug("value serializer: " + valueSerializer); 602 | LOG.debug("comparator: " + comparator); 603 | } 604 | } 605 | 606 | private void ensureValid() { 607 | checkState(isValid(), "Btree must be initialized or loaded"); 608 | } 609 | 610 | private void setRoot(final Node root) { 611 | this.root = root; 612 | rawPage.bufferForWriting(Header.ROOT_ID.getOffset()).putInt(root.getId()); 613 | } 614 | 615 | /** 616 | * Loads a node, either as leaf or as innernode 617 | * 618 | * @param id 619 | * @return 620 | */ 621 | private Node getNode(final int id) { 622 | if (leafPageManager.hasPage(id)) 623 | return leafPageManager.getPage(id); 624 | else 625 | return innerNodeManager.getPage(id); 626 | } 627 | 628 | /** @param i */ 629 | private void setNumberOfEntries(final int i) { 630 | numberOfEntries = i; 631 | rawPage.bufferForWriting(Header.NUM_OF_ENTRIES.getOffset()).putInt(numberOfEntries); 632 | } 633 | 634 | /** 635 | * opens the ResourceManager, sets the rawPage and sets valid, but does not create a root leaf or set the number of 636 | * entries 637 | * @throws java.io.IOException 638 | */ 639 | private void preInitialize() throws IOException { 640 | if (!rm.isOpen()) 641 | rm.open(); 642 | 643 | if (rm.hasPage(1)) 644 | rawPage = rm.getPage(1); 645 | else 646 | rawPage = rm.createPage(); 647 | 648 | if (rawPage.id() != 1) 649 | throw new IllegalStateException("rawPage must have id 1"); 650 | 651 | valid = true; 652 | } 653 | } 654 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/InnerNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree; 11 | 12 | import de.rwhq.btree.AdjustmentAction.ACTION; 13 | import de.rwhq.btree.BTree.NodeType; 14 | import de.rwhq.io.rm.*; 15 | import de.rwhq.serializer.FixLengthSerializer; 16 | import org.apache.commons.logging.Log; 17 | import org.apache.commons.logging.LogFactory; 18 | 19 | import java.io.IOException; 20 | import java.nio.ByteBuffer; 21 | import java.util.*; 22 | 23 | /** 24 | * stores pointers to the keys that get push upwards to InnerNodes from LeafPages, as well as the id of nodes in the 25 | * following order: 26 | *

27 | * NODE_TYPE | NUM_OF_KEYS | NODE_ID | KEY_POINTER | NODE_ID | KEY_POINTER | NODE_ID ... 28 | *

29 | * If the search/insert key is equal to the currently checked key, go to the left. 30 | * 31 | * @param 32 | * @param 33 | */ 34 | class InnerNode implements Node, ComplexPage { 35 | 36 | private static final NodeType NODE_TYPE = NodeType.INNER_NODE; 37 | private static final Log LOG = LogFactory.getLog(InnerNode.class); 38 | 39 | static enum Header { 40 | NODE_TYPE(0) {}, // char 41 | NUMBER_OF_KEYS(Character.SIZE / 8); // int 42 | 43 | private int offset; 44 | 45 | Header(final int offset) { 46 | this.offset = offset; 47 | } 48 | 49 | static int size() { 50 | return (Character.SIZE + Integer.SIZE) / 8; 51 | } // 6 52 | 53 | int getOffset() { 54 | return offset; 55 | } 56 | } 57 | 58 | private class KeyStruct { 59 | 60 | private int pos; 61 | 62 | private KeyStruct(final int pos) { 63 | setPos(pos); 64 | } 65 | 66 | private KeyStruct() { 67 | } 68 | 69 | public void setPos(final int pos) { 70 | this.pos = pos; 71 | } 72 | 73 | public boolean hasNext() { 74 | return pos < getNumberOfKeys() - 1; 75 | } 76 | 77 | public void becomeNext() { 78 | this.pos++; 79 | } 80 | 81 | private int getOffset() { 82 | return Header.size() + 83 | ((pos + 1) * getSizeOfPageId()) + // one id more that pages, the first id 84 | (pos * keySerializer.getSerializedLength()); 85 | } 86 | 87 | 88 | private byte[] getSerializedKey() { 89 | final ByteBuffer buf = rawPage().bufferForReading(getOffset()); 90 | final byte[] byteBuf = new byte[keySerializer.getSerializedLength()]; 91 | buf.get(byteBuf); 92 | 93 | return byteBuf; 94 | } 95 | 96 | private K getKey() { 97 | final ByteBuffer buf = rawPage().bufferForReading(getOffset()); 98 | final byte[] bytes = new byte[keySerializer.getSerializedLength()]; 99 | buf.get(bytes); 100 | return keySerializer.deserialize(bytes); 101 | } 102 | 103 | public String toString() { 104 | return "K(" + getKey() + ")"; 105 | } 106 | 107 | public String toStringWithLeftAndRightKey() { 108 | String str = ""; 109 | if (pos > 0) { 110 | str += new KeyStruct(pos - 1).toString() + " - "; 111 | } 112 | 113 | str += toString(); 114 | 115 | if (!isLastKey()) 116 | str += " - " + new KeyStruct(pos + 1).toString(); 117 | 118 | return str; 119 | } 120 | 121 | private boolean isLastKey() { 122 | return pos == getNumberOfKeys() - 1; 123 | } 124 | 125 | private Node getLeftNode() { 126 | final int offset = getOffset() - Integer.SIZE / 8; 127 | final int pageId = rawPage().bufferForReading(offset).getInt(); 128 | return pageIdToNode(pageId); 129 | } 130 | 131 | private Node getRightNode() { 132 | final int offset = getOffset() + keySerializer.getSerializedLength(); 133 | final int pageId = rawPage().bufferForReading(offset).getInt(); 134 | return pageIdToNode(pageId); 135 | } 136 | 137 | 138 | public boolean isValid() { 139 | return pos < getNumberOfKeys(); 140 | } 141 | } 142 | 143 | private final RawPage rawPage; 144 | private final Comparator comparator; 145 | private final PageManager> leafPageManager; 146 | private final PageManager> innerNodePageManager; 147 | private FixLengthSerializer keySerializer; 148 | 149 | private int numberOfKeys; 150 | private boolean valid = false; 151 | 152 | protected InnerNode( 153 | final RawPage rawPage, 154 | final FixLengthSerializer keySerializer, 155 | final Comparator comparator, 156 | final DataPageManager keyPageManager, 157 | final PageManager> leafPageManager, 158 | final PageManager> innerNodePageManager 159 | ) { 160 | 161 | if (comparator == null) { 162 | throw new IllegalStateException("comparator must not be null"); 163 | } 164 | 165 | this.leafPageManager = leafPageManager; 166 | this.innerNodePageManager = innerNodePageManager; 167 | this.rawPage = rawPage; 168 | this.comparator = comparator; 169 | this.keySerializer = keySerializer; 170 | } 171 | 172 | public void initRootState(final Integer pageId1, final byte[] serializedKey, final Integer pageId2) { 173 | ensureValid(); 174 | validateLengthOfSerializedKey(serializedKey); 175 | 176 | 177 | final ByteBuffer buf = rawPage().bufferForWriting(Header.size()); 178 | 179 | buf.putInt(pageId1); 180 | buf.put(serializedKey); 181 | buf.putInt(pageId2); 182 | 183 | setNumberOfKeys(1); 184 | 185 | rawPage.sync(); 186 | } 187 | 188 | /** @param serializedKey */ 189 | private void validateLengthOfSerializedKey(final byte[] serializedKey) { 190 | if (serializedKey.length != keySerializer.getSerializedLength()) 191 | throw new IllegalArgumentException( 192 | "serializedByteKey has " + serializedKey.length + " bytes instead of " + keySerializer.getSerializedLength()); 193 | } 194 | 195 | public void initRootState(final Integer pageId1, final K key, final Integer pageId2) { 196 | initRootState(pageId1, keySerializer.serialize(key), pageId2); 197 | } 198 | 199 | private Integer getPageIdForKey(final K key) { 200 | final ByteBuffer buf = rawPage.bufferForReading(getOffsetOfPageIdForKey(key)); 201 | return buf.getInt(); 202 | } 203 | 204 | 205 | /** Recursively check, if on of the leafs contains the given key */ 206 | public boolean containsKey(final K key) { 207 | ensureValid(); 208 | ensureKeyNotNull(key); 209 | 210 | return getPageForPageId(getPageIdForKey(key)).containsKey(key); 211 | } 212 | 213 | private void ensureKeyNotNull(final K key) { 214 | if (key == null) { 215 | throw new IllegalArgumentException("key must not be null"); 216 | } 217 | } 218 | 219 | private Node getPageForPageId(final Integer pageId) { 220 | 221 | if (innerNodePageManager.hasPage(pageId)) { 222 | return innerNodePageManager.getPage(pageId); 223 | } 224 | 225 | if (leafPageManager.hasPage(pageId)) 226 | return leafPageManager.getPage(pageId); 227 | 228 | throw new IllegalArgumentException( 229 | "the requested pageId " + pageId + " is neither in InnerNodePageManager nor in LeafPageManager"); 230 | } 231 | 232 | /** 233 | * @param numberOfKeys 234 | * the numberOfKeys to set 235 | */ 236 | private void setNumberOfKeys(final int numberOfKeys) { 237 | this.numberOfKeys = numberOfKeys; 238 | final ByteBuffer buf = rawPage.bufferForWriting(Header.NUMBER_OF_KEYS.getOffset()); 239 | buf.putInt(numberOfKeys); 240 | } 241 | 242 | /* (non-Javadoc) 243 | * @see MultiMap#get(java.lang.Object) 244 | */ 245 | @Override 246 | public List get(final K key) { 247 | ensureValid(); 248 | 249 | if (getNumberOfKeys() == 0) 250 | return new ArrayList(); 251 | 252 | return getNodeForKey(key).get(key); 253 | } 254 | 255 | private Node getNodeForKey(final K key) { 256 | 257 | final KeyStruct ks = getFirstLargerOrEqualKeyStruct(key); 258 | 259 | // largest key 260 | if (ks == null) 261 | return new KeyStruct(getNumberOfKeys() - 1).getRightNode(); 262 | else { 263 | if (comparator.compare(ks.getKey(), key) == 0) { 264 | return ks.getRightNode(); 265 | } else 266 | return ks.getLeftNode(); 267 | } 268 | } 269 | 270 | /* (non-Javadoc) 271 | * @see MultiMap#remove(java.lang.Object) 272 | */ 273 | @Override 274 | public int remove(final K key) { 275 | ensureValid(); 276 | 277 | if (getNumberOfKeys() == 0) 278 | return 0; 279 | 280 | final Integer id = getPageIdForKey(key); 281 | final Node node = getPageForPageId(id); 282 | return node.remove(key); 283 | } 284 | 285 | private int getSizeOfPageId() { 286 | return Integer.SIZE / 8; 287 | } 288 | 289 | private int posOfFirstLargerOrEqualKey(final K key) { 290 | final KeyStruct tmpKeyStruct = new KeyStruct(); 291 | 292 | for (int i = 0; i < getNumberOfKeys(); i++) { 293 | 294 | tmpKeyStruct.setPos(i); 295 | final byte[] sKey = tmpKeyStruct.getSerializedKey(); 296 | if (comparator.compare(keySerializer.deserialize(sKey), key) >= 0) { 297 | return i; 298 | } 299 | } 300 | return -1; 301 | } 302 | 303 | private int getOffsetForLeftPageIdOfKey(final int i) { 304 | return new KeyStruct(i).getOffset() - Integer.SIZE / 8; 305 | } 306 | 307 | private int getOffsetForRightPageIdOfKey(final int i) { 308 | return new KeyStruct(i).getOffset() + keySerializer.getSerializedLength(); 309 | } 310 | 311 | /* (non-Javadoc) 312 | * @see Node#remove(java.lang.Object, java.lang.Object) 313 | */ 314 | @Override 315 | public int remove(final K key, final V value) { 316 | ensureValid(); 317 | 318 | throw new UnsupportedOperationException(); 319 | } 320 | 321 | /* (non-Javadoc) 322 | * @see MultiMap#clear() 323 | */ 324 | @Override 325 | public void destroy() { 326 | ensureValid(); 327 | 328 | throw new UnsupportedOperationException(); 329 | } 330 | 331 | /* (non-Javadoc) 332 | * @see ComplexPage#load() 333 | */ 334 | @Override 335 | public void load() throws IOException { 336 | final ByteBuffer buf = rawPage.bufferForReading(0); 337 | if (NodeType.deserialize(buf.getChar()) != NODE_TYPE) 338 | throw new IOException( 339 | "You are trying to load a InnerNode from a byte array, that does not contain an InnerNode"); 340 | 341 | 342 | buf.position(Header.NUMBER_OF_KEYS.getOffset()); 343 | numberOfKeys = buf.getInt(); 344 | valid = true; 345 | } 346 | 347 | /* (non-Javadoc) 348 | * @see ComplexPage#isValid() 349 | */ 350 | @Override 351 | public boolean isValid() { 352 | return valid; 353 | } 354 | 355 | @Override 356 | public void loadOrInitialize() throws IOException { 357 | try { 358 | load(); 359 | } catch (IOException e) { 360 | initialize(); 361 | } 362 | } 363 | 364 | public int minPageSize() { 365 | return Header.size() + 3 * keySerializer.getSerializedLength() + 4 * Integer.SIZE / 8; 366 | } 367 | 368 | /* (non-Javadoc) 369 | * @see ComplexPage#rawPage() 370 | */ 371 | @Override 372 | public RawPage rawPage() { 373 | return rawPage; 374 | } 375 | 376 | 377 | private int getOffsetOfPageIdForKey(final K key) { 378 | final int posOfFirstLargerOrEqualKey = posOfFirstLargerOrEqualKey(key); 379 | 380 | if (posOfFirstLargerOrEqualKey < 0) // if key is largest 381 | return getOffsetForRightPageIdOfKey((getNumberOfKeys() - 1)); 382 | 383 | 384 | return getOffsetForLeftPageIdOfKey(posOfFirstLargerOrEqualKey); 385 | } 386 | 387 | /** @param key 388 | * @return KeyStruct or null */ 389 | private KeyStruct getFirstLargerOrEqualKeyStruct(final K key) { 390 | final KeyStruct ks = new KeyStruct(0); 391 | while (ks.pos < getNumberOfKeys() && comparator.compare(ks.getKey(), key) < 0) { 392 | ks.becomeNext(); 393 | } 394 | 395 | return ks.isValid() ? ks : null; 396 | } 397 | 398 | /* (non-Javadoc) 399 | * @see com.rwhq.btree.Node#insert(java.lang.Object, java.lang.Object) 400 | */ 401 | @Override 402 | public AdjustmentAction insert(final K key, final V value) { 403 | ensureValid(); 404 | ensureRoot(); 405 | 406 | final Node node; 407 | final KeyStruct ks = getFirstLargerOrEqualKeyStruct(key); 408 | 409 | if (ks == null) { // if key is largest 410 | node = new KeyStruct(getNumberOfKeys() - 1).getRightNode(); 411 | } else { 412 | node = ks.getLeftNode(); 413 | } 414 | 415 | final AdjustmentAction result; 416 | result = node.insert(key, value); 417 | 418 | // insert worked fine, no adjustment 419 | if (result == null) 420 | return null; 421 | 422 | if (result.getAction() == ACTION.UPDATE_KEY) { 423 | return handleUpdateKey(ks, result); 424 | } else if (result.getAction() == ACTION.INSERT_NEW_NODE) { 425 | return handleNewNodeAction(result, ks); 426 | } else { 427 | throw new IllegalStateException("result action must be of type newNode or updateKey"); 428 | } 429 | } 430 | 431 | /** 432 | * this method should be called when an insert action results in a new node that has to be inserted in this node. 433 | *

434 | * 435 | * @param result 436 | * of the insertion 437 | * @param ks 438 | * @return adjustment action or null 439 | */ 440 | private AdjustmentAction handleNewNodeAction(final AdjustmentAction result, final KeyStruct ks) { 441 | if (result.getAction() != ACTION.INSERT_NEW_NODE) { 442 | throw new IllegalArgumentException("result action type must be INSERT_NEW_NODE"); 443 | } 444 | 445 | if (LOG.isDebugEnabled()) { 446 | LOG.debug("handleNewNodeAction()"); 447 | LOG.debug("adjustmentActionKey: " + keySerializer.deserialize(result.getSerializedKey())); 448 | } 449 | // a new child node has been created and a key must be inserted, check for available space 450 | if (getNumberOfKeys() < getMaxNumberOfKeys()) { 451 | // space left, simply insert the key/pointer. 452 | // the key replaces the old key for our node, since the split caused a different 453 | // key to be the now highest in the subtree 454 | 455 | final int posForInsert = ks == null ? getNumberOfKeys() : ks.pos; 456 | insertKeyPointerPageIdAtPosition( 457 | result.getSerializedKey(), result.getPageId(), posForInsert); 458 | 459 | // no further adjustment necessary. even if we inserted to the last position, the 460 | // highest key in the subtree below is still the same, because otherwise we would 461 | // have never ended up here during the descend from the root, or we are in the 462 | // right-most path of the subtree. 463 | return null; 464 | } 465 | 466 | // else split is required, allocate new node 467 | final InnerNode inp = innerNodePageManager.createPage(); 468 | 469 | // move half the keys/pointers to the new node. remember the dropped key. 470 | final byte[] keyUpwardsBytes = moveLastToNewPage(inp, getNumberOfKeys() >> 1); 471 | rawPage().sync(); 472 | inp.rawPage().sync(); 473 | 474 | // decide where to insert the pointer we are supposed to insert 475 | // if the old key position is larger than the current numberOfKeys, the 476 | // entry has to go to the next node 477 | if (ks != null && ks.pos <= getNumberOfKeys()) { 478 | insertKeyPointerPageIdAtPosition(result.getSerializedKey(), result.getPageId(), ks.pos); 479 | } else { 480 | // determine the position where the key should have to be inserted. 481 | // if ks == null, then it was at the end 482 | int pos = ks == null ? getMaxNumberOfKeys() : ks.pos; 483 | 484 | // substract the number of keys in this node, and one more, the omitted one. 485 | pos = pos - getNumberOfKeys() - 1; 486 | inp.insertKeyPointerPageIdAtPosition(result.getSerializedKey(), result.getPageId(), 487 | pos); 488 | } 489 | 490 | 491 | rawPage.sync(); 492 | inp.rawPage().sync(); 493 | 494 | return new AdjustmentAction(ACTION.INSERT_NEW_NODE, keyUpwardsBytes, inp.getId()); 495 | } 496 | 497 | 498 | /** 499 | * This method moves a number of keys to the given new page. However, since one key is droped, this node remains with 500 | * allKeys - keysToBeMoved - 1. 501 | *

502 | * The most left key of the first pageId in the new Node is passed upwards; 503 | * 504 | * @param newPage 505 | * @param numberOfKeys 506 | * @return 507 | */ 508 | private byte[] moveLastToNewPage(final InnerNode newPage, final int numberOfKeys) { 509 | if (LOG.isDebugEnabled()) { 510 | LOG.debug("moveLastToNewPage():"); 511 | LOG.debug("currentPage: " + toString()); 512 | } 513 | 514 | if (!newPage.isValid()) 515 | newPage.initialize(); 516 | 517 | final ByteBuffer buf = newPage.rawPage().bufferForWriting(0); 518 | final int from = getOffsetForLeftPageIdOfKey(getNumberOfKeys() - numberOfKeys); 519 | final int to = Header.size(); 520 | final int length_to_copy = rawPage().bufferForReading(0).limit() - from; 521 | System.arraycopy(rawPage().bufferForWriting(0).array(), from, buf.array(), to, length_to_copy); 522 | newPage.setNumberOfKeys(numberOfKeys); 523 | 524 | // last key is dropped 525 | setNumberOfKeys(getNumberOfKeys() - numberOfKeys - 1); // one key less 526 | 527 | rawPage.sync(); 528 | return newPage.getFirstLeafKeySerialized(); 529 | } 530 | 531 | public byte[] getFirstLeafKeySerialized() { 532 | return new KeyStruct(0).getLeftNode().getFirstLeafKeySerialized(); 533 | } 534 | 535 | public String toString() { 536 | String str = "InnerNode(id: " + getId() + ", keys: " + getNumberOfKeys() + "):"; 537 | KeyStruct keyStruct = null; 538 | do { 539 | if (keyStruct == null) 540 | keyStruct = new KeyStruct(0); 541 | else 542 | keyStruct.becomeNext(); 543 | 544 | str += " " + keyStruct.toString(); 545 | } while (keyStruct.hasNext()); 546 | return str; 547 | } 548 | 549 | private void ensureRoot() { 550 | if (getNumberOfKeys() == 0) 551 | throw new IllegalStateException("use inizializeRootState() for the first insert!"); 552 | } 553 | 554 | /** 555 | * @param serializedKey 556 | * @param pageId 557 | * @param posOfKeyForInsert 558 | */ 559 | private void insertKeyPointerPageIdAtPosition(final byte[] serializedKey, 560 | final Integer pageId, final int posOfKeyForInsert) { 561 | 562 | final KeyStruct thisKeyStruct = new KeyStruct(posOfKeyForInsert); 563 | final ByteBuffer buf = rawPage().bufferForWriting(thisKeyStruct.getOffset()); 564 | 565 | final int spaceNeededForInsert = getSizeOfPageId() + keySerializer.getSerializedLength(); 566 | System.arraycopy(buf.array(), buf.position(), buf.array(), buf.position() + spaceNeededForInsert, 567 | buf.limit() - buf.position() - spaceNeededForInsert); 568 | 569 | buf.put(serializedKey); 570 | buf.putInt(pageId); 571 | 572 | setNumberOfKeys(getNumberOfKeys() + 1); 573 | rawPage().sync(); 574 | } 575 | 576 | public int getMaxNumberOfKeys() { 577 | int size = rawPage.bufferForReading(0).limit() - Header.size(); 578 | 579 | // size first page id 580 | size -= Integer.SIZE / 8; 581 | 582 | return size / (Integer.SIZE / 8 + keySerializer.getSerializedLength()); 583 | } 584 | 585 | private AdjustmentAction handleUpdateKey(final KeyStruct ks, final AdjustmentAction result) { 586 | if (result.getAction() != ACTION.UPDATE_KEY) 587 | throw new IllegalArgumentException("action must be of type UPDATE_KEY"); 588 | 589 | 590 | // if we inserted this in the last leaf, then just push the result one level up 591 | if (ks == null) { 592 | return result; 593 | } 594 | 595 | // We need to adjust our own key, because keys were moved to the next node. 596 | // That changes the highest key in this page, so the corresponding key 597 | // must be adjusted. 598 | setKey(result.getSerializedKey(), ks.pos); 599 | 600 | rawPage.sync(); 601 | return null; 602 | } 603 | 604 | private void setKey(final byte[] serializedKey, final int pos) { 605 | final ByteBuffer buf = rawPage().bufferForWriting(new KeyStruct(pos).getOffset()); 606 | buf.put(serializedKey); 607 | rawPage().sync(); 608 | } 609 | 610 | private void ensureValid() { 611 | if (!isValid()) { 612 | throw new IllegalStateException("inner page with the id " + rawPage().id() + " not valid!"); 613 | } 614 | } 615 | 616 | /* (non-Javadoc) 617 | * @see Node#getKeyPointer(int) 618 | */ 619 | @Override 620 | public PagePointer getKeyPointer(final int pos) { 621 | ensureValid(); 622 | throw new UnsupportedOperationException(); 623 | } 624 | 625 | /* (non-Javadoc) 626 | * @see Node#getId() 627 | */ 628 | @Override 629 | public Integer getId() { 630 | return rawPage.id(); 631 | } 632 | 633 | /* (non-Javadoc) 634 | * @see MustInitializeOrLoad#initialize() 635 | */ 636 | @Override 637 | public void initialize() { 638 | 639 | if (rawPage().bufferForReading(0).limit() < minPageSize()) { 640 | throw new IllegalStateException("rawPage is too small. It must be at least " + minPageSize() + " bytes."); 641 | } 642 | 643 | final ByteBuffer buf = rawPage().bufferForWriting(Header.NODE_TYPE.getOffset()); 644 | buf.putChar(NODE_TYPE.serialize()); 645 | setNumberOfKeys(0); 646 | 647 | valid = true; 648 | 649 | rawPage.sync(); 650 | } 651 | 652 | /** 653 | * @param rawKeys 654 | * @param pageIds 655 | * @param fromId 656 | * @return 657 | */ 658 | public int bulkInitialize(final ArrayList rawKeys, 659 | final ArrayList pageIds, final int fromId) { 660 | 661 | if (pageIds.size() < (fromId + 2) || rawKeys.size() != (pageIds.size() - 1)) 662 | throw new IllegalArgumentException( 663 | "for bulkinsert, you must have at least 2 page ids and keys.size() == (pageIds.size() - 1)\n" + 664 | "pageIds.size()=" + pageIds.size() + ";fromId=" + fromId + ";rawKeys.size()=" + rawKeys.size()); 665 | 666 | final int fromId2 = fromId; 667 | 668 | initialize(); 669 | final ByteBuffer buf = rawPage().bufferForWriting(Header.size()); 670 | buf.putInt(pageIds.get(fromId2)); 671 | 672 | final int requiredSpace = Integer.SIZE / 8 + rawKeys.get(0).length; 673 | final int spaceForEntries = buf.remaining() / requiredSpace; 674 | final int totalEntriesToInsert = (pageIds.size() - fromId - 1); 675 | int entriesToInsert = spaceForEntries < totalEntriesToInsert ? spaceForEntries : totalEntriesToInsert; 676 | 677 | // make sure that not exactly one pageId remains, because that can't be inserted alone in the next 678 | // InnerNode. == 2 because 679 | final int remaining = pageIds.size() - fromId - (entriesToInsert + 1); 680 | if (remaining == 1) 681 | entriesToInsert--; 682 | 683 | for (int i = 0; i < entriesToInsert; i++) { 684 | // System.out.println("fetching rawKey " + (fromId + i) + " from array length " + rawKeys.size() + " with i=" + i); 685 | buf.put(rawKeys.get(fromId + i)); // fromId + 1 - 1 +i 686 | //LOG.debug("insert key: " + keySerializer.deserialize(rawKeys.get(fromId + i))); 687 | buf.putInt(pageIds.get(fromId + 1 + i)); 688 | } 689 | 690 | setNumberOfKeys(entriesToInsert); 691 | 692 | rawPage.sync(); 693 | 694 | return entriesToInsert + 1; // page ids 695 | } 696 | 697 | /* (non-Javadoc) 698 | * @see Node#getNumberOfKeys() 699 | */ 700 | @Override 701 | public int getNumberOfKeys() { 702 | return numberOfKeys; 703 | } 704 | 705 | /* (non-Javadoc) 706 | * @see Node#getFirstKey() 707 | */ 708 | @Override 709 | public K getFirstLeafKey() { 710 | final ByteBuffer buf = rawPage().bufferForReading(getOffsetForLeftPageIdOfKey(0)); 711 | return getPageForPageId(buf.getInt()).getFirstLeafKey(); 712 | } 713 | 714 | /* (non-Javadoc) 715 | * @see Node#getLastKey() 716 | */ 717 | @Override 718 | public K getLastLeafKey() { 719 | return new KeyStruct(getNumberOfKeys() - 1).getRightNode().getLastLeafKey(); 720 | } 721 | 722 | @Override public byte[] getLastLeafKeySerialized() { 723 | return new KeyStruct(getNumberOfKeys() - 1).getRightNode().getLastLeafKeySerialized(); 724 | } 725 | 726 | /* (non-Javadoc) 727 | * @see Node#getIterator(java.lang.Object, java.lang.Object) 728 | */ 729 | @Override 730 | public Iterator getIterator(final K from, final K to) { 731 | return new InnerNodeIterator(from, to); 732 | } 733 | 734 | @Override public int getDepth() { 735 | return new KeyStruct(0).getLeftNode().getDepth() + 1; 736 | } 737 | 738 | @Override public void checkStructure() throws IllegalStateException { 739 | final KeyStruct ks = new KeyStruct(0); 740 | 741 | // check structure of all nodes 742 | K lastKey = null; 743 | while (ks.pos < getNumberOfKeys()) { 744 | if (LOG.isDebugEnabled()) 745 | LOG.debug("checking structure of level: " + getDepth() + ", key: " + ks.pos); 746 | 747 | if (lastKey != null && comparator.compare(lastKey, ks.getKey()) > 0) { 748 | throw new IllegalStateException("last key(" + lastKey + 749 | ") should be smaller or equal current Key(" + ks.getKey() + ")"); 750 | } 751 | 752 | lastKey = ks.getKey(); 753 | 754 | ks.getLeftNode().checkStructure(); 755 | 756 | 757 | // compare on byte-level 758 | if (!Arrays.equals(ks.getSerializedKey(), ks.getRightNode().getFirstLeafKeySerialized())) 759 | throw new IllegalStateException("key(" + ks.getKey() + 760 | ") should equal rhs first leaf key(" + ks.getRightNode().getFirstLeafKey() + ")"); 761 | 762 | if (ks.getLeftNode() instanceof LeafNode) { 763 | if (!((LeafNode) ks.getLeftNode()).getNextLeafId().equals(ks.getRightNode().getId())) { 764 | throw new IllegalStateException( 765 | "in the first layer of innernodes, the nextLeafId of the lhs-node (" + 766 | ((LeafNode) ks.getLeftNode()).getNextLeafId() + 767 | ") should be the id of the rhs-node (" + ks.getRightNode().getId() + ")"); 768 | } 769 | } 770 | 771 | ks.becomeNext(); 772 | } 773 | 774 | ks.getLeftNode().checkStructure(); 775 | } 776 | 777 | /* (non-Javadoc) 778 | * @see com.rwhq.multimap.btree.Node#getFirst(java.lang.Object) 779 | */ 780 | @Override 781 | public V getFirst(final K key) { 782 | final List res = get(key); 783 | return res.size() > 0 ? res.get(0) : null; 784 | } 785 | 786 | private LeafNode getLeafNodeForKey(final K key) { 787 | final Integer pageId = getPageIdForKey(key); 788 | final Node node = pageIdToNode(pageId); 789 | if (node instanceof LeafNode) 790 | return (LeafNode) node; 791 | return ((InnerNode) node).getLeafNodeForKey(key); 792 | } 793 | 794 | 795 | /** 796 | * goes down to the LeafNode for the from key and creates an iterator for this leaf. 797 | * When the Iterator does not have any more values within the from-to range, it goes to the next leaf. 798 | * If the next leaf has no values, too, it sets the currentLeaf to null and returns null; 799 | */ 800 | public class InnerNodeIterator implements Iterator { 801 | private K from; 802 | private K to; 803 | private KeyStruct ks; 804 | private V next = null; 805 | private Iterator currentIterator; 806 | private LeafNode currentLeaf; 807 | 808 | 809 | public InnerNodeIterator(K from, K to) { 810 | if (from == null) 811 | from = getFirstLeafKey(); 812 | 813 | if (to == null) 814 | to = getLastLeafKey(); 815 | 816 | currentLeaf = getLeafNodeForKey(from); 817 | currentIterator = currentLeaf.getIterator(from, to); 818 | 819 | this.from = from; 820 | this.to = to; 821 | } 822 | 823 | @Override 824 | public boolean hasNext() { 825 | if (next == null) 826 | next = next(); 827 | 828 | return next != null; 829 | } 830 | 831 | @Override 832 | public V next() { 833 | final V result; 834 | 835 | if (next != null) { 836 | result = next; 837 | next = null; 838 | return result; 839 | } 840 | 841 | // end condition 842 | if (currentLeaf == null) 843 | return null; 844 | 845 | 846 | // return next if currentIterator and hasNext() 847 | if (currentIterator.hasNext()) 848 | return currentIterator.next(); 849 | 850 | // if there is next leaf, return 851 | if (!currentLeaf.hasNextLeaf()) 852 | return null; 853 | 854 | currentLeaf = leafPageManager.getPage(currentLeaf.getNextLeafId()); 855 | currentIterator = currentLeaf.getIterator(from, to); 856 | result = currentIterator.next(); 857 | 858 | if (result == null) { 859 | currentLeaf = null; 860 | currentIterator = null; 861 | } 862 | 863 | return result; 864 | } 865 | 866 | @Override public void remove() { 867 | throw new UnsupportedOperationException(); 868 | } 869 | } 870 | 871 | 872 | private Node pageIdToNode(final int id) { 873 | if (leafPageManager.hasPage(id)) { 874 | return leafPageManager.getPage(id); 875 | } else { 876 | return innerNodePageManager.getPage(id); 877 | } 878 | } 879 | 880 | } 881 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/InnerNodeManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree; 11 | 12 | import de.rwhq.io.rm.AbstractPageManager; 13 | import de.rwhq.io.rm.DataPageManager; 14 | import de.rwhq.io.rm.PageManager; 15 | import de.rwhq.io.rm.RawPage; 16 | import de.rwhq.serializer.FixLengthSerializer; 17 | 18 | import java.util.Comparator; 19 | 20 | class InnerNodeManager extends AbstractPageManager> { 21 | 22 | private final FixLengthSerializer keySerializer; 23 | 24 | private final DataPageManager keyPageManager; 25 | 26 | private final Comparator comparator; 27 | private final PageManager> leafPageManager; 28 | 29 | public InnerNodeManager( 30 | final PageManager bpm, 31 | final DataPageManager keyPageManager, 32 | final DataPageManager valuePageManager, 33 | final LeafPageManager leafPageManager, 34 | final FixLengthSerializer keySerializer, 35 | final Comparator comparator) { 36 | super(bpm); 37 | this.keySerializer = keySerializer; 38 | this.keyPageManager = keyPageManager; 39 | this.leafPageManager = leafPageManager; 40 | this.comparator = comparator; 41 | } 42 | 43 | /* (non-Javadoc) 44 | * @see AbstractPageManager#createObjectPage() 45 | */ 46 | @Override 47 | protected InnerNode createObjectPage(final RawPage page) { 48 | return new InnerNode(page, keySerializer, comparator, keyPageManager, leafPageManager, this); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/LeafNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree; 11 | 12 | import de.rwhq.btree.AdjustmentAction.ACTION; 13 | import de.rwhq.io.rm.*; 14 | import de.rwhq.serializer.FixLengthSerializer; 15 | import org.apache.commons.logging.Log; 16 | import org.apache.commons.logging.LogFactory; 17 | 18 | import java.io.IOException; 19 | import java.nio.ByteBuffer; 20 | import java.util.AbstractMap.SimpleEntry; 21 | import java.util.*; 22 | 23 | 24 | /** 25 | * autosaves 26 | * 27 | * @param 28 | * @param 29 | */ 30 | class LeafNode implements Node, ComplexPage { 31 | 32 | private static final Log LOG = LogFactory.getLog(LeafNode.class); 33 | 34 | static enum Header { 35 | NODE_TYPE(0), // char 36 | NUMBER_OF_KEYS(Character.SIZE / 8), // int 37 | NEXT_LEAF_ID((Character.SIZE + Integer.SIZE) / 8); // int 38 | 39 | 40 | private int offset; 41 | 42 | Header(final int offset) { 43 | this.offset = offset; 44 | } 45 | 46 | static int size() { 47 | return (Character.SIZE + 2 * Integer.SIZE) / 8; 48 | } // 6 49 | 50 | int getOffset() { 51 | return offset; 52 | } 53 | } 54 | 55 | /** 56 | * If a leaf page is less full than this factor, it may be target of operations where entries are moved from one page 57 | * to another. 58 | */ 59 | private static final float MAX_LEAF_ENTRY_FILL_LEVEL_TO_MOVE = 0.75f; 60 | 61 | private static final BTree.NodeType NODE_TYPE = BTree.NodeType.LEAF_NODE; 62 | 63 | private final FixLengthSerializer valueSerializer; 64 | 65 | private final Comparator comparator; 66 | 67 | private boolean valid = false; 68 | 69 | // right now, we always store key/value pairs. If the entries are not unique, 70 | // it could make sense to store the key once with references to all values 71 | //TODO: investigate if we should do this 72 | private int maxEntries; 73 | 74 | 75 | private static final int NOT_FOUND = -1; 76 | protected static final Integer NO_NEXT_LEAF = 0; 77 | 78 | private final RawPage rawPage; 79 | 80 | // counters 81 | private int numberOfEntries = 0; 82 | 83 | private final PageManager> leafPageManager; 84 | 85 | private final FixLengthSerializer keySerializer; 86 | 87 | 88 | LeafNode( 89 | final RawPage page, 90 | final FixLengthSerializer keySerializer, 91 | final FixLengthSerializer valueSerializer, 92 | final Comparator comparator, 93 | final PageManager> leafPageManager, 94 | final int minNumberOfValues 95 | ) { 96 | this.leafPageManager = leafPageManager; 97 | this.rawPage = page; 98 | this.keySerializer = keySerializer; 99 | this.valueSerializer = valueSerializer; 100 | this.comparator = comparator; 101 | 102 | // one pointer to key, one to value 103 | maxEntries = (rawPage.bufferForReading(0).limit() - Header.size()) / 104 | (valueSerializer.getSerializedLength() + keySerializer.getSerializedLength()); 105 | 106 | final int requiredBytes = Header.size() + minNumberOfValues * 107 | (keySerializer.getSerializedLength() + valueSerializer.getSerializedLength()); 108 | if (page.bufferForReading(0).limit() - requiredBytes < 0) 109 | throw new IllegalArgumentException("The RawPage must have space for at least " + 110 | minNumberOfValues + " Entries (" + requiredBytes + " bytes)"); 111 | } 112 | 113 | /** 114 | * If a leaf page has at least that many free slots left, we can move pointers to it from another node. This number is 115 | * computed from the MAX_LEAF_ENTRY_FILL_LEVEL_TO_MOVE constant. 116 | * @return 117 | */ 118 | private int getMinFreeLeafEntriesToMove() { 119 | return (int) (getMaximalNumberOfEntries() * 120 | (1 - MAX_LEAF_ENTRY_FILL_LEVEL_TO_MOVE)) + 2; 121 | } 122 | 123 | public boolean isFull() { 124 | return numberOfEntries == maxEntries; 125 | } 126 | 127 | 128 | private void ensureValid() { 129 | if (isValid()) 130 | return; 131 | 132 | System.err.println("The current LeafPage with the id " + rawPage.id() + " is not valid"); 133 | System.exit(1); 134 | } 135 | 136 | public String toString() { 137 | String str = "leafNode(id: " + getId() + ", values: " + getNumberOfEntries() + "): "; 138 | str += "firstKey: " + (getFirstLeafKey() == null ? "null" : getFirstLeafKey().toString()); 139 | str += ",lastKey: " + (getLastLeafKey() == null ? "null" : getLastLeafKey().toString()); 140 | return str; 141 | } 142 | 143 | public void prependEntriesFromOtherPage(final LeafNode source, final int num) { 144 | // checks 145 | if (num < 0) 146 | throw new IllegalArgumentException("num must be > 0"); 147 | 148 | if (num > source.getNumberOfEntries()) 149 | throw new IllegalArgumentException("the source leaf has not enough entries"); 150 | 151 | if (getNumberOfEntries() + num > maxEntries) 152 | throw new IllegalArgumentException( 153 | "not enough space in this leaf to prepend " + num + " entries from other leaf"); 154 | 155 | if (getNumberOfEntries() > 0 && comparator.compare(source.getLastLeafKey(), getFirstLeafKey()) > 0) { 156 | throw new IllegalStateException( 157 | "the last key of the provided source leaf is larger than this leafs first key"); 158 | } 159 | 160 | final ByteBuffer buffer = rawPage().bufferForWriting(0); 161 | 162 | // make space in this leaf, move all elements to the right 163 | final int totalSize = num * (keySerializer.getSerializedLength() + valueSerializer.getSerializedLength()); 164 | final int byteToMove = buffer.limit() - Header.size() - totalSize; 165 | System.arraycopy(buffer.array(), Header.size(), buffer.array(), Header.size() + totalSize, byteToMove); 166 | 167 | // copy from other to us 168 | final int sourceOffset = source.getOffsetForKeyPos(source.getNumberOfEntries() - num); 169 | System.arraycopy(source.rawPage().bufferForWriting(0).array(), sourceOffset, buffer.array(), Header.size(), 170 | totalSize); 171 | 172 | // update headers, also sets modified 173 | source.setNumberOfEntries(source.getNumberOfEntries() - num); 174 | setNumberOfEntries(getNumberOfEntries() + num); 175 | } 176 | 177 | private void setNumberOfEntries(final int num) { 178 | numberOfEntries = num; 179 | rawPage().bufferForWriting(Header.NUMBER_OF_KEYS.getOffset()).putInt(numberOfEntries); 180 | } 181 | 182 | public K getLastLeafKey() { 183 | if (getNumberOfEntries() == 0) 184 | return null; 185 | 186 | int offset = offsetBehindLastEntry(); 187 | offset -= keySerializer.getSerializedLength() + valueSerializer.getSerializedLength(); 188 | return getKeyAtOffset(offset); 189 | } 190 | 191 | @Override public byte[] getLastLeafKeySerialized() { 192 | final ByteBuffer buffer = rawPage().bufferForReading(getOffsetForKeyPos(getNumberOfEntries() - 1)); 193 | final byte[] buf = new byte[keySerializer.getSerializedLength()]; 194 | buffer.get(buf); 195 | return buf; 196 | } 197 | 198 | public K getKeyAtOffset(final int offset) { 199 | final byte[] bytes = new byte[keySerializer.getSerializedLength()]; 200 | rawPage().bufferForReading(offset).get(bytes); 201 | return keySerializer.deserialize(bytes); 202 | } 203 | 204 | public K getFirstLeafKey() { 205 | if (getNumberOfEntries() == 0) 206 | return null; 207 | 208 | final int pos = getOffsetForKeyPos(0); 209 | return getKeyAtOffset(pos); 210 | } 211 | 212 | 213 | /** 214 | * 215 | * adds an entry to this LeafNodes rawPage, does not sync! 216 | * 217 | * @param key 218 | * @param value 219 | */ 220 | private void addEntry(final K key, final V value) { 221 | 222 | final ByteBuffer buf = rawPage().bufferForWriting(0); 223 | int offset = offsetOfKey(key, true); 224 | 225 | if (offset == -1) { 226 | offset = offsetBehindLastEntry(); 227 | } else { 228 | // move everything including pos backwards 229 | System.arraycopy(buf.array(), offset, buf.array(), offset + keySerializer.getSerializedLength() 230 | + valueSerializer.getSerializedLength(), 231 | buf.capacity() - (offset + keySerializer.getSerializedLength() + valueSerializer.getSerializedLength())); 232 | } 233 | // insert both 234 | buf.position(offset); 235 | buf.put(keySerializer.serialize(key)); 236 | buf.put(valueSerializer.serialize(value)); 237 | 238 | setNumberOfEntries(getNumberOfEntries() + 1); 239 | } 240 | 241 | public int getNumberOfEntries() { 242 | return numberOfEntries; 243 | } 244 | 245 | /* (non-Javadoc) 246 | * @see MultiMap#containsKey(java.lang.Object) 247 | */ 248 | @Override 249 | public boolean containsKey(final K key) { 250 | return offsetOfKey(key) != NOT_FOUND; 251 | } 252 | 253 | /** 254 | * @param pos, 255 | * must be between 0 and numberOfEntries - 1 256 | * @return offset 257 | */ 258 | int getOffsetForKeyPos(final int pos) { 259 | if (pos < 0 || pos >= getNumberOfEntries()) 260 | throw new IllegalArgumentException( 261 | "invalid pos: " + pos + ". pos must be between 0 and numberOfEntries - 1"); 262 | 263 | return Header.size() + pos * (valueSerializer.getSerializedLength() + keySerializer.getSerializedLength()); 264 | } 265 | 266 | private int offsetForValuePos(final int i) { 267 | return getOffsetForKeyPos(i) + valueSerializer.getSerializedLength(); 268 | } 269 | 270 | /** 271 | * does not alter the header bytebuffer 272 | * 273 | * @return 274 | */ 275 | private int offsetBehindLastEntry() { 276 | return Header.size() + getNumberOfEntries() * (valueSerializer.getSerializedLength() + keySerializer.getSerializedLength()); 277 | } 278 | 279 | /* (non-Javadoc) 280 | * @see com.rwhq.multimap.MultiMap#get(java.lang.Object) 281 | */ 282 | @Override 283 | public List get(final K key) { 284 | final List result = new ArrayList(); 285 | 286 | 287 | final byte[] keyBuf = keySerializer.serialize(key); 288 | 289 | final byte[] tmpKeyBuf = new byte[keySerializer.getSerializedLength()]; 290 | final byte[] tmpValBuf = new byte[valueSerializer.getSerializedLength()]; 291 | 292 | final int pos = offsetOfKey(key); 293 | if (pos == NOT_FOUND) 294 | return result; 295 | 296 | 297 | final ByteBuffer buffer = rawPage().bufferForReading(pos); 298 | 299 | 300 | while (buffer.position() < offsetBehindLastEntry()) { 301 | buffer.get(tmpKeyBuf); 302 | if (Arrays.equals(tmpKeyBuf, keyBuf)) { 303 | buffer.get(tmpValBuf); 304 | result.add(valueSerializer.deserialize(tmpValBuf)); 305 | } 306 | } 307 | 308 | return result; 309 | } 310 | 311 | /** 312 | * @param key 313 | * @param takeNext 314 | * boolean, whether if the key was not found the next higher key should be taken 315 | * @return position to set the buffer to, where the key starts, -1 if key not found 316 | */ 317 | private int offsetOfKey(final K key, final boolean takeNext) { 318 | 319 | final byte[] pointerBuf = new byte[valueSerializer.getSerializedLength()]; 320 | final byte[] keyBuf = new byte[keySerializer.getSerializedLength()]; 321 | 322 | final ByteBuffer buffer = rawPage().bufferForReading(Header.size()); 323 | 324 | for (int i = 0; i < getNumberOfEntries(); i++) { 325 | 326 | buffer.get(keyBuf); 327 | 328 | final int compResult = comparator.compare(keySerializer.deserialize(keyBuf), key); 329 | 330 | if (compResult == 0) { 331 | return buffer.position() - keySerializer.getSerializedLength(); 332 | } else if (compResult > 0) { 333 | if (takeNext) 334 | return buffer.position() - keySerializer.getSerializedLength(); 335 | else 336 | return NOT_FOUND; 337 | } 338 | 339 | // if compresult < 0: 340 | // get the data pointer but do nothing with it 341 | buffer.get(pointerBuf); 342 | } 343 | return NOT_FOUND; 344 | } 345 | 346 | private int offsetOfKey(final K key) { 347 | return offsetOfKey(key, false); 348 | } 349 | 350 | 351 | /** 352 | * @param currentPos 353 | * @return a valid position to read the next key from, or NOT_FOUND 354 | */ 355 | private int getPosWhereNextKeyStarts(int currentPos) { 356 | if (currentPos < Header.size()) 357 | currentPos = Header.size(); 358 | 359 | currentPos -= Header.size(); 360 | currentPos /= (valueSerializer.getSerializedLength() + keySerializer.getSerializedLength()); 361 | if (currentPos >= getNumberOfEntries()) 362 | return NOT_FOUND; 363 | 364 | currentPos *= (valueSerializer.getSerializedLength() + keySerializer.getSerializedLength()); 365 | return currentPos + Header.size(); 366 | } 367 | 368 | /* (non-Javadoc) 369 | * @see MultiMap#remove(java.lang.Object) 370 | */ 371 | @Override 372 | public int remove(final K key) { 373 | 374 | final int pos = offsetOfKey(key); 375 | if (pos == NOT_FOUND) 376 | return 0; 377 | 378 | final int numberOfValues = get(key).size(); 379 | final int sizeOfValues = 380 | numberOfValues * (valueSerializer.getSerializedLength() + keySerializer.getSerializedLength()); 381 | 382 | //TODO: free key and value pages 383 | 384 | // shift the pointers after key 385 | final ByteBuffer buffer = rawPage().bufferForWriting(0); 386 | System.arraycopy(buffer.array(), pos + sizeOfValues, buffer.array(), pos, 387 | buffer.capacity() - pos - sizeOfValues); 388 | setNumberOfEntries(getNumberOfEntries() - numberOfValues); 389 | 390 | rawPage().sync(); 391 | 392 | return numberOfValues; 393 | } 394 | 395 | /* (non-Javadoc) 396 | * @see Node#remove(java.lang.Object, java.lang.Object) 397 | */ 398 | @Override 399 | public int remove(final K key, final V value) { 400 | final int offset = offsetOfKey(key); 401 | if (offset == NOT_FOUND) 402 | return 0; 403 | 404 | 405 | final int numberOfValues = get(key).size(); 406 | 407 | final ByteBuffer buffer = rawPage().bufferForWriting(offset); 408 | final byte[] buf1 = new byte[keySerializer.getSerializedLength()]; 409 | final byte[] buf2 = new byte[valueSerializer.getSerializedLength()]; 410 | int removed = 0; 411 | 412 | for (int i = 0; i < numberOfValues; i++) { 413 | buffer.get(buf1); 414 | buffer.get(buf2); // load only the value 415 | final V val = valueSerializer.deserialize(buf2); 416 | 417 | if (val == null) 418 | throw new IllegalStateException("value retrieved from a value page should not be null"); 419 | 420 | // we cant use a comparator here since we have none for values (its the only case we need it) 421 | if (val.equals(value)) { 422 | // also free key page 423 | // move pointers forward and reset buffer 424 | final int startingPos = buffer.position() - buf1.length - buf2.length; 425 | System.arraycopy(buffer.array(), buffer.position(), buffer.array(), startingPos, 426 | buffer.capacity() - buffer.position()); 427 | 428 | buffer.position(startingPos); 429 | 430 | removed++; 431 | } 432 | } 433 | 434 | setNumberOfEntries(getNumberOfEntries() - removed); 435 | return removed; 436 | } 437 | 438 | /** 439 | * @param key 440 | * @return 441 | */ 442 | private DataPage getValueDataPage(final K key) { 443 | // TODO Auto-generated method stub 444 | return null; 445 | } 446 | 447 | 448 | /** 449 | * @param key 450 | * @return 451 | */ 452 | private DataPage getKeyDataPage(final K key) { 453 | // TODO Auto-generated method stub 454 | return null; 455 | } 456 | 457 | 458 | /* (non-Javadoc) 459 | * @see Node#destroy() 460 | */ 461 | @Override 462 | public void destroy() { 463 | leafPageManager.removePage(rawPage().id()); 464 | } 465 | 466 | /* (non-Javadoc) 467 | * @see ComplexPage#initialize() 468 | */ 469 | @Override 470 | public void initialize() { 471 | rawPage.bufferForWriting(Header.NODE_TYPE.getOffset()).putChar(NODE_TYPE.serialize()); 472 | setNumberOfEntries(0); 473 | setNextLeafId(NO_NEXT_LEAF); 474 | valid = true; 475 | 476 | rawPage.sync(); 477 | } 478 | 479 | 480 | /** @param kvs 481 | * @see #bulkInitialize(java.util.AbstractMap.SimpleEntry[], int, int) with from = 0 482 | * @return*/ 483 | public int bulkInitialize(final SimpleEntry[] kvs) { 484 | return bulkInitialize(kvs, 0, kvs.length - 1); 485 | } 486 | 487 | 488 | /** 489 | * Initializes the Leaf with data 490 | * 491 | * @param kvs 492 | * data to insert as KeyValueObj Array 493 | * @param from 494 | * from where in the array to start inserting 495 | * @param maxTo 496 | * usually lvs.length - 1 497 | * @return number of keys inserted 498 | */ 499 | public int bulkInitialize(final SimpleEntry[] kvs, final int from, final int maxTo) { 500 | initialize(); 501 | 502 | final int remainingToInsert = maxTo - from + 1; 503 | if (remainingToInsert <= 0) 504 | return 0; 505 | 506 | final ByteBuffer buf = rawPage().bufferForWriting(Header.size()); 507 | 508 | final int entrySize = keySerializer.getSerializedLength() + valueSerializer.getSerializedLength(); 509 | final int entriesThatFit = buf.remaining() / entrySize; 510 | final int entriesToInsert = entriesThatFit > remainingToInsert ? remainingToInsert : entriesThatFit; 511 | 512 | // determine value type 513 | boolean isSerialized = (kvs[from].getValue() instanceof byte[]); 514 | 515 | 516 | 517 | if(!isSerialized){ 518 | for (int i = 0; i < entriesToInsert; i++) { 519 | buf.put(keySerializer.serialize(kvs[from + i].getKey())); 520 | buf.put(valueSerializer.serialize((V) kvs[from + i].getValue())); 521 | } 522 | } else { 523 | for (int i = 0; i < entriesToInsert; i++) { 524 | buf.put(keySerializer.serialize(kvs[from + i].getKey())); 525 | buf.put((byte[]) kvs[from + i].getValue()); 526 | } 527 | } 528 | 529 | setNumberOfEntries(entriesToInsert); 530 | 531 | rawPage.sync(); 532 | return entriesToInsert; 533 | } 534 | 535 | 536 | /* (non-Javadoc) 537 | * @see ComplexPage#load() 538 | */ 539 | @Override 540 | public void load() { 541 | final ByteBuffer buf = rawPage().bufferForReading(0); 542 | if (buf.getChar() != NODE_TYPE.serialize()) 543 | throw new IllegalStateException("The RawPage " + rawPage.id() + " doesnt have the Leaf Node Type"); 544 | 545 | numberOfEntries = rawPage().bufferForReading(Header.NUMBER_OF_KEYS.getOffset()).getInt(); 546 | valid = true; 547 | } 548 | 549 | 550 | /* (non-Javadoc) 551 | * @see ComplexPage#isValid() 552 | */ 553 | @Override 554 | public boolean isValid() { 555 | return valid; 556 | } 557 | 558 | @Override 559 | public void loadOrInitialize() throws IOException { 560 | try { 561 | load(); 562 | } catch (Exception e) { 563 | initialize(); 564 | } 565 | } 566 | 567 | 568 | /* (non-Javadoc) 569 | * @see ComplexPage#rawPage() 570 | */ 571 | @Override 572 | public RawPage rawPage() { 573 | return rawPage; 574 | } 575 | 576 | /* (non-Javadoc) 577 | * @see com.rwhq.btree.Node#insert(java.lang.Object, java.lang.Object) 578 | */ 579 | @Override 580 | public AdjustmentAction insert(final K key, final V value) { 581 | ensureValid(); 582 | 583 | if (!isFull()) { 584 | // serialize data 585 | addEntry(key, value); 586 | rawPage().sync(); 587 | return null; 588 | } 589 | 590 | // if leaf does not have enough space but we can move some data to the next leaf 591 | if (hasNextLeaf()) { 592 | final LeafNode nextLeaf = leafPageManager.getPage(this.getNextLeafId()); 593 | 594 | if (nextLeaf.getRemainingEntries() >= getMinFreeLeafEntriesToMove()) { 595 | nextLeaf.prependEntriesFromOtherPage(this, nextLeaf.getRemainingEntries() >> 1); 596 | 597 | // see on which page we will insert the value 598 | if (comparator.compare(key, this.getLastLeafKey()) > 0) { 599 | nextLeaf.insert(key, value); 600 | } else { 601 | this.insert(key, value); 602 | } 603 | 604 | rawPage.sync(); 605 | nextLeaf.rawPage.sync(); 606 | 607 | return new AdjustmentAction(ACTION.UPDATE_KEY, nextLeaf.getFirstLeafKeySerialized(), null); 608 | } 609 | 610 | 611 | } 612 | 613 | // allocate new leaf 614 | final LeafNode newLeaf = leafPageManager.createPage(); 615 | newLeaf.setNextLeafId(getNextLeafId()); 616 | setNextLeafId(newLeaf.getId()); 617 | 618 | // newLeaf.setLastKeyContinuesOnNextPage(root.isLastKeyContinuingOnNextPage()); 619 | 620 | // move half of the keys to new page 621 | newLeaf.prependEntriesFromOtherPage(this, 622 | this.getNumberOfEntries() >> 1); 623 | 624 | // see on which page we will insert the value 625 | if (comparator.compare(key, this.getLastLeafKey()) > 0) { 626 | newLeaf.insert(key, value); 627 | } else { 628 | this.insert(key, value); 629 | } 630 | 631 | rawPage.sync(); 632 | newLeaf.rawPage.sync(); 633 | 634 | // just to make sure, that the adjustment action is correct: 635 | final AdjustmentAction action = new AdjustmentAction(ACTION.INSERT_NEW_NODE, 636 | newLeaf.getFirstLeafKeySerialized(), newLeaf.rawPage().id()); 637 | 638 | return action; 639 | } 640 | 641 | 642 | /** @return id of the next leaf or null */ 643 | public Integer getNextLeafId() { 644 | final ByteBuffer buffer = rawPage().bufferForReading(Header.NEXT_LEAF_ID.getOffset()); 645 | final Integer result = buffer.getInt(); 646 | return result == 0 ? null : result; 647 | } 648 | 649 | public void setNextLeafId(final Integer id) { 650 | final ByteBuffer buffer = rawPage().bufferForWriting(Header.NEXT_LEAF_ID.getOffset()); 651 | buffer.putInt(id == null ? NO_NEXT_LEAF : id); 652 | rawPage().sync(); 653 | } 654 | 655 | public boolean hasNextLeaf() { 656 | return getNextLeafId() != null; 657 | } 658 | 659 | /* (non-Javadoc) 660 | * @see com.rwhq.btree.Node#getKeyPointer(int) 661 | */ 662 | @Override 663 | public PagePointer getKeyPointer(final int pos) { 664 | 665 | if (pos >= 0) { 666 | getOffsetForKeyPos(pos); 667 | } 668 | 669 | return null; 670 | } 671 | 672 | /* (non-Javadoc) 673 | * @see com.rwhq.btree.Node#getId() 674 | */ 675 | @Override 676 | public Integer getId() { 677 | return rawPage.id(); 678 | } 679 | 680 | public int getRemainingEntries() { 681 | return getMaximalNumberOfEntries() - getNumberOfEntries(); 682 | } 683 | 684 | /** @return the maximal number of Entries */ 685 | public int getMaximalNumberOfEntries() { 686 | return maxEntries; 687 | } 688 | 689 | /* (non-Javadoc) 690 | * @see Node#getNumberOfUniqueKeys() 691 | */ 692 | @Override 693 | public int getNumberOfKeys() { 694 | throw new UnsupportedOperationException(); 695 | } 696 | 697 | /** 698 | * @param pos, 699 | * starting with 0, going to numberOfEntries - 1 700 | * @return 701 | */ 702 | public K getKeyAtPosition(final int pos) { 703 | return getKeyAtOffset(getOffsetForKeyPos(pos)); 704 | } 705 | 706 | public List getKeySet() { 707 | final List result = new ArrayList(); 708 | for (int i = 0; i < getNumberOfEntries(); i++) { 709 | result.add(getKeyAtPosition(i)); 710 | } 711 | return result; 712 | } 713 | 714 | class KeyStruct { 715 | private int position = 0; 716 | 717 | private KeyStruct() { 718 | } 719 | 720 | private KeyStruct(final int position) { 721 | this.position = position; 722 | } 723 | 724 | private K getKey() { 725 | final byte[] bytes = new byte[keySerializer.getSerializedLength()]; 726 | rawPage.bufferForReading(getOffset()).get(bytes); 727 | return keySerializer.deserialize(bytes); 728 | } 729 | 730 | private int getOffset() { 731 | return Header.size() + position * (keySerializer.getSerializedLength() + valueSerializer.getSerializedLength()); 732 | } 733 | 734 | /** @return true if next Key exists */ 735 | public boolean becomeNext() { 736 | position++; 737 | return position < getNumberOfEntries(); 738 | } 739 | 740 | private int getValueOffset() { 741 | return getOffset() + keySerializer.getSerializedLength(); 742 | } 743 | 744 | public V getValue() { 745 | final byte[] bytes = new byte[valueSerializer.getSerializedLength()]; 746 | rawPage().bufferForReading(getValueOffset()).get(bytes); 747 | return valueSerializer.deserialize(bytes); 748 | } 749 | } 750 | 751 | 752 | public class LeafNodeIterator implements Iterator { 753 | 754 | private final K from; 755 | private final K to; 756 | private V next; 757 | private KeyStruct currentKeyStruct; 758 | 759 | public LeafNodeIterator(final K from, final K to) { 760 | this.from = from; 761 | this.to = to; 762 | 763 | if(from != null) 764 | currentKeyStruct = firstKeyStructEqualOrLargerThan(from); 765 | else 766 | currentKeyStruct = new KeyStruct(); 767 | 768 | if(currentKeyStruct == null) 769 | LOG.warn("iterator requested from a key that is larger than this leafs largest key"); 770 | } 771 | 772 | /* (non-Javadoc) 773 | * @see java.util.Iterator#hasNext() 774 | */ 775 | @Override 776 | public boolean hasNext() { 777 | if (next != null) 778 | return true; 779 | 780 | next = next(); 781 | return next != null; 782 | } 783 | 784 | /* (non-Javadoc) 785 | * @see java.util.Iterator#next() 786 | */ 787 | @Override 788 | public V next() { 789 | 790 | if (next != null) { 791 | final V result = next; 792 | next = null; 793 | return result; 794 | } 795 | 796 | // from key was larger than this leaf 797 | if(currentKeyStruct == null) 798 | return null; 799 | 800 | // if we are at the end of the leaf, or the current key is larger than the to we were looking for 801 | if(currentKeyStruct.position >= getNumberOfEntries() || (to != null && comparator.compare(currentKeyStruct.getKey(), to) > 0)){ 802 | return null; 803 | } 804 | 805 | final V result = currentKeyStruct.getValue(); 806 | currentKeyStruct.becomeNext(); 807 | return result; 808 | } 809 | 810 | @Override public void remove() { 811 | throw new UnsupportedOperationException(); 812 | } 813 | 814 | } 815 | 816 | private KeyStruct firstKeyStructEqualOrLargerThan(final K from) { 817 | final KeyStruct current = new KeyStruct(0); 818 | do { 819 | if (comparator.compare(current.getKey(), from) >= 0) { 820 | return current; 821 | } 822 | } while (current.becomeNext()); 823 | 824 | return null; 825 | } 826 | 827 | 828 | /* (non-Javadoc) 829 | * @see com.rwhq.multimap.btree.Node#getIterator(java.lang.Object, java.lang.Object) 830 | */ 831 | @Override 832 | public Iterator getIterator(final K from, final K to) { 833 | return new LeafNodeIterator(from, to); 834 | } 835 | 836 | @Override public int getDepth() { 837 | return 1; 838 | } 839 | 840 | @Override public void checkStructure() throws IllegalStateException { 841 | K lastKey = null; 842 | for (int i = 0; i < getNumberOfEntries(); i++) { 843 | if (lastKey != null && comparator.compare(lastKey, getKeyAtPosition(i)) > 0) 844 | throw new IllegalStateException("lastKey should be smaller or equal to current key"); 845 | lastKey = getKeyAtPosition(i); 846 | } 847 | } 848 | 849 | /* (non-Javadoc) 850 | * @see com.rwhq.btree.Node#getFirst(java.lang.Object) 851 | */ 852 | @Override 853 | public V getFirst 854 | (final K 855 | key) { 856 | final List res = get(key); 857 | return res.size() > 0 ? res.get(0) : null; 858 | } 859 | 860 | @Override public byte[] getFirstLeafKeySerialized 861 | () { 862 | if (getNumberOfEntries() == 0) 863 | throw new IllegalStateException("you must have keys to get the first serialized key"); 864 | 865 | final byte[] result = new byte[keySerializer.getSerializedLength()]; 866 | rawPage().bufferForReading(Header.size()).get(result); 867 | return result; 868 | } 869 | } 870 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/LeafPageManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import de.rwhq.io.rm.AbstractPageManager; 14 | import de.rwhq.io.rm.PageManager; 15 | import de.rwhq.io.rm.RawPage; 16 | import de.rwhq.serializer.FixLengthSerializer; 17 | 18 | import java.util.Comparator; 19 | 20 | class LeafPageManager extends AbstractPageManager> { 21 | 22 | private final FixLengthSerializer valueSerializer; 23 | private final FixLengthSerializer keySerializer; 24 | 25 | private final Comparator comparator; 26 | 27 | public LeafPageManager( 28 | final PageManager bpm, 29 | final FixLengthSerializer valueSerializer, 30 | final FixLengthSerializer keySerializer, 31 | final Comparator comparator) { 32 | super(bpm); 33 | this.valueSerializer = valueSerializer; 34 | this.keySerializer = keySerializer; 35 | this.comparator = comparator; 36 | } 37 | 38 | /* (non-Javadoc) 39 | * @see AbstractPageManager#createObjectPage() 40 | */ 41 | @Override 42 | protected LeafNode createObjectPage(final RawPage page) { 43 | return new LeafNode(page, keySerializer, valueSerializer, comparator, this, 1); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/MultiMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import java.io.IOException; 14 | import java.util.Iterator; 15 | import java.util.List; 16 | 17 | public interface MultiMap { 18 | 19 | /** 20 | * @return number of values 21 | */ 22 | public int getNumberOfEntries(); 23 | 24 | /** 25 | * @param key 26 | * @return boolean if the key is contained in the map 27 | */ 28 | public boolean containsKey(K key); 29 | 30 | /** 31 | * @param key 32 | * @return array of values associated with the key or an empty array if the key does not exist 33 | */ 34 | public List get(K key); 35 | 36 | 37 | 38 | // Modification Operations 39 | 40 | /** 41 | * Adds the specified value to the specified key. 42 | * 43 | * @param key 44 | * @param value 45 | * 46 | */ 47 | public void add(K key, V value); 48 | 49 | /** 50 | * Removes the key with all its associated values from the map. 51 | * If the key was not found, an empty array is returned. 52 | * 53 | * @param key 54 | */ 55 | void remove(K key); 56 | 57 | /** 58 | * Removes the value under key. 59 | * IF the key or value was not found, null is returned. 60 | * 61 | * @param key 62 | * @param value 63 | */ 64 | void remove(K key, V value); 65 | 66 | /** 67 | * removes all keys and values 68 | */ 69 | void clear() throws IOException; 70 | 71 | /** 72 | * @return iterator over all values 73 | */ 74 | public Iterator getIterator(); 75 | 76 | /** 77 | * returns an iterator over the values for the keys of the given range 78 | * 79 | * @param from 80 | * @param to 81 | * @return 82 | */ 83 | public Iterator getIterator(K from, K to); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/Node.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import de.rwhq.io.rm.PagePointer; 14 | 15 | import java.util.Iterator; 16 | import java.util.List; 17 | 18 | interface Node { 19 | 20 | /** 21 | * inserts the key and value into the node 22 | * 23 | * @param key 24 | * @param value 25 | * @return 26 | */ 27 | public AdjustmentAction insert(K key, V value); 28 | 29 | 30 | /** 31 | * @param pos of the key, can also be e.g. -1, which returns the last key 32 | * @return 33 | */ 34 | public PagePointer getKeyPointer(int pos); 35 | 36 | 37 | /** 38 | * @return id of this node 39 | */ 40 | public Integer getId(); 41 | 42 | /** 43 | * @return number of keys in a node 44 | */ 45 | public int getNumberOfKeys(); 46 | 47 | /** 48 | * @param key 49 | * @return boolean if the key is contained in the map 50 | */ 51 | public boolean containsKey(K key); 52 | 53 | /** 54 | * @param key 55 | * @return array of values associated with the key or an empty array if the key does not exist 56 | */ 57 | public List get(K key); 58 | 59 | /** 60 | * @param key 61 | * @return first element of get(key) 62 | */ 63 | public V getFirst(K key); 64 | 65 | // Modification Operations 66 | 67 | /** 68 | * Removes the key with all its associated values from the map. 69 | * If the key was not found, an empty array is returned. 70 | * 71 | * @param key 72 | * @return number of removed values 73 | */ 74 | int remove(K key); 75 | 76 | /** 77 | * Removes the value under key. 78 | * IF the key or value was not found, null is returned. 79 | * 80 | * Note: This method might use value.equals to determine the values to remove. Make sure this method works correctly. 81 | * 82 | * @param key 83 | * @param value 84 | * @return number of removed values 85 | */ 86 | int remove(K key, V value); 87 | 88 | /** 89 | * removes all key and values, destroying all rawPages with the keyPages, valuePages, leafPages and innerNodePages 90 | */ 91 | void destroy(); 92 | 93 | 94 | /** 95 | * @return first key of first leaf 96 | */ 97 | public K getFirstLeafKey(); 98 | 99 | public byte[] getFirstLeafKeySerialized(); 100 | 101 | /** 102 | * @return last key of last leaf 103 | */ 104 | public K getLastLeafKey(); 105 | 106 | public byte[] getLastLeafKeySerialized(); 107 | 108 | public Iterator getIterator(K from, K to); 109 | 110 | /** 111 | * @return 1 if the node is a leaf, otherwise the depth of the innernode 112 | */ 113 | public int getDepth(); 114 | 115 | /** 116 | * @return true if all sub-nodes are in the right order and are valid 117 | * @throws IllegalStateException 118 | */ 119 | public void checkStructure() throws IllegalStateException; 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/Range.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import com.google.common.base.Objects; 14 | import com.google.common.collect.Lists; 15 | import com.google.common.collect.Sets; 16 | 17 | import java.util.*; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | /** 22 | * Very generic Range object for getting values form the BTree 23 | * 24 | * @param 25 | */ 26 | public class Range { 27 | public static Comparator> createRangeComparator(final Comparator comparator){ 28 | return new Comparator>() { 29 | private int compareWithNull(K k1, K k2, boolean nullIsSmallest) { 30 | if (k1 == null) { 31 | if (k2 == null) { 32 | return 0; 33 | } else { 34 | return nullIsSmallest ? -1 : 1; 35 | } 36 | } else if (k2 == null) { 37 | return nullIsSmallest ? 1 : -1; 38 | } 39 | 40 | return comparator.compare(k1, k2); 41 | } 42 | 43 | @Override 44 | public int compare(final Range r1, final Range r2) { 45 | int compareResult = compareWithNull(r1.getFrom(), r2.getFrom(), true); 46 | 47 | if (compareResult != 0) 48 | return compareResult; 49 | 50 | return compareWithNull(r1.getTo(), r2.getTo(), false); 51 | } 52 | }; 53 | } 54 | 55 | /** 56 | * merges the given Collection of Ranges by using the provided comparator. 57 | * 58 | * @param ranges 59 | * @param comparator 60 | * @param 61 | * @return 62 | */ 63 | public static TreeSet> merge(final Collection> ranges, final Comparator comparator) { 64 | checkNotNull(ranges, "range list must not be null"); 65 | checkNotNull(comparator, "comparator must not be null"); 66 | 67 | TreeSet> tmpSet = Sets.newTreeSet(createRangeComparator(comparator)); 68 | 69 | tmpSet.addAll(ranges); 70 | Range last = null; 71 | 72 | Iterator> iterator = tmpSet.iterator(); 73 | while (iterator.hasNext()) { 74 | Range r = iterator.next(); 75 | 76 | if (last == null) { 77 | last = r; 78 | continue; 79 | } 80 | 81 | // only if this to() is larger than last to(), extend to() 82 | if (last.getTo() != null) { 83 | if ((r.getFrom() == null || comparator.compare(last.getTo(), r.getFrom()) >= 0)) { 84 | if (r.getTo() == null) 85 | last.setTo(null); 86 | else if (comparator.compare(last.getTo(), r.getTo()) < 0) { 87 | last.setTo(r.getTo()); 88 | } 89 | 90 | iterator.remove(); 91 | } else { // separate ranges 92 | last = r; 93 | } 94 | } else { 95 | iterator.remove(); 96 | } 97 | } 98 | 99 | return tmpSet; 100 | } 101 | 102 | private T from; 103 | private T to; 104 | 105 | private Comparator comparator; 106 | 107 | public Comparator getComparator() { 108 | return comparator; 109 | } 110 | 111 | public void setComparator(Comparator comparator) { 112 | this.comparator = comparator; 113 | } 114 | 115 | public Range() { 116 | } 117 | 118 | public Range(final T from, final T to) { 119 | this(from, to, null); 120 | } 121 | 122 | public Range(final T from, final T to, final Comparator comparator) { 123 | this.from = from; 124 | this.to = to; 125 | this.comparator = comparator; 126 | } 127 | 128 | public boolean contains(T obj) { 129 | return contains(obj, comparator); 130 | } 131 | 132 | public boolean contains(T obj, Comparator comparator) { 133 | checkNotNull(obj, "can't check contains on null. Check from/to directly."); 134 | checkNotNull(comparator, "comparator must not be null for contains() to work"); 135 | 136 | return (from == null || comparator.compare(from, obj) <= 0) && 137 | (to == null || comparator.compare(obj, to) <= 0); 138 | } 139 | 140 | public T getTo() { 141 | return to; 142 | } 143 | 144 | public void setTo(final T to) { 145 | this.to = to; 146 | } 147 | 148 | public T getFrom() { 149 | return from; 150 | } 151 | 152 | public void setFrom(final T from) { 153 | this.from = from; 154 | } 155 | 156 | public String toString() { 157 | return Objects.toStringHelper(this) 158 | .add("from", from) 159 | .add("to", to) 160 | .toString(); 161 | } 162 | 163 | @Override 164 | public int hashCode() { 165 | return Objects.hashCode(from, to); 166 | } 167 | 168 | @Override 169 | public boolean equals(final Object obj) { 170 | if (obj instanceof Range) { 171 | final Range other = (Range) obj; 172 | return Objects.equal(from, other.from) && 173 | Objects.equal(to, other.to); 174 | } else { 175 | return false; 176 | } 177 | } 178 | } 179 | 180 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/run/PrintTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree.run; 11 | 12 | import de.rwhq.btree.BTree; 13 | import de.rwhq.comparator.IntegerComparator; 14 | import de.rwhq.io.rm.ResourceManager; 15 | import de.rwhq.io.rm.ResourceManagerBuilder; 16 | import de.rwhq.serializer.FixedStringSerializer; 17 | import de.rwhq.serializer.IntegerSerializer; 18 | import org.apache.commons.logging.Log; 19 | import org.apache.commons.logging.LogFactory; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.util.Iterator; 24 | 25 | /** 26 | * Iterates over the values of a tree 27 | */ 28 | public class PrintTree { 29 | 30 | private static Log LOG = LogFactory.getLog(PrintTree.class); 31 | 32 | public static void main(final String[] args) throws IOException { 33 | 34 | 35 | final File f = new File("/tmp/indexha"); 36 | if(!f.exists()) 37 | throw new IllegalArgumentException("File does not exist"); 38 | 39 | final ResourceManager resourceManager = new ResourceManagerBuilder().file(f).build(); 40 | final BTree tree = BTree.create(resourceManager, IntegerSerializer.INSTANCE, 41 | FixedStringSerializer.INSTANCE_1000, 42 | IntegerComparator.INSTANCE); 43 | 44 | final Iterator it = tree.getIterator(); 45 | while(it.hasNext()){ 46 | System.out.println(it.next()); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/run/commons-logging.properties: -------------------------------------------------------------------------------- 1 | # 2 | # This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | # 4 | # http://creativecommons.org/licenses/by-nc/3.0/ 5 | # 6 | # For alternative conditions contact the author. 7 | # 8 | # Copyright (c) 2011 "Robin Wenglewski " 9 | # 10 | 11 | org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger 12 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/btree/run/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/comparator/IntegerComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.comparator; 11 | 12 | import java.util.Comparator; 13 | 14 | public enum IntegerComparator implements Comparator { 15 | INSTANCE; 16 | 17 | /* (non-Javadoc) 18 | * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 19 | */ 20 | @Override 21 | public int compare(final Integer o1, final Integer o2) { 22 | return o1.compareTo(o2); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/comparator/LongComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.comparator; 12 | 13 | import java.util.Comparator; 14 | 15 | public enum LongComparator implements Comparator { 16 | INSTANCE; 17 | 18 | @Override 19 | public int compare(Long l1, Long l2) { 20 | return l1.compareTo(l2); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/comparator/StringComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.comparator; 11 | 12 | import java.util.Comparator; 13 | 14 | public enum StringComparator implements Comparator { 15 | INSTANCE; 16 | 17 | /* (non-Javadoc) 18 | * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 19 | */ 20 | @Override 21 | public int compare(final String o1, final String o2) { 22 | return o1.compareTo(o2); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/AbstractMustInitializeOrLoad.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * wraps the MustInitializeOrLoad interface to implement the pretty generic method loadOrInitialize(). 17 | */ 18 | public abstract class AbstractMustInitializeOrLoad implements MustInitializeOrLoad{ 19 | 20 | @Override 21 | public void loadOrInitialize() throws IOException { 22 | try{ 23 | load(); 24 | } catch (IOException e){ 25 | initialize(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/MustBeOpened.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io; 11 | 12 | public interface MustBeOpened { 13 | 14 | /** 15 | * opens a closed Object. 16 | * @throws Exception 17 | */ 18 | public void open() throws Exception; 19 | 20 | /** 21 | * @return true if the object is open, false if the object is closed 22 | */ 23 | public boolean isOpen(); 24 | 25 | /** 26 | * Closes the Object or throws an Exception (and leaves the resource open). The Exception is thrown to give the programmer the chance to save the data in 27 | * the object or to make sure that the Resource is closable. For instance, if an Array is synchronized with a File, close might 28 | * throw an Exception if the File is not accessible. The programmer can then either make the file accessible or copy the values 29 | * of the array to another place. 30 | * 31 | * @throws Exception 32 | */ 33 | public void close() throws Exception; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/MustInitializeOrLoad.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io; 11 | 12 | import java.io.IOException; 13 | 14 | 15 | public interface MustInitializeOrLoad { 16 | 17 | /** 18 | * 19 | * Since this is an extremely generic interface, 20 | * some implementations might throw an IOException, some might not. 21 | * 22 | * Just be be sure, the interface specifies the Exception. 23 | * In the documentation of you implementation it can be specified that this 24 | * Exception is never thrown. 25 | * 26 | * @throws java.io.IOException 27 | */ 28 | public void initialize() throws IOException; 29 | 30 | /** 31 | * @throws IOException 32 | */ 33 | public void load() throws IOException; 34 | 35 | /** 36 | * @return if the object has been initialized or loaded 37 | */ 38 | public boolean isValid(); 39 | 40 | /** 41 | * Loads the object if it can be loaded, otherwise initializes it 42 | * 43 | * @throws IOException 44 | */ 45 | public void loadOrInitialize() throws IOException; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/NoSpaceException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io; 12 | 13 | public class NoSpaceException extends RuntimeException { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | NoSpaceException(){ 18 | super("Not enough space!"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/AbstractPageManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import org.apache.commons.logging.Log; 13 | import org.apache.commons.logging.LogFactory; 14 | 15 | import java.io.IOException; 16 | 17 | public abstract class AbstractPageManager implements PageManager { 18 | 19 | private static final Log LOG = LogFactory.getLog(AbstractPageManager.class); 20 | private final PageManager rpm; 21 | 22 | protected AbstractPageManager(final PageManager rpm) { 23 | this.rpm = rpm; 24 | } 25 | 26 | protected PageManager getRawPageManager() { 27 | return rpm; 28 | } 29 | 30 | public boolean hasRawPageManager(final PageManager rpm) { 31 | return this.rpm.equals(rpm); 32 | } 33 | 34 | /* (non-Javadoc) 35 | * @see PageManager#getPage(int) 36 | */ 37 | @Override 38 | public T getPage(final int id) { 39 | final T result; 40 | 41 | 42 | final RawPage page = rpm.getPage(id); 43 | result = createObjectPage(page); 44 | 45 | try { 46 | result.load(); 47 | } catch (IOException e) { 48 | // if the page cannot be loaded, something is off. 49 | // we should only be able to fetch initialized pages from the rpm. 50 | throw new IllegalArgumentException("cant load InnerNodePage with id " + id); 51 | } 52 | 53 | return result; 54 | } 55 | 56 | /* (non-Javadoc) 57 | * @see PageManager#createPage() 58 | */ 59 | @Override 60 | public T createPage() { 61 | return createPage(true); 62 | } 63 | 64 | public T createPage(final boolean initialize) { 65 | final T l = createObjectPage(rpm.createPage()); 66 | 67 | if (initialize) 68 | try { 69 | l.initialize(); 70 | } catch (IOException e) { 71 | throw new RuntimeException(e); 72 | } 73 | 74 | if (LOG.isDebugEnabled()) 75 | LOG.debug("node created: type: \t" + l.getClass().getSimpleName().toString() + "\tid: " + l.rawPage().id()); 76 | 77 | return l; 78 | } 79 | 80 | 81 | /* (non-Javadoc) 82 | * @see PageManager#removePage(int) 83 | */ 84 | @Override 85 | public void removePage(final int id) { 86 | rpm.removePage(id); 87 | } 88 | 89 | /** 90 | * This method is a utility method since the dependencies for the concrete page creation are only available in the 91 | * extensions of this AbstractPageManager 92 | * 93 | * @param page 94 | * which should be initialized with the page-specific data 95 | * @return a Complex Page 96 | */ 97 | protected abstract T createObjectPage(RawPage page); 98 | 99 | /* (non-Javadoc) 100 | * @see PageManager#hasPage(long) 101 | */ 102 | @Override 103 | public boolean hasPage(final int id) { 104 | if (!rpm.hasPage(id)) 105 | return false; 106 | 107 | try { 108 | getPage(id); 109 | return true; 110 | } catch (Exception e) { 111 | return false; 112 | } 113 | } 114 | 115 | 116 | @Override public void writePage(T page) { 117 | rpm.writePage(page.rawPage()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/CachedResourceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import com.google.common.base.Objects; 14 | import com.google.common.cache.*; 15 | 16 | import java.io.IOException; 17 | import java.util.concurrent.ExecutionException; 18 | 19 | import static com.google.common.base.Preconditions.checkArgument; 20 | import static com.google.common.base.Preconditions.checkNotNull; 21 | 22 | /** 23 | * This class caches RawPages coming from a ResourceManager. Through the caching, we can ensure that pages are written 24 | * back to the ResourceManager even if they are not explicitly persisted in the using class. 25 | *

26 | * Additionally, if a Page is requested which is not in cache but still in Memory (meaning that maybe it's still being 27 | * used), then the memory instance is added to the cache and returned. 28 | *

29 | * CachedResourceManager works with all kinds of ResourceManagers, although usually used with a {@link 30 | * FileResourceManager} 31 | */ 32 | public class CachedResourceManager implements ResourceManager { 33 | 34 | private final ResourceManager rm; 35 | private final Cache cache; 36 | private final int cacheSize; 37 | 38 | CachedResourceManager(final ResourceManager _rm, final int cacheSize) { 39 | checkNotNull(_rm); 40 | checkArgument(cacheSize > 0, "cacheSize must be > 0"); 41 | 42 | this.rm = _rm; 43 | this.cacheSize = cacheSize; 44 | this.cache = CacheBuilder.newBuilder().maximumSize(cacheSize) 45 | .removalListener(new RemovalListener() { 46 | @Override 47 | public void onRemoval(final RemovalNotification integerRawPageRemovalNotification) { 48 | final RawPage rawPage = integerRawPageRemovalNotification.getValue(); 49 | rawPage.sync(); 50 | } 51 | }) 52 | .build(new CacheLoader() { 53 | @Override public RawPage load(final Integer key) throws Exception { 54 | return rm.getPage(key); 55 | } 56 | }); 57 | 58 | } 59 | 60 | public int getCacheSize() { 61 | return cacheSize; 62 | } 63 | 64 | @Override public void writePage(final RawPage page) { 65 | cache.asMap().put(page.id(), page); 66 | rm.writePage(page); 67 | } 68 | 69 | @Override public Integer getPageSize() { 70 | return rm.getPageSize(); 71 | } 72 | 73 | @Override public void open() throws IOException { 74 | rm.open(); 75 | } 76 | 77 | @Override public boolean isOpen() { 78 | return rm.isOpen(); 79 | } 80 | 81 | @Override public void close() throws IOException { 82 | sync(); 83 | cache.invalidateAll(); 84 | rm.close(); 85 | } 86 | 87 | @Override public int numberOfPages() { 88 | return rm.numberOfPages(); 89 | } 90 | 91 | @Override public void clear() { 92 | cache.invalidateAll(); 93 | rm.clear(); 94 | } 95 | 96 | @Override public RawPage createPage() { 97 | final RawPage page = rm.createPage(); 98 | cache.asMap().put(page.id(), page); 99 | return page; 100 | } 101 | 102 | @Override public RawPage getPage(final int id) { 103 | try { 104 | return cache.get(id); 105 | } catch (ExecutionException e) { 106 | throw new RuntimeException(e); 107 | } 108 | } 109 | 110 | @Override public void removePage(final int id) { 111 | cache.invalidate(id); 112 | rm.removePage(id); 113 | } 114 | 115 | @Override public boolean hasPage(final int id) { 116 | return rm.hasPage(id); 117 | } 118 | 119 | public void sync() { 120 | for (final RawPage p : cache.asMap().values()) { 121 | p.sync(); 122 | } 123 | } 124 | 125 | public ResourceManager getResourceManager() { 126 | return rm; 127 | } 128 | 129 | public Cache getCache() { 130 | return cache; 131 | } 132 | 133 | public String toString(){ 134 | return Objects.toStringHelper(this) 135 | .add("maxCacheSize", cacheSize) 136 | .add("cacheSize", cache.asMap().size()) 137 | .add("cacheStats", cache.stats()) 138 | .add("resourceManager", rm) 139 | .toString(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/ComplexPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import de.rwhq.io.MustInitializeOrLoad; 13 | 14 | public interface ComplexPage extends MustInitializeOrLoad { 15 | 16 | /** 17 | * @return the underlying RawPage 18 | */ 19 | public RawPage rawPage(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/DataPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import de.rwhq.serializer.FixLengthSerializer; 13 | import de.rwhq.serializer.Serializer; 14 | 15 | 16 | public interface DataPage extends ComplexPage { 17 | 18 | /** 19 | * adds some bytes to the underlying body. It is possible that the header 20 | * also grows through this process. 21 | * 22 | * @param value array to be written 23 | * @return id of the entry/byte[] within this page, or null if the page could not be added (e.g. page is full) 24 | */ 25 | public Integer add(T value); 26 | 27 | 28 | /** 29 | * @param id within this page 30 | * @return byte array with this id 31 | */ 32 | public T get(int id); 33 | 34 | 35 | /** 36 | * removes the byte array with the given id and truncates the page 37 | * @param id of the byte array to be removed 38 | */ 39 | public void remove(int id); 40 | 41 | /** 42 | * @return the remaining number Of bytes that can be used by the body or header 43 | */ 44 | public int remaining(); 45 | 46 | /** 47 | * @return number of entries stored in the DataPage 48 | */ 49 | public int numberOfEntries(); 50 | 51 | /** 52 | * @return the serializer object used to serialize PagePoints 53 | */ 54 | public FixLengthSerializer pagePointSerializer(); 55 | 56 | /** 57 | * @return the serializer object used to serialize the data 58 | */ 59 | public Serializer dataSerializer(); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/DataPageManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import de.rwhq.serializer.FixLengthSerializer; 14 | import de.rwhq.serializer.Serializer; 15 | 16 | public class DataPageManager extends AbstractPageManager> { 17 | 18 | private final PageManager bpm; 19 | private final FixLengthSerializer pointSerializer; 20 | private final Serializer dataSerializer; 21 | 22 | public DataPageManager( 23 | final PageManager bpm, 24 | final FixLengthSerializer pointSerializer, 25 | final Serializer dataSerializer 26 | ){ 27 | super(bpm); 28 | this.bpm = bpm; 29 | this.pointSerializer = pointSerializer; 30 | this.dataSerializer = dataSerializer; 31 | } 32 | 33 | 34 | /* (non-Javadoc) 35 | * @see PageManager#hasPage(long) 36 | */ 37 | @Override 38 | public boolean hasPage(final int id) { 39 | return bpm.hasPage(id); 40 | } 41 | 42 | /* (non-Javadoc) 43 | * @see AbstractPageManager#createObjectPage(RawPage) 44 | */ 45 | @Override 46 | protected DataPage createObjectPage(final RawPage page) { 47 | return new DynamicDataPage(page, pointSerializer, dataSerializer); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/DuplicatePageIdException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | public class DuplicatePageIdException extends RuntimeException { 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | public DuplicatePageIdException(final Long id) { 17 | super("The page with the id " + id + " does already exist."); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/DynamicDataPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import de.rwhq.serializer.FixLengthSerializer; 13 | import de.rwhq.serializer.Serializer; 14 | 15 | import java.io.IOException; 16 | import java.nio.ByteBuffer; 17 | import java.util.Map; 18 | import java.util.Random; 19 | import java.util.TreeMap; 20 | 21 | public class DynamicDataPage implements DataPage, ComplexPage { 22 | 23 | private final RawPage rawPage; 24 | 25 | private final FixLengthSerializer pointSerializer; 26 | private final Serializer entrySerializer; 27 | 28 | 29 | /** 30 | * ByteBuffer.getInt returns 0 if no int could be read. To avoid thinking we already initialized the buffer, 31 | * we write down this number instead of 0 if we have no entries. 32 | */ 33 | public static final int NO_ENTRIES_INT = -1; 34 | 35 | // id | offset in this page 36 | private final Map entries; 37 | 38 | private boolean valid = false; 39 | 40 | private int getHeaderSize(){ 41 | return Integer.SIZE + entries.size() * Integer.SIZE * 2; 42 | } 43 | 44 | DynamicDataPage( 45 | final RawPage rawPage, 46 | final FixLengthSerializer pointSerializer, 47 | final Serializer dataSerializer){ 48 | 49 | this.rawPage = rawPage; 50 | 51 | this.pointSerializer = pointSerializer; 52 | this.entrySerializer = dataSerializer; 53 | 54 | this.entries = new TreeMap(); 55 | } 56 | 57 | @Override 58 | public void initialize() { 59 | writeAndAdjustHeader(); 60 | this.valid = true; 61 | } 62 | 63 | /* (non-Javadoc) 64 | * @see com.rwhq.btree.DataPage#add(byte[]) 65 | */ 66 | @Override 67 | public Integer add(final T entry) { 68 | ensureValid(); 69 | 70 | final byte[] bytes = entrySerializer.serialize(entry); 71 | 72 | if(bytes.length > remaining()) 73 | return null; 74 | 75 | final int bodyOffset = getBodyOffset() - bytes.length; 76 | rawPage.bufferForWriting(bodyOffset).put(bytes); 77 | 78 | final int id = generateId(); 79 | if(entries.containsKey(id)){ 80 | throw new IllegalStateException(); 81 | } 82 | 83 | entries.put(id, bodyOffset); 84 | 85 | writeAndAdjustHeader(); 86 | 87 | return id; 88 | } 89 | 90 | private int generateId(){ 91 | final Random r = new Random(); 92 | int id; 93 | while(entries.containsKey(id = r.nextInt())){} 94 | return id; 95 | } 96 | 97 | private int bodyUsedBytes(){ 98 | return rawPage.bufferForWriting(0).limit() - getBodyOffset(); 99 | } 100 | 101 | /* (non-Javadoc) 102 | * @see com.rwhq.btree.DataPage#remove(int) 103 | */ 104 | @Override 105 | public void remove(final int id) { 106 | 107 | final Integer offset = entries.get(id); 108 | if(offset == null) 109 | return; 110 | 111 | // move all body elements 112 | final int size = sizeOfEntryAt(offset); 113 | System.arraycopy(rawPage.bufferForWriting(0).array(), getBodyOffset(), rawPage.bufferForWriting(0).array(), getBodyOffset() + size, offset - getBodyOffset() ); 114 | 115 | // adjust the entries in the entries array 116 | for(final int key : entries.keySet()){ 117 | if(entries.get(key) < offset) 118 | entries.put(key, entries.get(key) + size); 119 | } 120 | 121 | entries.remove(id); 122 | 123 | // write the adjustments to byte array 124 | writeAndAdjustHeader(); 125 | } 126 | 127 | /** 128 | * Creates a valid header by writing the entries in memory to the header and adjusts the header limit. 129 | */ 130 | private void writeAndAdjustHeader() { 131 | final ByteBuffer buffer = rawPage().bufferForWriting(0); 132 | buffer.putInt(entries.size() == 0 ? NO_ENTRIES_INT : entries.size()); 133 | 134 | for(final int key : entries.keySet()){ 135 | buffer.putInt(key); 136 | buffer.putInt(entries.get(key)); 137 | } 138 | } 139 | 140 | /* (non-Javadoc) 141 | * @see com.rwhq.btree.DataPage#get(int) 142 | */ 143 | @Override 144 | public T get(final int id) { 145 | ensureValid(); 146 | 147 | if(!entries.containsKey(id)) 148 | return null; 149 | 150 | final Integer offset = entries.get(id); 151 | 152 | final int size = sizeOfEntryAt(offset); 153 | final byte[] bytes = new byte[size]; 154 | rawPage.bufferForReading(offset).get(bytes); 155 | 156 | return entrySerializer.deserialize(bytes); 157 | } 158 | 159 | private void ensureValid() throws InvalidPageException { 160 | if( !isValid() ) 161 | throw new InvalidPageException(this); 162 | } 163 | 164 | private int nextEntry(final int offset){ 165 | int smallestLarger = -1; 166 | 167 | for(final int o : entries.values()){ 168 | if(o > offset){ 169 | if(o < smallestLarger || smallestLarger == -1) 170 | smallestLarger = o; 171 | } 172 | } 173 | 174 | return smallestLarger; 175 | } 176 | 177 | private int sizeOfEntryAt(final int offset){ 178 | int smallestLarger = nextEntry(offset); 179 | 180 | if(smallestLarger == -1) 181 | smallestLarger = rawPage.bufferForReading(0).limit(); 182 | 183 | return smallestLarger - offset; 184 | } 185 | 186 | /* (non-Javadoc) 187 | * @see com.rwhq.btree.DataPage#remaining() 188 | */ 189 | @Override 190 | public int remaining() { 191 | return rawPage.bufferForReading(0).limit() - getHeaderSize() - bodyUsedBytes(); 192 | } 193 | /* (non-Javadoc) 194 | * @see ComplexPage#load() 195 | */ 196 | @Override 197 | public void load() { 198 | entries.clear(); 199 | 200 | final ByteBuffer buffer = rawPage().bufferForReading(0); 201 | final int numberOfEntries = buffer.getInt(); 202 | 203 | for(int i = 0; i < numberOfEntries; i++){ 204 | final int key = buffer.getInt(); 205 | entries.put(key, buffer.getInt()); 206 | } 207 | 208 | valid = true; 209 | } 210 | 211 | private int getBodyOffset(){ 212 | int offset = rawPage().bufferForReading(0).limit(); 213 | for(final int pos : entries.values()){ 214 | if(pos < offset) 215 | offset = pos; 216 | } 217 | return offset; 218 | } 219 | 220 | /* (non-Javadoc) 221 | * @see com.rwhq.io.rm.ComplexPage#isValid() 222 | */ 223 | @Override 224 | public boolean isValid() { 225 | return valid; 226 | } 227 | 228 | @Override 229 | public void loadOrInitialize() throws IOException { 230 | try { 231 | load(); 232 | } catch (Exception e){ 233 | initialize(); 234 | } 235 | } 236 | 237 | /* (non-Javadoc) 238 | * @see DataPage#numberOfEntries() 239 | */ 240 | @Override 241 | public int numberOfEntries() throws InvalidPageException { 242 | ensureValid(); 243 | 244 | return entries.size(); 245 | } 246 | 247 | /* (non-Javadoc) 248 | * @see com.rwhq.io.rm.ComplexPage#rawPage() 249 | */ 250 | @Override 251 | public RawPage rawPage() { 252 | return rawPage; 253 | } 254 | 255 | /* (non-Javadoc) 256 | * @see DataPage#pagePointSerializer() 257 | */ 258 | @Override 259 | public FixLengthSerializer pagePointSerializer() { 260 | return pointSerializer; 261 | } 262 | 263 | /* (non-Javadoc) 264 | * @see DataPage#dataSerializer() 265 | */ 266 | @Override 267 | public Serializer dataSerializer() { 268 | return entrySerializer; 269 | } 270 | 271 | @Override 272 | public String toString(){ 273 | return "DynamicDataPage(id: " + System.identityHashCode(this) + ", rawPage:" + rawPage().id() + ", entries: " + entries.size() + ", headerSize: + "+ getHeaderSize()+", bodyOffset: "+getBodyOffset()+")"; 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/FileResourceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import com.google.common.base.Objects; 13 | import org.apache.commons.logging.Log; 14 | import org.apache.commons.logging.LogFactory; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.io.RandomAccessFile; 19 | import java.nio.ByteBuffer; 20 | import java.nio.channels.FileChannel; 21 | import java.nio.channels.FileLock; 22 | 23 | import static com.google.common.base.Preconditions.checkState; 24 | 25 | 26 | /** 27 | * Writes Pages to a File. It does not cache, and Pages have to be written back manually or the changes will not be 28 | * written to disk. 29 | */ 30 | public class FileResourceManager implements ResourceManager { 31 | private RandomAccessFile handle; 32 | private final File file; 33 | private FileLock fileLock; 34 | private FileChannel ioChannel; 35 | private ResourceHeader header; 36 | private boolean doLock; 37 | 38 | private static Log LOG = LogFactory.getLog(FileResourceManager.class); 39 | 40 | FileResourceManager(final ResourceManagerBuilder builder) { 41 | this.file = builder.getFile(); 42 | this.doLock = builder.useLock(); 43 | this.header = new ResourceHeader(this, builder.getPageSize()); 44 | } 45 | 46 | /* (non-Javadoc) 47 | * @see ResourceManager#open() 48 | */ 49 | @Override 50 | public void open() throws IOException { 51 | if (isOpen()) 52 | throw new IllegalStateException("Resource already open"); 53 | 54 | // if the file does not exist already 55 | if (!getFile().exists()) { 56 | getFile().createNewFile(); 57 | } 58 | 59 | initIOChannel(getFile()); 60 | 61 | if(header.isValid()) 62 | header = new ResourceHeader(this, header.getPageSize()); 63 | 64 | if (handle.length() == 0) { 65 | header.initialize(); 66 | } else { 67 | // load header if file existed 68 | header.load(); 69 | } 70 | } 71 | 72 | @Override 73 | public void writePage(final RawPage page) { 74 | if (LOG.isDebugEnabled()) 75 | LOG.debug("writing page to disk: " + page.id()); 76 | 77 | ensureOpen(); 78 | ensurePageExists(page.id()); 79 | 80 | final ByteBuffer buffer = page.bufferForReading(0); 81 | 82 | try { 83 | final long offset = header.getPageOffset(page.id()); 84 | ioChannel.write(buffer, offset); 85 | } catch (IOException e) { 86 | throw new RuntimeException(e); 87 | } 88 | } 89 | 90 | /* (non-Javadoc) 91 | * @see com.rwhq.io.rm.PageManager#getPage(long) 92 | */ 93 | @Override 94 | public RawPage getPage(final int pageId) { 95 | 96 | ensureOpen(); 97 | ensurePageExists(pageId); 98 | 99 | final RawPage result; 100 | 101 | final ByteBuffer buf = ByteBuffer.allocate(header.getPageSize()); 102 | 103 | try { 104 | ioChannel.read(buf, header.getPageOffset(pageId)); 105 | } catch (IOException e) { 106 | e.printStackTrace(); 107 | System.exit(1); 108 | } 109 | 110 | result = new RawPage(buf, pageId, this); 111 | return result; 112 | } 113 | 114 | /** 115 | * @param pageId 116 | * @throws PageNotFoundException 117 | */ 118 | private void ensurePageExists(final int pageId) { 119 | if (!header.contains(pageId)) 120 | throw new PageNotFoundException(this, pageId); 121 | } 122 | 123 | /* (non-Javadoc) 124 | * @see ResourceManager#close() 125 | */ 126 | @Override 127 | public void close() throws IOException { 128 | try { 129 | if (fileLock != null && fileLock.isValid()) { 130 | fileLock.release(); 131 | fileLock = null; 132 | } 133 | 134 | if (ioChannel != null) { 135 | ioChannel.close(); 136 | ioChannel = null; 137 | } 138 | 139 | if (handle != null) { 140 | handle.close(); 141 | handle = null; 142 | } 143 | } catch (Exception ignored) { 144 | } 145 | } 146 | 147 | /* (non-Javadoc) 148 | * @see ResourceManager#getPageSize() 149 | */ 150 | @Override 151 | public Integer getPageSize() { 152 | return header.getPageSize(); 153 | } 154 | 155 | /** 156 | * Generic private initializer that takes the random access file and initializes the I/O channel and locks it for 157 | * exclusive use by this instance. 158 | *

159 | * from minidb 160 | * 161 | * @param file 162 | * The random access file representing the index. 163 | * @throws IOException 164 | * Thrown, when the I/O channel could not be opened. 165 | */ 166 | private void initIOChannel(final File file) 167 | throws IOException { 168 | handle = new RandomAccessFile(file, "rw"); 169 | 170 | // Open the channel. If anything fails, make sure we close it again 171 | for (int i = 1; i <= 5; i++) { 172 | try { 173 | ioChannel = handle.getChannel(); 174 | if (doLock) { 175 | LOG.debug("trying to aquire lock ..."); 176 | ioChannel.lock(); 177 | LOG.debug("lock aquired"); 178 | break; 179 | } 180 | } catch (Throwable t) { 181 | LOG.warn("File " + file.getAbsolutePath() + " could not be locked in attempt " + i + "."); 182 | 183 | try { 184 | Thread.sleep(200); 185 | } catch (InterruptedException ignored) { 186 | } 187 | 188 | // propagate the exception 189 | if (i >= 5) { 190 | close(); 191 | throw new IOException("An error occured while opening the index: ", t); 192 | } 193 | } 194 | } 195 | } 196 | 197 | @Override 198 | public boolean isOpen() { 199 | return !(ioChannel == null || !ioChannel.isOpen()); 200 | } 201 | 202 | /* 203 | * (non-Javadoc) 204 | * @see java.lang.Object#toString() 205 | */ 206 | @Override 207 | public String toString() { 208 | Objects.ToStringHelper helper = Objects.toStringHelper(this) 209 | .add("file", getFile().getAbsolutePath()) 210 | .add("isOpen", isOpen()) 211 | .add("pageSize", getPageSize()); 212 | 213 | if (isOpen()) 214 | helper.add("numberOfPages", numberOfPages()); 215 | 216 | return helper.toString(); 217 | } 218 | 219 | private void ensureOpen() { 220 | if (!isOpen()) 221 | throw new IllegalStateException("Resource is not open: " + toString()); 222 | 223 | checkState(file.exists(), "File (%s) has been deleted externally.", getFile().getAbsolutePath()); 224 | } 225 | 226 | /* (non-Javadoc) 227 | * @see ResourceManager#numberOfPages() 228 | */ 229 | @Override 230 | public int numberOfPages() { 231 | ensureOpen(); 232 | return header.getNumberOfPages(); 233 | } 234 | 235 | @Override public void clear() { 236 | ensureOpen(); 237 | header = new ResourceHeader(this, header.getPageSize()); 238 | 239 | try { 240 | ioChannel.truncate(0); 241 | } catch (IOException e) { 242 | throw new RuntimeException(e); 243 | } 244 | 245 | header.initialize(); 246 | } 247 | 248 | /* (non-Javadoc) 249 | * @see ResourceManager#createPage() 250 | */ 251 | @Override 252 | public RawPage createPage() { 253 | ensureOpen(); 254 | 255 | final ByteBuffer buf = ByteBuffer.allocate(header.getPageSize()); 256 | final RawPage result = new RawPage(buf, header.generateId(), this); 257 | 258 | return result; 259 | } 260 | 261 | /* (non-Javadoc) 262 | * @see ResourceManager#removePage(long) 263 | */ 264 | @Override 265 | public void removePage(final int pageId) { 266 | header.removePage(pageId); 267 | } 268 | 269 | @Override 270 | protected void finalize() throws Throwable { 271 | try { 272 | close(); 273 | } catch (Exception e) { 274 | super.finalize(); 275 | } 276 | } 277 | 278 | /* (non-Javadoc) 279 | * @see com.rwhq.io.rm.PageManager#hasPage(long) 280 | */ 281 | @Override 282 | public boolean hasPage(final int id) { 283 | ensureOpen(); 284 | return header.contains(id); 285 | } 286 | 287 | /** @return the file */ 288 | public File getFile() { 289 | return file; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/InvalidPageException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | public class InvalidPageException extends RuntimeException { 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | public InvalidPageException() { 17 | super("The page is invalid!"); 18 | } 19 | 20 | public InvalidPageException(final Object p){ 21 | super("Page " + p.toString() + " is invalid!"); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/PageManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | /** 13 | * None of the methods throw an IOException. It is assumed, that the PageManager has an open Resource to which it can 14 | * write. 15 | * 16 | * @param 17 | */ 18 | public interface PageManager { 19 | 20 | 21 | /** 22 | * write the provided RawPage to the resource. The Resource must have been created with createPage(). 23 | * 24 | * @param page 25 | * RawPage to write to the Resource. The ResourceManager and Id of the RawPage must be set. 26 | */ 27 | public void writePage(T page); 28 | 29 | 30 | /** 31 | * creates a new valid Page with a valid id for which space has been reserved in the resource. 32 | * 33 | * @return page 34 | */ 35 | public T createPage(); 36 | 37 | /** 38 | * @param id 39 | * of the page to be fetched 40 | * @return page with given id from resource or cache, null if page could not be found 41 | */ 42 | public T getPage(int id); 43 | 44 | /** 45 | * removes the Page with the given id 46 | * 47 | * @param id 48 | * of the Page to be removed 49 | */ 50 | public void removePage(int id); 51 | 52 | /** 53 | * @param id 54 | * of the page 55 | * @return true, if the page exists 56 | */ 57 | public boolean hasPage(int id); 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/PageNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | public class PageNotFoundException extends RuntimeException { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | public PageNotFoundException(final ResourceManager rm, final RawPage page){ 18 | this(rm, page.id()); 19 | } 20 | 21 | public PageNotFoundException(final ResourceManager rm, final int pageId){ 22 | super("The Page with the id " + pageId + " could not be found in the ResourceManager " + rm.toString()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/PagePointer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | public class PagePointer { 14 | 15 | private int offset; 16 | private int id; 17 | 18 | 19 | 20 | /** 21 | * @param offset 22 | * @param id 23 | */ 24 | public PagePointer(final int id, final int offset) { 25 | super(); 26 | this.offset = offset; 27 | this.id = id; 28 | } 29 | 30 | /** 31 | * @param offset the offset to set 32 | */ 33 | public void setOffset(final int offset) { 34 | this.offset = offset; 35 | } 36 | /** 37 | * @return the offset 38 | */ 39 | public int getOffset() { 40 | return offset; 41 | } 42 | /** 43 | * @param pageId the pageId to set 44 | */ 45 | public void setId(final int pageId) { 46 | this.id = pageId; 47 | } 48 | /** 49 | * @return the pageId 50 | */ 51 | public int getId() { 52 | return id; 53 | } 54 | 55 | @Override 56 | public boolean equals(final Object o){ 57 | return o instanceof PagePointer && ((PagePointer)o).getId() == getId() && 58 | ((PagePointer)o).getOffset() == getOffset(); 59 | } 60 | 61 | @Override 62 | public String toString(){ 63 | return "PagePointer {id: " + getId() + ", offset: " + getOffset() + "}"; 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/PageSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | public class PageSize { 14 | public static final int DEFAULT_PAGE_SIZE = 4096; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/RawPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import java.io.Serializable; 14 | import java.nio.ByteBuffer; 15 | 16 | /** 17 | * A byte array, usually with an id and a ResourceManager it comes from. 18 | */ 19 | public class RawPage implements Serializable { 20 | 21 | private ByteBuffer buffer; 22 | private int id; 23 | private ResourceManager resourceManager; 24 | 25 | // private static Log LOG = LogFactory.getLog(RawPage.class); 26 | 27 | /** 28 | * buffer has been modified since RawPage was created? 29 | */ 30 | private boolean modified = false; 31 | 32 | 33 | public RawPage(final ByteBuffer buffer, final int pageId){this(buffer, pageId, null);} 34 | public RawPage(final ByteBuffer buffer, final int pageId, final ResourceManager rm){ 35 | this.buffer = buffer; 36 | this.id = pageId; 37 | this.resourceManager = rm; 38 | } 39 | 40 | /** 41 | * @param pos 42 | * @return ByteBuffer backing this RawPage 43 | */ 44 | public ByteBuffer bufferForWriting(final int pos){setModified(true); buffer.position(pos); return buffer;} 45 | public ByteBuffer bufferForReading(final int pos){buffer.position(pos); return buffer.asReadOnlyBuffer();} 46 | public Integer id(){return id;} 47 | 48 | /** 49 | * @param modified the modified to set 50 | */ 51 | public void setModified(final boolean modified) { 52 | this.modified = modified; 53 | } 54 | 55 | /** 56 | * @return the modified 57 | */ 58 | public boolean isModified() { 59 | return modified; 60 | } 61 | 62 | /** 63 | * @return the resourceManager 64 | */ 65 | public ResourceManager getResourceManager() { 66 | return resourceManager; 67 | } 68 | 69 | /** 70 | * if this RawPage was modified, this method syncs the RawPage to the ResourceManager it is from. 71 | */ 72 | public void sync() { 73 | if (isModified() && resourceManager != null) 74 | getResourceManager().writePage(this); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/ReferenceCachedResourceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import com.google.common.base.Objects; 14 | import com.google.common.collect.MapMaker; 15 | 16 | import java.io.IOException; 17 | import java.util.concurrent.ConcurrentMap; 18 | 19 | /** 20 | * This class uses a map with weakReferences to alway return the same instance if the instance is still 21 | * in memory. 22 | */ 23 | public class ReferenceCachedResourceManager implements ResourceManager { 24 | 25 | private ResourceManager rm; 26 | private ConcurrentMap map; 27 | 28 | ReferenceCachedResourceManager(ResourceManager rm){ 29 | this.rm = rm; 30 | this.map = new MapMaker().weakValues().makeMap(); 31 | } 32 | 33 | @Override public Integer getPageSize() { 34 | return rm.getPageSize(); 35 | } 36 | 37 | @Override public void open() throws IOException { 38 | rm.open(); 39 | } 40 | 41 | @Override public boolean isOpen() { 42 | return rm.isOpen(); 43 | } 44 | 45 | @Override public void close() throws IOException { 46 | rm.close(); 47 | map.clear(); 48 | } 49 | 50 | @Override public int numberOfPages() { 51 | return rm.numberOfPages(); 52 | } 53 | 54 | @Override public void clear() { 55 | rm.clear(); 56 | map.clear(); 57 | } 58 | 59 | @Override public void writePage(RawPage page) { 60 | rm.writePage(page); 61 | } 62 | 63 | @Override public RawPage createPage() { 64 | RawPage page = rm.createPage(); 65 | map.put(page.id(), page); 66 | return page; 67 | } 68 | 69 | @Override public RawPage getPage(int id) { 70 | RawPage page = map.get(id); 71 | if(page != null) 72 | return page; 73 | 74 | page = rm.getPage(id); 75 | map.put(page.id(), page); 76 | return page; 77 | } 78 | 79 | @Override public void removePage(int id) { 80 | rm.removePage(id); 81 | map.remove(id); 82 | } 83 | 84 | @Override public boolean hasPage(int id) { 85 | return rm.hasPage(id); 86 | } 87 | 88 | public String toString(){ 89 | return Objects.toStringHelper(this) 90 | .add("resourceManager", rm) 91 | .add("mapSize", map.size()) 92 | .toString(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/ResourceHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import com.google.common.annotations.VisibleForTesting; 13 | import de.rwhq.io.AbstractMustInitializeOrLoad; 14 | 15 | import java.io.IOException; 16 | import java.nio.ByteBuffer; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import static com.google.common.base.Preconditions.checkState; 20 | 21 | 22 | /** 23 | * A ResourceHeader that can be applied to different ResourceManagers. 24 | *

25 | * It organizes the underlying byte[] like this: 26 | *

27 | * HEADER: NEXT_PAGE | NUM_OF_FREE_PAGES | PAGE_SIZE | LAST_ID | TOTAL_NUM_OF_FREE_PAGES | FREE_PAGE_1 | FREE_PAGE_X 28 | *

29 | * Page 0 is reserved for the header. 30 | *

31 | * Whenever the ResourceHeader needs extra space for storing free page ids, it can create a page and set this as 32 | * nextPage. It is not possible to swap the next header page behind this page since the offset for a page is calculated 33 | * though the PageId. 34 | *

35 | * The additional Header Pages are structured like this: 36 | *

37 | * NUM_OF_FREE_PAGES | FREE_PAGE_1 | FREE_PAGE_X 38 | */ 39 | public class ResourceHeader extends AbstractMustInitializeOrLoad { 40 | private boolean valid = false; 41 | private RawPage firstPage; 42 | private ResourceManager resourceManager; 43 | private Integer pageSize; 44 | private int lastId; 45 | private RawPage pageForFreeIds; 46 | 47 | 48 | public int getPageSize() { 49 | return pageSize; 50 | } 51 | 52 | @VisibleForTesting 53 | static enum Header { 54 | NEXT_PAGE(0), 55 | FREE_PAGES_NUM(1 * Integer.SIZE / 8), 56 | PAGE_SIZE(2 * Integer.SIZE / 8), 57 | LAST_ID(3 * Integer.SIZE / 8), 58 | FREE_PAGES_TOTAL(4 * Integer.SIZE / 8); 59 | 60 | int offset; 61 | 62 | Header(int offset) { 63 | this.offset = offset; 64 | } 65 | 66 | static int size() { 67 | return 5 * Integer.SIZE / 8; 68 | } 69 | } 70 | 71 | 72 | ResourceHeader(ResourceManager rm, Integer pageSize) { 73 | checkNotNull(rm); 74 | 75 | this.resourceManager = rm; 76 | this.pageSize = pageSize; 77 | } 78 | 79 | 80 | /** @return 0 if the ResourceHeader is not valid, otherwise a number > 0 */ 81 | public int generateId() { 82 | if (valid == false) 83 | return 0; 84 | 85 | if (getFreePagesNum() == 0) { 86 | setLastId(getLastId() + 1); 87 | firstPage.sync(); 88 | return getLastId(); 89 | } 90 | 91 | int result = firstPage.bufferForReading(Header.size() + (getFreePagesNum()-1) * Integer.SIZE / 8).getInt(); 92 | setFreePagesNum(getFreePagesNum() - 1); 93 | setTotalNumberOfFreePages(getTotalNumberOfFreePages() - 1); 94 | firstPage.sync(); 95 | 96 | return result; 97 | } 98 | 99 | private int getLastId() { 100 | return firstPage.bufferForReading(Header.LAST_ID.offset).getInt(); 101 | } 102 | 103 | private void setLastId(int id) { 104 | firstPage.bufferForWriting(Header.LAST_ID.offset).putInt(id); 105 | } 106 | 107 | @Override 108 | public void load() throws IOException { 109 | firstPage = resourceManager.getPage(0); 110 | pageForFreeIds = firstPage; 111 | 112 | final int ps = firstPage.bufferForReading(Header.PAGE_SIZE.offset).getInt(); 113 | 114 | if (pageSize == null) 115 | pageSize = ps; 116 | else if (!pageSize.equals(ps)) 117 | throw new RuntimeException("Resource has a different page size"); 118 | 119 | valid = true; 120 | } 121 | 122 | boolean contains(final int id) { 123 | if (id == 0) 124 | return true; 125 | 126 | if (id > getLastId()) 127 | return false; 128 | 129 | if (containsFreePageId(id)) 130 | return false; 131 | 132 | int nextId = getNextPageId(); 133 | 134 | while (nextId != 0) { 135 | ResourceHeaderOverflowPage next = new ResourceHeaderOverflowPage(resourceManager.getPage(nextId)); 136 | 137 | if (next.containsFreePageId(id)) 138 | return false; 139 | 140 | nextId = next.getNextPageId(); 141 | } 142 | 143 | return true; 144 | } 145 | 146 | private boolean containsFreePageId(int id) { 147 | int free = getTotalNumberOfFreePages(); 148 | if (free == 0) 149 | return false; 150 | 151 | 152 | ByteBuffer buffer = firstPage.bufferForReading(Header.size()); 153 | for (int i = 0; i < free; i++) { 154 | if (buffer.getInt() == id) 155 | return true; 156 | } 157 | 158 | return false; 159 | } 160 | 161 | /** @return number of pages without page or overflow pages */ 162 | int getNumberOfPages() { 163 | return getLastId() - getTotalNumberOfFreePages() - getNumberOfOverflowPages(); 164 | } 165 | 166 | private int getNumberOfOverflowPages() { 167 | int next = getNextPageId(); 168 | if (next == 0) 169 | return 0; 170 | 171 | int num = 0; 172 | while (next != 0) { 173 | num++; 174 | next = new ResourceHeaderOverflowPage(resourceManager.getPage(next)).getNextPageId(); 175 | } 176 | 177 | return num; 178 | } 179 | 180 | private int getNextPageId() { 181 | return firstPage.bufferForReading(Header.NEXT_PAGE.offset).getInt(); 182 | } 183 | 184 | /* (non-Javadoc) 185 | * @see MustInitializeOrLoad#initialize() 186 | */ 187 | @Override 188 | public void initialize() { 189 | checkState(valid == false, "FileResourceHeader already valid."); 190 | checkNotNull(pageSize, "pageSize must not be null when initializing the FileResourceManager"); 191 | checkState(pageSize >= Header.size(), "pageSize must be larger than %s byte. It is %s byte", Header.size(), pageSize); 192 | 193 | firstPage = resourceManager.createPage(); 194 | pageForFreeIds = firstPage; 195 | 196 | firstPage.bufferForWriting(Header.PAGE_SIZE.offset).putInt(pageSize); 197 | firstPage.bufferForWriting(Header.FREE_PAGES_NUM.offset).putInt(0); 198 | firstPage.bufferForWriting(Header.FREE_PAGES_TOTAL.offset).putInt(0); 199 | firstPage.bufferForWriting(Header.NEXT_PAGE.offset).putInt(0); 200 | firstPage.bufferForWriting(Header.LAST_ID.offset).putInt(0); 201 | firstPage.sync(); 202 | 203 | valid = true; 204 | } 205 | 206 | /* (non-Javadoc) 207 | * @see MustInitializeOrLoad#isValid() 208 | */ 209 | @Override 210 | public boolean isValid() { 211 | return valid; 212 | } 213 | 214 | 215 | /** 216 | * @param id 217 | * @return 218 | */ 219 | public Long getPageOffset(final int id) { 220 | if (!contains(id)) 221 | return null; 222 | 223 | // first page reserved for header 224 | return new Long((id) * pageSize); 225 | } 226 | 227 | public void removePage(int pageId) { 228 | addFreePage(pageId); 229 | 230 | setTotalNumberOfFreePages(getTotalNumberOfFreePages() + 1); 231 | firstPage.sync(); 232 | } 233 | 234 | private int getFreePagesNum() { 235 | return firstPage.bufferForReading(Header.FREE_PAGES_NUM.offset).getInt(); 236 | } 237 | 238 | private void setFreePagesNum(int num) { 239 | firstPage.bufferForWriting(Header.FREE_PAGES_NUM.offset).putInt(num); 240 | } 241 | 242 | private void addFreePage(int pageId) { 243 | if (getOffsetForFreePageId() + Integer.SIZE / 8 <= pageSize) { 244 | firstPage.bufferForWriting(getOffsetForFreePageId()).putInt(pageId); 245 | incrFreePagesNum(); 246 | return; 247 | } 248 | 249 | throw new UnsupportedOperationException("ResourceHeaderOverflowPages are not supported yet"); 250 | } 251 | 252 | private void incrFreePagesNum() { 253 | firstPage.bufferForWriting(Header.FREE_PAGES_NUM.offset).putInt(getFreePagesNum() + 1); 254 | } 255 | 256 | private int getOffsetForFreePageId() { 257 | int offset = Header.size(); 258 | offset += firstPage.bufferForReading(Header.FREE_PAGES_NUM.offset).getInt() * Integer.SIZE / 8; 259 | return offset; 260 | } 261 | 262 | private void setTotalNumberOfFreePages(int i) { 263 | firstPage.bufferForWriting(Header.FREE_PAGES_TOTAL.offset).putInt(i); 264 | } 265 | 266 | private int getTotalNumberOfFreePages() { 267 | return firstPage.bufferForReading(Header.FREE_PAGES_TOTAL.offset).getInt(); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/ResourceHeaderOverflowPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | 16 | /** 17 | * Header: NEXT_PAGE | FREE_PAGES_NUM | FREE_PAGE_1 | FREE_PAGE_X 18 | */ 19 | class ResourceHeaderOverflowPage { 20 | 21 | private final RawPage rawPage; 22 | 23 | public int getNextPageId() { 24 | return rawPage.bufferForReading(Header.NEXT_PAGE.offset).getInt(); 25 | } 26 | 27 | public void initialize() throws IOException { 28 | rawPage.bufferForWriting(Header.NEXT_PAGE.offset).putInt(0); 29 | rawPage.bufferForWriting(Header.FREE_PAGES_NUM.offset).putInt(0); 30 | } 31 | 32 | public boolean containsFreePageId(int id) { 33 | int c = getNumberOfFreeIds(); 34 | if(c == 0) 35 | return false; 36 | 37 | ByteBuffer buffer = rawPage.bufferForReading(Header.size()); 38 | for(int i = 0; i < c; i++){ 39 | if(buffer.getInt() == id) 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | private int getNumberOfFreeIds() { 46 | return rawPage.bufferForReading(Header.FREE_PAGES_NUM.offset).getInt(); 47 | } 48 | 49 | 50 | static enum Header { 51 | NEXT_PAGE(0), 52 | FREE_PAGES_NUM(Integer.SIZE / 8); 53 | private int offset; 54 | 55 | Header(int offset){ 56 | this.offset = offset; 57 | } 58 | 59 | public static int size() { 60 | return 2 * Integer.SIZE / 8; 61 | } 62 | } 63 | 64 | ResourceHeaderOverflowPage(RawPage p){ 65 | this.rawPage = p; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/ResourceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * Provides RawPages from a Resource. 17 | * It is not ensured that the pages are automatically persisted. 18 | * To make sure that this is the case, call #writePage(). 19 | * 20 | * An alternative Interface is {@link AutoSaveResourceManager}. 21 | */ 22 | public interface ResourceManager extends PageManager { 23 | 24 | /** 25 | * @return size of the pages in this resource 26 | */ 27 | public Integer getPageSize(); 28 | 29 | public void open() throws IOException; 30 | public boolean isOpen(); 31 | public void close() throws IOException; 32 | 33 | /** 34 | * @return the number of real pages, not header pages 35 | */ 36 | public int numberOfPages(); 37 | 38 | /** 39 | * removes all pages from the ResourceManager 40 | */ 41 | public void clear(); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/ResourceManagerBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | 16 | import static com.google.common.base.Preconditions.checkArgument; 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | 19 | /** used to configure and build Resource Managers */ 20 | public class ResourceManagerBuilder { 21 | private boolean useLock = true; 22 | private int cacheSize = 100; 23 | private int pageSize = PageSize.DEFAULT_PAGE_SIZE; 24 | private boolean open = false; 25 | private boolean useReferenceCache = false; 26 | 27 | private File file = null; 28 | 29 | public ResourceManagerBuilder useReferenceCache(final boolean referenceCached){ 30 | this.useReferenceCache = referenceCached; 31 | return this; 32 | } 33 | 34 | public ResourceManagerBuilder useLock(final boolean useLock) { 35 | this.useLock = useLock; 36 | return this; 37 | } 38 | 39 | public ResourceManagerBuilder cacheSize(final int cacheSize) { 40 | checkArgument(cacheSize >= 0, "cacheSize must be positive"); 41 | this.cacheSize = cacheSize; 42 | return this; 43 | } 44 | 45 | public ResourceManagerBuilder pageSize(final int pageSize) { 46 | checkArgument(pageSize >= 24, "min pageSize is 24"); 47 | this.pageSize = pageSize; 48 | return this; 49 | } 50 | 51 | public ResourceManagerBuilder file(final File file) { 52 | checkNotNull(file, "file must not be null"); 53 | this.file = file; 54 | return this; 55 | } 56 | 57 | public ResourceManagerBuilder file(final String file) { 58 | checkNotNull(file, "file must not be null"); 59 | this.file = new File(file); 60 | return this; 61 | } 62 | 63 | public ResourceManager build() { 64 | checkNotNull(file, "file must be set"); 65 | 66 | ResourceManager rm = new FileResourceManager(this); 67 | if(useReferenceCache){ 68 | rm = new ReferenceCachedResourceManager(rm); 69 | } 70 | 71 | if (cacheSize > 0) { 72 | rm = new CachedResourceManager(rm, cacheSize); 73 | } 74 | 75 | if(open){ 76 | try { 77 | rm.open(); 78 | } catch (IOException e) { 79 | throw new RuntimeException(e); 80 | } 81 | } 82 | 83 | return rm; 84 | } 85 | 86 | public ResourceManagerBuilder open(){ 87 | this.open = true; 88 | return this; 89 | } 90 | 91 | int getCacheSize() { 92 | return cacheSize; 93 | } 94 | 95 | File getFile() { 96 | return file; 97 | } 98 | 99 | int getPageSize() { 100 | return pageSize; 101 | } 102 | 103 | boolean useLock() { 104 | return useLock; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/io/rm/WrongPageSizeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | public class WrongPageSizeException extends IllegalStateException { 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | WrongPageSizeException(final RawPage p, final int expected){ 17 | super("The Page " + p + " does not have the expected PageSize of " + expected); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/FixLengthSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.serializer; 12 | 13 | /** 14 | * A Serializer that serializes always to the same String/Buffer length 15 | * 16 | * @author "Robin Wenglewski " 17 | * 18 | */ 19 | public interface FixLengthSerializer extends Serializer { 20 | 21 | /** 22 | * @return length of the object returned by {@link #serialize(Object)} 23 | */ 24 | public int getSerializedLength(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/FixedStringSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.serializer; 11 | 12 | import org.apache.commons.logging.Log; 13 | import org.apache.commons.logging.LogFactory; 14 | 15 | import java.nio.ByteBuffer; 16 | 17 | public enum FixedStringSerializer implements FixLengthSerializer { 18 | INSTANCE(100), 19 | INSTANCE_10(10), 20 | INSTANCE_100(100), 21 | INSTANCE_1000(1000); 22 | 23 | private int length; 24 | 25 | private static Log LOG = LogFactory.getLog(FixedStringSerializer.class); 26 | 27 | 28 | private FixedStringSerializer(final int length) { 29 | this.length = length; 30 | } 31 | 32 | /* (non-Javadoc) 33 | * @see Serializer#serialize(java.lang.Object) 34 | */ 35 | @Override 36 | public byte[] serialize(final String o) { 37 | final byte[] bytes = o.getBytes(); 38 | 39 | if(bytes.length > (length - 1)){ 40 | throw new IllegalArgumentException("String is too long to be serialized"); 41 | } 42 | 43 | 44 | final ByteBuffer buf = ByteBuffer.allocate(length); 45 | buf.putShort((short) bytes.length); 46 | buf.put(bytes); 47 | return buf.array(); 48 | } 49 | 50 | /* (non-Javadoc) 51 | * @see com.rwhq.serializer.Serializer#deserialize(java.lang.Object) 52 | */ 53 | @Override 54 | public String deserialize(final byte[] o) { 55 | final ByteBuffer buf = ByteBuffer.wrap(o); 56 | final short length = buf.getShort(); 57 | 58 | final byte[] bytes = new byte[length]; 59 | buf.get(bytes); 60 | return new String(bytes); 61 | } 62 | 63 | /* (non-Javadoc) 64 | * @see com.rwhq.serializer.FixLengthSerializer#getSerializedLength() 65 | */ 66 | @Override 67 | public int getSerializedLength() { 68 | return length; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/IntegerSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.serializer; 11 | 12 | import java.nio.ByteBuffer; 13 | 14 | public enum IntegerSerializer implements FixLengthSerializer { 15 | INSTANCE; 16 | 17 | /* (non-Javadoc) 18 | * @see com.rwhq.serializer.Serializer#serialize(java.lang.Object) 19 | */ 20 | @Override 21 | public byte[] serialize(final Integer o) { 22 | return ByteBuffer.allocate(4).putInt(o).array(); 23 | } 24 | 25 | /* (non-Javadoc) 26 | * @see com.rwhq.serializer.Serializer#deserialize(java.lang.Object) 27 | */ 28 | @Override 29 | public Integer deserialize(final byte[] o) { 30 | return ByteBuffer.wrap(o).getInt(); 31 | } 32 | 33 | /* (non-Javadoc) 34 | * @see FixLengthSerializer#getSerializedLength() 35 | */ 36 | @Override 37 | public int getSerializedLength() { 38 | // TODO Auto-generated method stub 39 | return 4; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/LongSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.serializer; 12 | 13 | import java.nio.ByteBuffer; 14 | 15 | public enum LongSerializer implements FixLengthSerializer { 16 | INSTANCE; 17 | 18 | @Override 19 | public byte[] serialize(final Long o) { 20 | final ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / 8); 21 | buffer.position(0); 22 | buffer.putLong(o); 23 | return buffer.array(); 24 | } 25 | 26 | @Override 27 | public Long deserialize(final byte[] o) { 28 | return ByteBuffer.wrap(o).getLong(); 29 | } 30 | 31 | @Override 32 | public int getSerializedLength() { 33 | return Long.SIZE / 8; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/PagePointSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.serializer; 11 | 12 | import de.rwhq.io.rm.PagePointer; 13 | 14 | import java.nio.ByteBuffer; 15 | 16 | public enum PagePointSerializer implements FixLengthSerializer { 17 | INSTANCE; 18 | 19 | /* (non-Javadoc) 20 | * @see Serializer#serialize(java.lang.Object) 21 | */ 22 | @Override 23 | public byte[] serialize(final PagePointer o) { 24 | final ByteBuffer b = ByteBuffer.allocate(getSerializedLength()); 25 | b.putInt(o.getId()); 26 | b.putInt(o.getOffset()); 27 | return b.array(); 28 | } 29 | 30 | /* (non-Javadoc) 31 | * @see Serializer#deserialize(java.lang.Object) 32 | */ 33 | @Override 34 | public PagePointer deserialize(final byte[] o) { 35 | final ByteBuffer b = ByteBuffer.wrap(o); 36 | final Integer id = b.getInt(); 37 | final Integer offset = b.getInt(); 38 | return new PagePointer(id, offset); 39 | } 40 | 41 | /* (non-Javadoc) 42 | * @see FixLengthSerializer#getSerializedLength() 43 | */ 44 | @Override 45 | public int getSerializedLength() { 46 | // TODO Auto-generated method stub 47 | return 8; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/Serializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.serializer; 12 | 13 | public interface Serializer { 14 | 15 | /** 16 | * serializes the object of type InputType 17 | * 18 | * @param o input object 19 | * @return input object serialized as ResultType 20 | */ 21 | public ResultType serialize(InputType o); 22 | 23 | /** 24 | * deserializes the object of type ResultType 25 | * 26 | * @param o serialized object 27 | * @return deserialized object 28 | */ 29 | public InputType deserialize(ResultType o); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/StringCutSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.serializer; 12 | 13 | import com.google.common.collect.MapMaker; 14 | 15 | import java.nio.ByteBuffer; 16 | import java.util.concurrent.ConcurrentMap; 17 | 18 | public class StringCutSerializer implements FixLengthSerializer { 19 | private static ConcurrentMap cache = null; 20 | 21 | public static StringCutSerializer get(final Integer size) { 22 | if (cache == null) { 23 | cache = new MapMaker().weakValues().makeMap(); 24 | } else if (cache.containsKey(size)) { 25 | return cache.get(size); 26 | } 27 | 28 | final StringCutSerializer s = new StringCutSerializer(size); 29 | cache.put(size, s); 30 | return s; 31 | } 32 | 33 | private int size; 34 | 35 | private StringCutSerializer(final int size) { 36 | this.size = size; 37 | } 38 | 39 | @Override public int getSerializedLength() { 40 | return size; 41 | } 42 | 43 | @Override public byte[] serialize(final String o) { 44 | final ByteBuffer buf = ByteBuffer.allocate(size); 45 | buf.putShort((short) 0); 46 | 47 | final byte[] bytes = o.getBytes(); 48 | final short toWrite = (short) (bytes.length > buf.remaining() ? buf.remaining() : bytes.length); 49 | 50 | buf.put(bytes, 0, toWrite); 51 | buf.position(0); 52 | buf.putShort(toWrite); 53 | 54 | return buf.array(); 55 | } 56 | 57 | @Override public String deserialize(final byte[] o) { 58 | final ByteBuffer buf = ByteBuffer.wrap(o); 59 | final short length = buf.getShort(); 60 | final byte[] bytes = new byte[length]; 61 | buf.get(bytes); 62 | return new String(bytes); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/de/rwhq/serializer/StringSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.serializer; 11 | 12 | public enum StringSerializer implements Serializer { 13 | INSTANCE; 14 | 15 | /* (non-Javadoc) 16 | * @see com.rwhq.serializer.Serializer#serialize(java.lang.Object) 17 | */ 18 | @Override 19 | public byte[] serialize(final String o) { 20 | return o.getBytes(); 21 | } 22 | 23 | /* (non-Javadoc) 24 | * @see Serializer#deserialize(java.lang.Object) 25 | */ 26 | @Override 27 | public String deserialize(final byte[] o) { 28 | return new String(o); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/commons-logging.properties: -------------------------------------------------------------------------------- 1 | # 2 | # This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | # 4 | # http://creativecommons.org/licenses/by-nc/3.0/ 5 | # 6 | # For alternative conditions contact the author. 7 | # 8 | # Copyright (c) 2011 "Robin Wenglewski " 9 | # 10 | 11 | org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger 12 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/btree/LeafNodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree; 11 | 12 | 13 | import de.rwhq.comparator.IntegerComparator; 14 | import de.rwhq.io.rm.ResourceManager; 15 | import de.rwhq.io.rm.ResourceManagerBuilder; 16 | import de.rwhq.serializer.IntegerSerializer; 17 | import org.apache.commons.logging.Log; 18 | import org.apache.commons.logging.LogFactory; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.Iterator; 25 | 26 | import static org.fest.assertions.Assertions.assertThat; 27 | 28 | public class LeafNodeTest { 29 | private static final Log LOG = LogFactory.getLog(LeafNodeTest.class); 30 | 31 | private final static File file = new File("/tmp/LeafNodeTest"); 32 | private LeafNode leaf; 33 | private LeafPageManager lpm; 34 | 35 | private Integer key1 = 1; 36 | private Integer key2 = 2; 37 | 38 | private Integer value1 = 101; 39 | private Integer value2 = 102; 40 | private ResourceManager rm; 41 | 42 | public LeafNodeTest(){ 43 | file.delete(); 44 | this.rm = new ResourceManagerBuilder().file(file).open().cacheSize(0).build(); 45 | } 46 | 47 | @Before 48 | public void setUp() throws IOException { 49 | LOG.warn("setting up leafnodetest"); 50 | rm.clear(); 51 | lpm = BTree.create(rm, IntegerSerializer.INSTANCE, IntegerSerializer.INSTANCE, IntegerComparator.INSTANCE).getLeafPageManager(); 52 | leaf = lpm.createPage(); 53 | } 54 | 55 | @Test 56 | public void shouldBeAbleToInsertAndGet() { 57 | leaf.insert(key1, value1); 58 | assertThat(leaf.containsKey(key1)).isTrue(); 59 | assertThat( leaf.getNumberOfEntries()).isEqualTo(1); 60 | assertThat( leaf.get(key1).size()).isEqualTo(1); 61 | assertThat( leaf.get(key1).get(0)).isEqualTo(value1); 62 | } 63 | 64 | @Test public void shouldBeAbleToGetLastKeyAndPointer() { 65 | leaf.insert(key1, value1); 66 | assertThat(leaf.getLastLeafKey()).isNotNull(); 67 | assertThat(leaf.getLastLeafKeySerialized()).isNotNull(); 68 | 69 | leaf.insert(key2, value2); 70 | assertThat(leaf.getLastLeafKey()).isNotNull(); 71 | assertThat(leaf.getLastLeafKeySerialized()).isNotNull(); 72 | } 73 | 74 | @Test public void shouldAlwaysWorkAfterReload() { 75 | for (int i = 0; i < 5; i++) { 76 | leaf.insert(key1, value1); 77 | } 78 | leaf.insert(key2, value2); 79 | assertThat( leaf.getNumberOfEntries()).isEqualTo(6); 80 | leaf.load(); 81 | assertThat( leaf.getNumberOfEntries()).isEqualTo(6); 82 | assertThat( leaf.get(key2).size()).isEqualTo(1); 83 | 84 | } 85 | 86 | @Test public void shouldAtSomePointReturnAValidAdjustmentAction() { 87 | AdjustmentAction action; 88 | do { 89 | action = leaf.insert(key1, value1); 90 | } while (action == null); 91 | 92 | assertThat(leaf.getLastLeafKey()).isNotNull(); 93 | assertThat( action.getAction()).isEqualTo(AdjustmentAction.ACTION.INSERT_NEW_NODE); 94 | 95 | assertThat(action.getSerializedKey()).isNotNull(); 96 | 97 | 98 | // this should still work and not throw an exception 99 | stateTest(leaf); 100 | final LeafNode newLeaf = lpm.getPage(action.getPageId()); 101 | ; 102 | stateTest(newLeaf); 103 | } 104 | 105 | private void stateTest(final LeafNode leaf) { 106 | final Integer k = leaf.getLastLeafKey(); 107 | assertThat(leaf.get(k)).isNotNull(); 108 | assertThat(leaf.containsKey(k)).isTrue(); 109 | 110 | // all keys should be accessible 111 | for (int i = 0; i < leaf.getNumberOfEntries(); i++) { 112 | final Integer key = leaf.getKeyAtPosition(i); 113 | assertThat(k).isNotNull(); 114 | assertThat(leaf.containsKey(key)).isTrue(); 115 | 116 | } 117 | assertThat( leaf.getKeyAtPosition(leaf.getNumberOfEntries() - 1)).isEqualTo(k); 118 | 119 | } 120 | 121 | @Test 122 | public void iterators() { 123 | fillLeaf(leaf, 10); 124 | 125 | Iterator iterator = leaf.getIterator(-5, 5); 126 | for (int i = 0; i <= 5; i++) 127 | assertThat( (int) iterator.next()).isEqualTo(i); 128 | assertThat(iterator.hasNext()).isFalse(); 129 | 130 | iterator = leaf.getIterator(5, 15); 131 | for (int i = 5; i < 10; i++) 132 | assertThat( (int) iterator.next()).isEqualTo(i); 133 | assertThat(iterator.hasNext()).isFalse(); 134 | 135 | iterator = leaf.getIterator(0, 9); 136 | for (int i = 0; i < 10; i++) 137 | assertThat( (int) iterator.next()).isEqualTo(i); 138 | assertThat(iterator.hasNext()).isFalse(); 139 | 140 | 141 | iterator = leaf.getIterator(5, null); 142 | for (int i = 5; i < 10; i++) 143 | assertThat( (int) iterator.next()).isEqualTo(i); 144 | assertThat(iterator.hasNext()).isFalse(); 145 | 146 | iterator = leaf.getIterator(null, 5); 147 | for (int i = 0; i <= 5; i++) 148 | assertThat( (int) iterator.next()).isEqualTo(i); 149 | assertThat(iterator.hasNext()).isFalse(); 150 | 151 | iterator = leaf.getIterator(null, null); 152 | for (int i = 0; i < 10; i++) 153 | assertThat( (int) iterator.next()).isEqualTo(i); 154 | assertThat(iterator.hasNext()).isFalse(); 155 | } 156 | 157 | @Test 158 | public void iteratorStartingInTheMiddle(){ 159 | fillLeaf(leaf, 10); 160 | Iterator iterator = leaf.getIterator(5, null); 161 | for(int i = 5; i<10;i++) 162 | assertThat(iterator.next()).isEqualTo(i); 163 | 164 | assertThat(iterator.next()).isNull(); 165 | } 166 | 167 | 168 | private void fillLeaf(final LeafNode leaf, final int count) { 169 | for (int i = 0; i < count; i++) { 170 | leaf.insert(i, i); 171 | } 172 | } 173 | 174 | @Test 175 | public void shouldContainAddedEntries() { 176 | leaf.insert(key1, value1); 177 | assertThat(leaf.containsKey(key1)).isTrue(); 178 | assertThat( leaf.get(key1).size()).isEqualTo(1); 179 | assertThat( leaf.get(key1).get(0)).isEqualTo(value1); 180 | assertThat( leaf.getNumberOfEntries()).isEqualTo(1); 181 | 182 | leaf.insert(key1, value2); 183 | assertThat(leaf.containsKey(key1)).isTrue(); 184 | assertThat( leaf.get(key1).size()).isEqualTo(2); 185 | assertThat(leaf.get(key1).contains(value1)).isTrue(); 186 | assertThat(leaf.get(key1).contains(value2)).isTrue(); 187 | assertThat( leaf.getNumberOfEntries()).isEqualTo(2); 188 | 189 | leaf.insert(key2, value2); 190 | assertThat(leaf.containsKey(key2)).isTrue(); 191 | assertThat( leaf.get(key2).size()).isEqualTo(1); 192 | assertThat(leaf.get(key1).contains(value2)).isTrue(); 193 | assertThat(leaf.get(key1).contains(value1)).isTrue(); 194 | assertThat(leaf.get(key1).size() == 2).isTrue(); 195 | assertThat( leaf.getNumberOfEntries()).isEqualTo(3); 196 | } 197 | 198 | @Test 199 | public void removeWithValueArgumentShouldRemoveOnlyThisValue() { 200 | leaf.insert(key1, value1); 201 | leaf.insert(key1, value2); 202 | leaf.insert(key2, value2); 203 | 204 | assertThat( leaf.getNumberOfEntries()).isEqualTo(3); 205 | leaf.remove(key1, value2); 206 | assertThat( leaf.get(key1).size()).isEqualTo(1); 207 | assertThat( leaf.get(key1).get(0)).isEqualTo(value1); 208 | assertThat( leaf.get(key2).get(0)).isEqualTo(value2); 209 | } 210 | 211 | 212 | @Test 213 | public void prependEntriesShouldWork() { 214 | final int leaf2Id = lpm.createPage().getId(); 215 | 216 | 217 | int totalInserted = 0; 218 | 219 | // fill leaf 220 | for (int i = 0; i < leaf.getMaximalNumberOfEntries(); i++) { 221 | assertThat(leaf.insert(i, i)).isNull(); 222 | totalInserted++; 223 | } 224 | 225 | // in testPrepend, one entry is inserted 226 | testPrepend(leaf, lpm.getPage(leaf2Id)); 227 | totalInserted++; 228 | 229 | assertThat(leaf.getNumberOfEntries() + lpm.getPage(leaf2Id).getNumberOfEntries()).isEqualTo(totalInserted); 230 | 231 | // should work again, when we have to actually move some entries in leaf2 232 | for (int i = leaf.getNumberOfEntries(); i < leaf 233 | .getMaximalNumberOfEntries(); i++) { 234 | assertThat(leaf.insert(-1 * i, i)).isNull(); 235 | totalInserted++; 236 | } 237 | 238 | testPrepend(leaf, lpm.getPage(leaf2Id)); 239 | totalInserted++; 240 | assertThat(leaf.getNumberOfEntries() + lpm.getPage(leaf2Id).getNumberOfEntries()).isEqualTo(totalInserted); 241 | 242 | } 243 | 244 | /** in testPrepend, one entry is inserted* 245 | * @param leaf1 246 | * @param leaf2*/ 247 | private void testPrepend(final LeafNode leaf1, final LeafNode leaf2) { 248 | leaf1.setNextLeafId(leaf2.getId()); 249 | 250 | // insert key so that move should happen 251 | final AdjustmentAction action = leaf1.insert(1, 1); 252 | 253 | // an update key action should be passed up 254 | assertThat(action).isNotNull(); 255 | 256 | // make sure leaf structures are in tact 257 | assertThat(leaf1.getKeyAtPosition(leaf1.getNumberOfEntries() - 1)).isEqualTo(leaf1.getLastLeafKey()); 258 | 259 | for (final int key : leaf1.getKeySet()) { 260 | assertThat(leaf1.get(key)).isNotNull(); 261 | } 262 | 263 | for (final int key : leaf2.getKeySet()) { 264 | assertThat(leaf2.get(key)).isNotNull(); 265 | } 266 | } 267 | 268 | 269 | } 270 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/btree/LeafNodeUnitTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.btree; 11 | 12 | import de.rwhq.btree.BTree.NodeType; 13 | import de.rwhq.btree.LeafNode.Header; 14 | import de.rwhq.comparator.IntegerComparator; 15 | import de.rwhq.io.rm.PageManager; 16 | import de.rwhq.io.rm.RawPage; 17 | import de.rwhq.serializer.IntegerSerializer; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.mockito.Mock; 21 | import org.mockito.MockitoAnnotations; 22 | 23 | import java.nio.ByteBuffer; 24 | 25 | import static org.fest.assertions.Assertions.assertThat; 26 | import static org.mockito.Mockito.verify; 27 | 28 | /** 29 | * @deprecated 30 | */ 31 | public class LeafNodeUnitTest { 32 | 33 | private LeafNode node; 34 | 35 | // dependencies 36 | private RawPage rawPage; 37 | private int minNumberOfValues = 3; 38 | private int rawPageSize = 34; 39 | @Mock private PageManager> leafPageManager; 40 | 41 | 42 | @Before 43 | public void setUp(){ 44 | MockitoAnnotations.initMocks(this); 45 | rawPage = new RawPage(ByteBuffer.allocate(rawPageSize), 100); 46 | final RawPage rawPage2 = new RawPage(ByteBuffer.allocate(rawPageSize), 101); 47 | node = new LeafNode(rawPage, IntegerSerializer.INSTANCE, 48 | IntegerSerializer.INSTANCE, IntegerComparator.INSTANCE, leafPageManager, minNumberOfValues); 49 | node.initialize(); 50 | } 51 | 52 | @Test 53 | public void load(){ 54 | node.insert(1, 101); 55 | node.insert(2, 201); 56 | assertThat( node.getNumberOfEntries()).isEqualTo(2); 57 | 58 | node = new LeafNode(rawPage, IntegerSerializer.INSTANCE, 59 | IntegerSerializer.INSTANCE, IntegerComparator.INSTANCE, leafPageManager, minNumberOfValues); 60 | 61 | 62 | node.load(); 63 | assertThat( node.getNumberOfEntries()).isEqualTo(2); 64 | assertThat( (int) node.getFirst(1)).isEqualTo(101); 65 | assertThat( (int) node.getFirst(2)).isEqualTo(201); 66 | 67 | } 68 | 69 | @Test 70 | public void destroy(){ 71 | node.insert(1, 101); 72 | node.destroy(); 73 | verify(leafPageManager).removePage(100); 74 | } 75 | 76 | @Test 77 | public void minNumberOfValues(){ 78 | final int tmpValues = minNumberOfValues; 79 | final int tmpSize = rawPageSize; 80 | 81 | minNumberOfValues = 1; 82 | rawPageSize = Header.size() + 2*IntegerSerializer.INSTANCE.getSerializedLength(); 83 | 84 | // this should work 85 | setUp(); 86 | 87 | // this shouldn't work 88 | try{ 89 | rawPageSize--; 90 | setUp(); 91 | throw new IllegalStateException("this shouldn't work"); 92 | } catch (Exception e) { 93 | } 94 | 95 | minNumberOfValues = 0; 96 | rawPageSize = Header.size(); 97 | 98 | // should work 99 | setUp(); 100 | 101 | // this shouldn't work 102 | try{ 103 | rawPageSize--; 104 | setUp(); 105 | throw new IllegalStateException("this shouldn't work"); 106 | } catch (Exception e) { 107 | } 108 | 109 | minNumberOfValues = 2; 110 | rawPageSize = Header.size() + 4*IntegerSerializer.INSTANCE.getSerializedLength(); 111 | 112 | // should work 113 | setUp(); 114 | 115 | // this shouldn't work 116 | try{ 117 | rawPageSize--; 118 | setUp(); 119 | throw new IllegalStateException("this shouldn't work"); 120 | } catch (Exception e) { 121 | } 122 | 123 | rawPageSize +=2; 124 | 125 | // should work 126 | setUp(); 127 | 128 | // reset values 129 | minNumberOfValues = tmpValues; 130 | rawPageSize = tmpSize; 131 | } 132 | 133 | @Test 134 | public void testInitialize(){ 135 | final ByteBuffer buf = rawPage.bufferForReading(0); 136 | assertThat( buf.getChar()).isEqualTo(NodeType.LEAF_NODE.serialize()); 137 | assertThat( buf.getInt()).isEqualTo(0); 138 | assertThat( buf.getInt()).isEqualTo((int)LeafNode.NO_NEXT_LEAF); 139 | } 140 | 141 | @Test 142 | public void firstInsert(){ 143 | node.insert(1, 101); 144 | ensureKeyValueInRawPage(rawPage, Header.size(), 1, 101); 145 | 146 | assertThat( node.get(1).size()).isEqualTo(1); 147 | assertThat( (int) node.get(1).get(0)).isEqualTo(101); 148 | } 149 | 150 | private void ensureKeyValueInRawPage(final RawPage rp, final int offset, final int key, final int value){ 151 | final ByteBuffer buf = rp.bufferForReading(offset); 152 | final byte[] bytes = new byte[IntegerSerializer.INSTANCE.getSerializedLength()]; 153 | buf.get(bytes); 154 | assertThat( (int) IntegerSerializer.INSTANCE.deserialize(bytes)).isEqualTo(key); 155 | buf.get(bytes); 156 | assertThat( (int) IntegerSerializer.INSTANCE.deserialize(bytes)).isEqualTo(value); 157 | } 158 | 159 | @Test 160 | public void secondInsert(){ 161 | firstInsert(); 162 | node.insert(10, 1001); 163 | ensureKeyValueInRawPage(rawPage, Header.size() + 2*IntegerSerializer.INSTANCE.getSerializedLength(), 10, 1001); 164 | 165 | 166 | assertThat( node.get(1).size()).isEqualTo(1); 167 | assertThat( (int) node.get(1).get(0)).isEqualTo(101); 168 | 169 | assertThat( node.get(10).size()).isEqualTo(1); 170 | assertThat( (int) node.get(10).get(0)).isEqualTo(1001); 171 | } 172 | 173 | @Test 174 | public void doubleInsert(){ 175 | secondInsert(); 176 | node.insert(1, 102); 177 | ensureKeyValueInRawPage(rawPage, Header.size(), 1, 102); 178 | 179 | assertThat( node.get(1).size()).isEqualTo(2); 180 | assertThat( (int) node.get(1).get(0)).isEqualTo(102); 181 | assertThat( (int) node.get(1).get(1)).isEqualTo(101); 182 | } 183 | 184 | @Test 185 | public void insertionInTheMiddle(){ 186 | secondInsert(); 187 | node.insert(5, 501); 188 | ensureKeyValueInRawPage(rawPage, Header.size() + 0*IntegerSerializer.INSTANCE.getSerializedLength(), 1, 101); 189 | ensureKeyValueInRawPage(rawPage, Header.size() + 2*IntegerSerializer.INSTANCE.getSerializedLength(), 5, 501); 190 | ensureKeyValueInRawPage(rawPage, Header.size() + 4* IntegerSerializer.INSTANCE.getSerializedLength(), 10, 1001); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/btree/PageManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import de.rwhq.comparator.StringComparator; 14 | import de.rwhq.io.rm.ResourceManager; 15 | import de.rwhq.io.rm.ResourceManagerBuilder; 16 | import de.rwhq.serializer.FixedStringSerializer; 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | 25 | import static org.fest.assertions.Assertions.assertThat; 26 | 27 | public class PageManagerTest { 28 | 29 | private static String path = "/tmp/PageManagerTest"; 30 | private static File file = new File(path); 31 | private ResourceManager rm; 32 | private BTree tree; 33 | private LeafPageManager lpm; 34 | private InnerNodeManager inm; 35 | 36 | @Before 37 | public void setUp() throws IOException { 38 | file.delete(); 39 | final ResourceManager manager = new ResourceManagerBuilder().file(file).cacheSize(0).build(); 40 | tree = BTree.create(manager, FixedStringSerializer.INSTANCE_1000, 41 | FixedStringSerializer.INSTANCE_1000, 42 | StringComparator.INSTANCE); 43 | lpm = tree.getLeafPageManager(); 44 | inm = tree.getInnerNodeManager(); 45 | rm = tree.getResourceManager(); 46 | if(!rm.isOpen()) 47 | rm.open(); 48 | } 49 | 50 | @Test 51 | public void pageCreations(){ 52 | final List leafs = new LinkedList(); 53 | final List inners = new LinkedList(); 54 | 55 | final int count = 10000; 56 | for(int i = 0;i" 9 | */ 10 | 11 | package de.rwhq.btree; 12 | 13 | import com.google.common.collect.Lists; 14 | import de.rwhq.comparator.IntegerComparator; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.TreeSet; 21 | 22 | import static org.fest.assertions.Assertions.assertThat; 23 | 24 | public class RangeTest { 25 | private List> list; 26 | 27 | @Before 28 | public void setUp(){ 29 | list = Lists.newArrayList(); 30 | list.add(new Range(-5, 5, IntegerComparator.INSTANCE)); 31 | list.add(new Range(0, 10)); 32 | list.add(new Range(100, 1000)); 33 | } 34 | 35 | @Test 36 | public void merge(){ 37 | TreeSet> merge = Range.merge(list, IntegerComparator.INSTANCE); 38 | assertThat(merge).hasSize(2).contains(new Range(-5, 10), new Range(100, 1000)); 39 | } 40 | 41 | @Test 42 | public void mergeNotOnlyByFrom(){ 43 | ArrayList> rangeList = Lists.newArrayList(); 44 | rangeList.add(new Range(50, 55)); 45 | rangeList.add(new Range(52, 53)); 46 | rangeList.add(new Range(49, 53)); 47 | rangeList.add(new Range(52, 56)); 48 | 49 | TreeSet> merge = Range.merge(rangeList, IntegerComparator.INSTANCE); 50 | assertThat(merge).hasSize(1).contains(new Range(49, 56)); 51 | 52 | } 53 | 54 | @Test 55 | public void mergeWithNullTo(){ 56 | list.add(new Range(500, null)); 57 | TreeSet> merge = Range.merge(list, IntegerComparator.INSTANCE); 58 | assertThat(merge).contains(new Range(100, null)); 59 | } 60 | 61 | @Test 62 | public void mergeWithNullFrom(){ 63 | list.add(new Range(null, 0)); 64 | TreeSet> merge = Range.merge(list, IntegerComparator.INSTANCE); 65 | assertThat(merge).contains(new Range(null, 10)); 66 | } 67 | 68 | @Test 69 | public void mergeWithLowerRangeLater(){ 70 | list.add(new Range(-6,-4)); 71 | Range.merge(list, IntegerComparator.INSTANCE); 72 | assertThat(list).contains(new Range(-6, 10)); 73 | } 74 | 75 | @Test 76 | public void mergeWithNulls(){ 77 | list = Lists.newArrayList(); 78 | list.add(new Range(25, null)); 79 | list.add(new Range(null, null)); 80 | list.add(new Range(null, 23)); 81 | 82 | TreeSet> merge = Range.merge(list, IntegerComparator.INSTANCE); 83 | 84 | assertThat(merge).hasSize(1).contains(new Range(null, null)); 85 | } 86 | 87 | @Test 88 | public void mergeWithDuplicates(){ 89 | list = Lists.newArrayList(); 90 | list.add(new Range(1,3)); 91 | list.add(new Range(1,3)); 92 | list.add(new Range(9,10)); 93 | 94 | TreeSet> merged = Range.merge(list, IntegerComparator.INSTANCE); 95 | assertThat(merged).hasSize(2).contains(new Range(1,3), new Range(9,10)); 96 | } 97 | 98 | @Test 99 | public void contains(){ 100 | Range range = list.get(0); 101 | 102 | assertThat(range.contains(0)).isTrue(); 103 | assertThat(range.contains(-5)).isTrue(); 104 | assertThat(range.contains(5)).isTrue(); 105 | 106 | assertThat(range.contains(6)).isFalse(); 107 | assertThat(range.contains(-6)).isFalse(); 108 | } 109 | 110 | @Test(expected = NullPointerException.class) 111 | public void containsNullShouldThrow(){ 112 | list.get(0).contains(null); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/CachedResourceManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import com.google.common.cache.Cache; 14 | import org.apache.log4j.Logger; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.Random; 21 | 22 | import static org.fest.assertions.Assertions.assertThat; 23 | 24 | 25 | public class CachedResourceManagerTest { 26 | 27 | private ResourceManager rm; 28 | private static final Logger LOG = Logger.getLogger(CachedResourceManagerTest.class); 29 | private static File file = new File("/tmp/CachedResourceManagerTest"); 30 | 31 | @Before 32 | public void setUp() throws IOException { 33 | file.delete(); 34 | rm = new ResourceManagerBuilder().file(file).cacheSize(0).cacheSize(1000).open().build(); 35 | } 36 | 37 | public static class ResourceManagerTestImpl extends ResourceManagerTest { 38 | 39 | @Override 40 | protected ResourceManager resetResourceManager() { 41 | file.delete(); 42 | return new ResourceManagerBuilder().file(file).cacheSize(0).cacheSize(1000).open().build(); 43 | } 44 | } 45 | 46 | @Test 47 | public void open() throws IOException { 48 | rm.close(); 49 | file.delete(); 50 | rm = new ResourceManagerBuilder().file(file).cacheSize(0).build(); 51 | assertThat(rm.isOpen()).isFalse(); 52 | rm.open(); 53 | assertThat(rm.isOpen()).isTrue(); 54 | } 55 | 56 | 57 | @Test 58 | public void cache() throws IOException { 59 | assertThat(rm.isOpen()).isTrue(); 60 | final int count = 10; 61 | final RawPage[] pages = new RawPage[count]; 62 | 63 | for (int i = 0; i < count; i++) { 64 | pages[i] = rm.createPage(); 65 | } 66 | 67 | final CachedResourceManager crm = (CachedResourceManager) rm; 68 | final Cache cache = crm.getCache(); 69 | 70 | final Random random = new Random(); 71 | for (int i = 0; i < count * 10; i++) { 72 | rm.getPage(pages[random.nextInt(10)].id()); 73 | } 74 | 75 | assertThat(cache.stats().hitCount()).isEqualTo(count * 10); 76 | } 77 | 78 | 79 | @Test 80 | public void testAutoSave() throws IOException { 81 | final CachedResourceManager crm = (CachedResourceManager) rm; 82 | final RawPage p = rm.createPage(); 83 | final int testInt = 5343; 84 | p.bufferForWriting(0).putInt(testInt); 85 | 86 | for (int i = 0; i < crm.getCacheSize() * 2; i++) { 87 | rm.createPage(); 88 | } 89 | 90 | assertThat(crm.getCache().asMap().containsKey(p)).isFalse(); 91 | assertThat(rm.getPage(p.id()).bufferForReading(0).getInt()).isEqualTo(testInt); 92 | } 93 | 94 | @Test 95 | public void closeShouldInvalidateCache() throws IOException { 96 | final RawPage page = rm.createPage(); 97 | rm.close(); 98 | rm.open(); 99 | assertThat(rm.getPage(page.id())).isNotSameAs(page); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/DynamicDataPageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.io.rm; 11 | 12 | import de.rwhq.io.NoSpaceException; 13 | import de.rwhq.serializer.PagePointSerializer; 14 | import de.rwhq.serializer.StringSerializer; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | import static org.fest.assertions.Assertions.assertThat; 21 | 22 | 23 | public class DynamicDataPageTest { 24 | 25 | private DynamicDataPage page; 26 | 27 | // some test strings to insert 28 | private String s1 = "blubla"; 29 | private String s2 = "blast"; 30 | private String s3 = "ups"; 31 | 32 | @Before 33 | public void setUp(){ 34 | page = new DynamicDataPage(new RawPage(ByteBuffer.allocate(PageSize.DEFAULT_PAGE_SIZE), 1), PagePointSerializer.INSTANCE, StringSerializer.INSTANCE); 35 | } 36 | 37 | @Test 38 | public void shouldHaveToInitialize(){ 39 | assertThat(page.isValid()).isFalse(); 40 | page.initialize(); 41 | assertThat(page.isValid()).isTrue(); 42 | checkAndSetModified(page); 43 | } 44 | 45 | @Test 46 | public void shouldBeEmptyAfterInitialize() throws InvalidPageException{ 47 | page.initialize(); 48 | assertThat( page.numberOfEntries()).isEqualTo(0); 49 | } 50 | 51 | private void checkAndSetModified(final DynamicDataPage page){ 52 | assertThat(page.rawPage().isModified()).isTrue(); 53 | page.rawPage().setModified(false); 54 | } 55 | 56 | 57 | @Test(expected = InvalidPageException.class) 58 | public void shouldThrowAnExceptionIfInvalidEntryId() throws Exception{ 59 | page.get(432); 60 | } 61 | 62 | @Test 63 | public void remainingShouldGetSmallerWhenInsertingSomething() throws NoSpaceException, InvalidPageException{ 64 | page.initialize(); 65 | checkAndSetModified(page); 66 | 67 | final int rest = page.remaining(); 68 | page.add("bla"); 69 | checkAndSetModified(page); 70 | assertThat(rest > page.remaining()).isTrue(); 71 | } 72 | 73 | @Test(expected = InvalidPageException.class) 74 | public void shouldThrowAnExceptionOnAddIfNotValid() throws NoSpaceException, InvalidPageException{ 75 | page.add(s1); 76 | checkAndSetModified(page); 77 | } 78 | 79 | @Test(expected = InvalidPageException.class) 80 | public void shouldThrowAnExceptionOnGetIfNotValid() throws Exception{ 81 | page.get(0); 82 | } 83 | 84 | @Test(expected = InvalidPageException.class) 85 | public void shouldThrowAnExceptionOnNumberOfEntriesIfNotValid() throws Exception{ 86 | page.numberOfEntries(); 87 | } 88 | 89 | @Test 90 | public void shouldBeAbleToReturnInsertedItems() throws Exception{ 91 | 92 | page.initialize(); 93 | 94 | assertThat( page.rawPage().bufferForReading(0).getInt()).isEqualTo(DynamicDataPage.NO_ENTRIES_INT); 95 | assertThat( page.numberOfEntries()).isEqualTo(0); 96 | 97 | final int id1 = page.add(s1); 98 | assertThat( page.rawPage().bufferForReading(0).getInt()).isEqualTo(1); 99 | assertThat( page.numberOfEntries()).isEqualTo(1); 100 | final int id2 = page.add(s2); 101 | assertThat( page.rawPage().bufferForReading(0).getInt()).isEqualTo(2); 102 | assertThat( page.numberOfEntries()).isEqualTo(2); 103 | final int id3 = page.add(s3); 104 | assertThat( page.rawPage().bufferForReading(0).getInt()).isEqualTo(3); 105 | assertThat( page.numberOfEntries()).isEqualTo(3); 106 | checkAndSetModified(page); 107 | 108 | assertThat( page.get(id1)).isEqualTo(s1); 109 | assertThat( page.get(id3)).isEqualTo(s3); 110 | 111 | page.remove(id1); 112 | assertThat( page.rawPage().bufferForReading(0).getInt()).isEqualTo(2); 113 | assertThat( page.numberOfEntries()).isEqualTo(2); 114 | checkAndSetModified(page); 115 | assertThat( page.get(id2)).isEqualTo(s2); 116 | assertThat( page.get(id3)).isEqualTo(s3); 117 | } 118 | 119 | @Test 120 | public void shouldReturnNullIfEntryWasRemoved() throws Exception { 121 | 122 | page.initialize(); 123 | 124 | final int id = page.add(s3); 125 | page.remove(id); 126 | checkAndSetModified(page); 127 | 128 | assertThat( page.get(id)).isEqualTo(null); 129 | assertThat( page.get(id + 5)).isEqualTo(null); 130 | 131 | } 132 | 133 | @Test 134 | public void remainingMethodShouldAdjustWhenInsertingOrRemovingEntries() throws NoSpaceException, InvalidPageException { 135 | page.initialize(); 136 | 137 | final int r1 = page.remaining(); 138 | final int id1 = page.add(s1); 139 | final int r2 = page.remaining(); 140 | assertThat(r1 > r2).isTrue(); 141 | final int id2 = page.add(s2); 142 | final int r3 = page.remaining(); 143 | assertThat(r2 > r3).isTrue(); 144 | page.remove(id1); 145 | assertThat( page.remaining()).isEqualTo(r1 - (r2 - r3)); 146 | page.remove(id2); 147 | assertThat( page.remaining()).isEqualTo(r1); 148 | } 149 | 150 | @Test public void remainingValueShouldBeCorrectAfterReload() throws NoSpaceException, InvalidPageException{ 151 | page.initialize(); 152 | 153 | page.add(s1); 154 | page.add(s2); 155 | final int r = page.remaining(); 156 | page = new DynamicDataPage(page.rawPage(), page.pagePointSerializer(), page.dataSerializer()); 157 | page.load(); 158 | assertThat( page.remaining()).isEqualTo(r); 159 | } 160 | 161 | @Test 162 | public void shouldHaveSameSizeAfterInsertAndRemove() throws NoSpaceException, InvalidPageException { 163 | page.initialize(); 164 | 165 | final int remaining = page.remaining(); 166 | final int id = page.add("blast"); 167 | assertThat(remaining > page.remaining()).isTrue(); 168 | page.remove(id); 169 | assertThat( page.remaining()).isEqualTo(remaining); 170 | } 171 | 172 | @Test 173 | public void shouldLoadCorrectly() throws Exception{ 174 | assertThat(page.isValid()).isFalse(); 175 | page.initialize(); 176 | assertThat(page.isValid()).isTrue(); 177 | final int id = page.add(s1); 178 | assertThat( page.numberOfEntries()).isEqualTo(1); 179 | page = new DynamicDataPage(page.rawPage(), page.pagePointSerializer(), page.dataSerializer()); 180 | assertThat(page.isValid()).isFalse(); 181 | page.load(); 182 | assertThat(page.isValid()).isTrue(); 183 | assertThat( page.numberOfEntries()).isEqualTo(1); 184 | assertThat( page.get(id)).isEqualTo(s1); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/FileResourceManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.RandomAccessFile; 20 | import java.nio.ByteBuffer; 21 | 22 | import static org.fest.assertions.Assertions.assertThat; 23 | import static org.junit.Assert.fail; 24 | 25 | public class FileResourceManagerTest { 26 | 27 | private final static String filePath = "/tmp/frm_test"; 28 | private final static File file = new File(filePath); 29 | 30 | 31 | static FileResourceManager createNewOpenResourceManager() { 32 | if (file.exists()) { 33 | file.delete(); 34 | } 35 | 36 | return createOpenResourceManager(); 37 | } 38 | 39 | static FileResourceManager createOpenResourceManager() { 40 | FileResourceManager rm = (FileResourceManager) new ResourceManagerBuilder().file(file).cacheSize(0).build(); 41 | 42 | try { 43 | rm.open(); 44 | } catch (IOException e) { 45 | throw new RuntimeException(e); 46 | } 47 | 48 | return rm; 49 | } 50 | private FileResourceManager rm; 51 | 52 | @Before 53 | public void setUp() throws IOException { 54 | rm = createNewOpenResourceManager(); 55 | } 56 | 57 | @After 58 | public void tearDown() throws IOException { 59 | rm.close(); 60 | } 61 | 62 | @Test 63 | public void shouldWriteOutHeaderCorrectly() throws IOException { 64 | rm = createNewOpenResourceManager(); 65 | rm.createPage(); 66 | rm.close(); 67 | 68 | final RandomAccessFile rFile = new RandomAccessFile(file, "rw"); 69 | rFile.seek(ResourceHeader.Header.PAGE_SIZE.offset); 70 | assertThat(rFile.readInt()).isEqualTo(PageSize.DEFAULT_PAGE_SIZE); 71 | rFile.close(); 72 | } 73 | 74 | @Test(expected = IOException.class) 75 | public void shouldThrowExceptionIfFileIsLocked() throws IOException { 76 | rm = (FileResourceManager) new ResourceManagerBuilder().file(file).cacheSize(0).build(); 77 | rm.open(); 78 | final FileResourceManager rm2 = 79 | (FileResourceManager) new ResourceManagerBuilder().file(file).cacheSize(0).build(); 80 | rm2.open(); 81 | fail("FileResourceManager should throw an IOException if the file is already locked"); 82 | } 83 | 84 | // ******** TESTS ********** 85 | 86 | @Test(expected = IllegalStateException.class) 87 | public void shouldThrowExceptionIfResourceClosed() throws IOException { 88 | rm.close(); 89 | rm.createPage(); 90 | } 91 | 92 | @Test(expected = PageNotFoundException.class) 93 | public void shouldThrowExceptionIfPageToWriteDoesNotExist() throws IOException { 94 | final RawPage page = new RawPage(ByteBuffer.allocate(PageSize.DEFAULT_PAGE_SIZE), 3423); 95 | rm.writePage(page); 96 | } 97 | 98 | @Test 99 | public void shouldGenerateDifferentIdsForEachPage() throws IOException { 100 | assertThat(rm.createPage().id() != rm.createPage().id()).isTrue(); 101 | } 102 | 103 | @Test 104 | public void shouldReadWrittenPages() throws IOException { 105 | final RawPage page = rm.createPage(); 106 | page.bufferForWriting(0).putInt(1234); 107 | rm.writePage(page); 108 | 109 | assertThat(page.bufferForWriting(0)).isEqualTo(rm.getPage(page.id()).bufferForWriting(0)); 110 | } 111 | 112 | @Test 113 | public void addingAPageShouldIncreaseNumberOfPages() throws IOException { 114 | final int num = rm.numberOfPages(); 115 | rm.createPage(); 116 | assertThat(rm.numberOfPages()).isEqualTo(num + 1); 117 | } 118 | 119 | @Test 120 | public void shouldBeAbleToReadPagesAfterReopen() throws IOException { 121 | assertThat(rm.numberOfPages()).isEqualTo(0); 122 | final RawPage page = rm.createPage(); 123 | assertThat(rm.numberOfPages()).isEqualTo(1); 124 | rm.createPage(); 125 | assertThat(rm.numberOfPages()).isEqualTo(2); 126 | 127 | final long longToCompare = 12345L; 128 | final ByteBuffer buf = page.bufferForWriting(0); 129 | buf.putLong(longToCompare); 130 | rm.writePage(page); 131 | 132 | assertThat(rm.numberOfPages()).isEqualTo(2); 133 | assertThat(rm.getPage(page.id()).bufferForWriting(0).getLong()).isEqualTo(longToCompare); 134 | 135 | rm.close(); 136 | 137 | // throw away all local variables 138 | rm = createOpenResourceManager(); 139 | 140 | assertThat(rm.numberOfPages()).isEqualTo(2); 141 | assertThat(rm.getPage(page.id()).bufferForWriting(0).getLong()).isEqualTo(longToCompare); 142 | } 143 | 144 | @Test 145 | public void ensureNoHeapOverflowExeptionIsThrown() throws IOException { 146 | final int count = 100000; 147 | for (int i = 0; i < count; i++) { 148 | rm.createPage(); 149 | } 150 | rm.close(); 151 | assertThat(rm.getFile().getTotalSpace() > count * PageSize.DEFAULT_PAGE_SIZE).isTrue(); 152 | } 153 | 154 | @Test(expected = IllegalStateException.class) 155 | public void detectExternalFileDelete() { 156 | file.delete(); 157 | rm.createPage(); 158 | } 159 | 160 | @Test 161 | public void clear() { 162 | final RawPage page1 = rm.createPage(); 163 | page1.bufferForWriting(0).putInt(0); 164 | page1.sync(); 165 | assertThat(rm.getFile().length()).isEqualTo(2 * rm.getPageSize()); 166 | 167 | rm.clear(); 168 | assertThat(rm.getFile().length()).isEqualTo(rm.getPageSize()); 169 | } 170 | 171 | public static class ResourceManagerTestImpl extends ResourceManagerTest { 172 | 173 | @Override 174 | protected ResourceManager resetResourceManager() { 175 | return createNewOpenResourceManager(); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/ReferenceCachedResourceManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import org.apache.log4j.Logger; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | 20 | import static org.fest.assertions.Assertions.assertThat; 21 | 22 | public class ReferenceCachedResourceManagerTest { 23 | private ResourceManager rm; 24 | private static final Logger LOG = Logger.getLogger(ReferenceCachedResourceManagerTest.class); 25 | private static File file = new File("/tmp/ReferenceCachedResourceManagerTest"); 26 | 27 | @Before 28 | public void setUp() throws IOException { 29 | file.delete(); 30 | rm = new ResourceManagerBuilder().file(file).cacheSize(0).useReferenceCache(true).open().build(); 31 | } 32 | 33 | public static class ResourceManagerTestImpl extends ResourceManagerTest { 34 | 35 | @Override 36 | protected ResourceManager resetResourceManager() { 37 | file.delete(); 38 | return new ResourceManagerBuilder().file(file).cacheSize(0).useReferenceCache(true).open().build(); 39 | } 40 | } 41 | 42 | @Test 43 | public void instanceOfReferenceCachedResourceManager(){ 44 | assertThat(rm).isInstanceOf(ReferenceCachedResourceManager.class); 45 | } 46 | 47 | 48 | @Test 49 | public void shouldReturnSameInstanceIfAlreadyInMemory(){ 50 | final RawPage page = rm.createPage(); 51 | 52 | // invalidate cache 53 | rm.createPage(); 54 | rm.createPage(); 55 | 56 | assertThat(rm.getPage(page.id())).isSameAs(page); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/ResourceHeaderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import com.google.common.collect.Lists; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.mockito.Mock; 17 | import org.mockito.MockitoAnnotations; 18 | 19 | import java.io.IOException; 20 | import java.nio.ByteBuffer; 21 | import java.util.ArrayList; 22 | 23 | import static org.fest.assertions.Assertions.assertThat; 24 | import static org.mockito.Mockito.*; 25 | 26 | public class ResourceHeaderTest { 27 | @Mock private ResourceManager rm; 28 | private int pageSize = 50; 29 | private ResourceHeader header; 30 | private ArrayList pages; 31 | 32 | @Before 33 | public void setUp() { 34 | pages = Lists.newArrayList(); 35 | 36 | MockitoAnnotations.initMocks(this); 37 | 38 | when(rm.createPage()).thenReturn(newPage()); 39 | 40 | this.header = new ResourceHeader(rm, 50); 41 | } 42 | 43 | private RawPage newPage() { 44 | pages.add(new RawPage(ByteBuffer.allocate(pageSize), pages.size(), rm)); 45 | return pages.get(pages.size() - 1); 46 | } 47 | 48 | @Test 49 | public void initialize() { 50 | header.initialize(); 51 | verify(rm).createPage(); 52 | verify(rm).writePage(pages.get(0)); 53 | verifyNoMoreInteractions(rm); 54 | 55 | RawPage firstPage = pages.get(0); 56 | assertThat(firstPage.bufferForReading(ResourceHeader.Header.FREE_PAGES_NUM.offset).getInt()).isEqualTo(0); 57 | assertThat(firstPage.bufferForReading(ResourceHeader.Header.FREE_PAGES_TOTAL.offset).getInt()).isEqualTo(0); 58 | assertThat(firstPage.bufferForReading(ResourceHeader.Header.LAST_ID.offset).getInt()).isEqualTo(0); 59 | assertThat(firstPage.bufferForReading(ResourceHeader.Header.PAGE_SIZE.offset).getInt()).isEqualTo(pageSize); 60 | assertThat(firstPage.bufferForReading(ResourceHeader.Header.NEXT_PAGE.offset).getInt()).isEqualTo(0); 61 | 62 | // for other tests depending on this one 63 | reset(rm); 64 | } 65 | 66 | @Test 67 | public void load() throws IOException { 68 | initialize(); 69 | 70 | ResourceHeader header2 = new ResourceHeader(rm, null); 71 | when(rm.getPage(0)).thenReturn(pages.get(0)); 72 | header2.load(); 73 | verify(rm).getPage(0); 74 | verifyNoMoreInteractions(rm); 75 | 76 | assertThat(header2.getNumberOfPages()).isEqualTo(0); 77 | assertThat(header2.getPageSize()).isEqualTo(pageSize); 78 | } 79 | 80 | @Test 81 | public void generateId(){ 82 | initialize(); 83 | 84 | header.generateId(); 85 | assertThat(pages.get(0).bufferForReading(ResourceHeader.Header.LAST_ID.offset).getInt()).isEqualTo(1); 86 | assertThat(header.getNumberOfPages()).isEqualTo(1); 87 | 88 | verify(rm).writePage(pages.get(0)); 89 | verifyNoMoreInteractions(rm); 90 | } 91 | 92 | @Test 93 | public void removeWithoutOverflow(){ 94 | initialize(); 95 | 96 | int[] ids = new int[]{header.generateId(), header.generateId(), header.generateId()}; 97 | 98 | assertThat(header.getNumberOfPages()).isEqualTo(3); 99 | assertThat(header.contains(ids[1])).isTrue(); 100 | 101 | header.removePage(ids[1]); 102 | assertThat(header.getNumberOfPages()).isEqualTo(2); 103 | assertThat(header.contains(ids[1])).isFalse(); 104 | 105 | assertThat(header.generateId()).isEqualTo(ids[1]); 106 | assertThat(header.getNumberOfPages()).isEqualTo(3); 107 | assertThat(header.contains(ids[1])).isTrue(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/ResourceManagerBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.io.File; 17 | 18 | import static org.fest.assertions.Assertions.assertThat; 19 | 20 | public class ResourceManagerBuilderTest { 21 | private ResourceManagerBuilder builder; 22 | 23 | @Before 24 | public void setUp(){ 25 | builder = new ResourceManagerBuilder(); 26 | } 27 | 28 | @Test(expected = NullPointerException.class) 29 | public void buildShouldRequireAFile(){ 30 | builder.build(); 31 | } 32 | 33 | @Test 34 | public void buildShouldOnlyRequireAFile(){ 35 | final ResourceManager rm = setFile().build(); 36 | assertThat(rm).isNotNull(); 37 | } 38 | 39 | @Test 40 | public void withoutCache(){ 41 | assertThat(setFile().cacheSize(0).build() instanceof FileResourceManager).isTrue(); 42 | } 43 | 44 | private ResourceManagerBuilder setFile(){ 45 | return builder.file(new File("/tmp/ResourceManagerBuilderTest")); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/io/rm/ResourceManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.io.rm; 12 | 13 | import org.apache.log4j.Logger; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import java.io.IOException; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Random; 21 | 22 | import static org.fest.assertions.Assertions.assertThat; 23 | import static org.fest.assertions.Fail.fail; 24 | 25 | public abstract class ResourceManagerTest { 26 | private ResourceManager rm; 27 | private static Logger LOG = Logger.getLogger(ResourceManagerTest.class); 28 | 29 | protected abstract ResourceManager resetResourceManager(); 30 | 31 | @Before 32 | public void setUpResourceManagerTest() throws IOException { 33 | rm = resetResourceManager(); 34 | if (!rm.isOpen()) 35 | rm.open(); 36 | 37 | rm.clear(); 38 | } 39 | 40 | @Test 41 | public void shouldBeEmptyAtFirst() throws IOException { 42 | assertThat(rm.numberOfPages()).isZero(); 43 | assertThat(rm.getPageSize()).isEqualTo(PageSize.DEFAULT_PAGE_SIZE); 44 | } 45 | 46 | 47 | @Test 48 | public void performance() { 49 | LOG.info("ResourceManager: " + rm); 50 | final int count = 10000; 51 | final int[] ids = new int[count]; 52 | 53 | final Random rand = new Random(); 54 | 55 | final long createStart = System.currentTimeMillis(); 56 | // create ids 57 | for (int i = 0; i < count; i++) { 58 | ids[i] = rm.createPage().id(); 59 | } 60 | final long createEnd = System.currentTimeMillis(); 61 | LOG.info("Time for creating " + count + " ids (in ms): " + (createEnd - createStart)); 62 | 63 | // randomly write to ids 64 | final long writeStart = System.currentTimeMillis(); 65 | for (int i = 0; i < count; i++) { 66 | final int index = rand.nextInt(count); 67 | RawPage page = rm.getPage(ids[index]); 68 | page.bufferForWriting(0).putInt(i); 69 | page.sync(); 70 | } 71 | final long writeEnd = System.currentTimeMillis(); 72 | LOG.info("Time for reading and writing randomly " + count + " ids (in ms): " + (writeEnd - writeStart)); 73 | LOG.info("ResourceManager: " + rm); 74 | } 75 | 76 | @Test 77 | public void shouldBeAbleToCreateAMassiveNumberOfPages() { 78 | final List ids = new ArrayList(); 79 | 80 | final RawPage p1 = rm.createPage(); 81 | p1.bufferForWriting(0).putInt(111); 82 | p1.sync(); 83 | 84 | final int size = 10000; 85 | for (int i = 0; i < size; i++) { 86 | ids.add(rm.createPage().id()); 87 | } 88 | 89 | final RawPage p2 = rm.createPage(); 90 | p2.bufferForWriting(0).putInt(222); 91 | p2.sync(); 92 | 93 | assertThat( rm.getPage(p1.id()).bufferForReading(0).getInt()).isEqualTo(111); 94 | assertThat( rm.getPage(p2.id()).bufferForReading(0).getInt()).isEqualTo(222); 95 | 96 | assertThat( rm.numberOfPages()).isEqualTo(size + 2); 97 | for (int i = 0; i < size; i++) { 98 | final Integer id = ids.get(0); 99 | assertThat( rm.getPage(id).id()).isEqualTo(id); 100 | } 101 | } 102 | 103 | @Test 104 | public void toStringShouldAlwaysWork() throws IOException { 105 | if (!rm.isOpen()) 106 | rm.open(); 107 | 108 | assertThat(rm.toString()).isNotNull(); 109 | rm.close(); 110 | assertThat(rm.toString()).isNotNull(); 111 | } 112 | 113 | @Test 114 | public void openAndClose() throws IOException, InterruptedException { 115 | assertThat(rm.isOpen()).isTrue(); 116 | rm.close(); 117 | assertThat(rm.isOpen()).isFalse(); 118 | Thread.sleep(500); 119 | rm.open(); 120 | assertThat(rm.isOpen()).isTrue(); 121 | } 122 | 123 | @Test 124 | public void shouldBeAbleToRemovePages() throws Exception { 125 | final RawPage p1 = rm.createPage(); 126 | final int i = rm.numberOfPages(); 127 | final Integer p1Id = p1.id(); 128 | 129 | rm.removePage(p1Id); 130 | assertThat(rm.numberOfPages()).isEqualTo(i - 1); 131 | try { 132 | rm.getPage(p1Id); 133 | fail("reading a non-existent page should throw an exeption"); 134 | } catch (Exception expected) { 135 | } 136 | 137 | final RawPage p3 = rm.createPage(); 138 | assertThat( rm.numberOfPages()).isEqualTo(i); 139 | rm.removePage(p3.id()); 140 | assertThat(rm.numberOfPages()).isEqualTo(i - 1); 141 | } 142 | } -------------------------------------------------------------------------------- /src/test/java/de/rwhq/serializer/FixedStringSerializerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.serializer; 11 | 12 | import org.junit.Test; 13 | 14 | import static org.fest.assertions.Assertions.assertThat; 15 | 16 | public class FixedStringSerializerTest { 17 | @Test 18 | public void result(){ 19 | final String s = "bla"; 20 | final byte[] bytes = FixedStringSerializer.INSTANCE_1000.serialize(s); 21 | assertThat( bytes.length).isEqualTo(FixedStringSerializer.INSTANCE_1000.getSerializedLength()); 22 | assertThat( FixedStringSerializer.INSTANCE_1000.deserialize(bytes)).isEqualTo(s); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/serializer/PagePointerSerializerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | package de.rwhq.serializer; 11 | 12 | import de.rwhq.io.rm.PagePointer; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import static org.fest.assertions.Assertions.assertThat; 17 | 18 | public class PagePointerSerializerTest { 19 | 20 | private PagePointer p1, p2; 21 | private FixLengthSerializer serializer; 22 | 23 | @Before 24 | public void setUp(){ 25 | p1 = new PagePointer(5, 10); 26 | p2 = new PagePointer(50, 100); 27 | serializer = PagePointSerializer.INSTANCE; 28 | } 29 | 30 | @Test 31 | public void testSerializer(){ 32 | final byte[] b1 = serializer.serialize(p1); 33 | final byte[] b2 = serializer.serialize(p2); 34 | 35 | assert b1 != b2; 36 | assertThat( b1.length).isEqualTo(serializer.getSerializedLength()); 37 | assertThat( b2.length).isEqualTo(serializer.getSerializedLength()); 38 | assertThat( serializer.deserialize(b1)).isEqualTo(p1); 39 | assertThat( serializer.deserialize(b2)).isEqualTo(p2); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/de/rwhq/serializer/StringCutSerializerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License: 3 | * 4 | * http://creativecommons.org/licenses/by-nc/3.0/ 5 | * 6 | * For alternative conditions contact the author. 7 | * 8 | * Copyright (c) 2011 "Robin Wenglewski " 9 | */ 10 | 11 | package de.rwhq.serializer; 12 | 13 | import org.junit.Test; 14 | 15 | import static org.fest.assertions.Assertions.assertThat; 16 | 17 | public class StringCutSerializerTest { 18 | private static final StringCutSerializer serializer; 19 | private static final String testStr = "test123"; 20 | 21 | static { 22 | serializer = StringCutSerializer.get(10); 23 | } 24 | 25 | @Test 26 | public void shouldBeSingleton() { 27 | assertThat( StringCutSerializer.get(10)).isSameAs(serializer); 28 | assertThat( StringCutSerializer.get(11)).isNotSameAs(serializer); 29 | 30 | } 31 | 32 | @Test 33 | public void serializeAcceptableStringsShouldWork() { 34 | assertThat( serializer.deserialize(serializer.serialize(testStr))).isEqualTo(testStr); 35 | } 36 | 37 | @Test 38 | public void fillCompletely() { 39 | final String filled = "12345678"; 40 | assertThat( serializer.deserialize(serializer.serialize(filled))).isEqualTo(filled); 41 | } 42 | 43 | @Test 44 | public void tooLongIsCut() { 45 | final String tooLong = "123456789000"; 46 | assertThat( serializer.deserialize(serializer.serialize(tooLong))).isEqualTo("12345678"); 47 | } 48 | 49 | @Test 50 | public void emptyString() { 51 | final String empty = ""; 52 | assertThat( serializer.deserialize(serializer.serialize(empty))).isEqualTo(empty); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------