1 /*- 2 * Copyright (C) 2006-2009 Erik Larsson 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package org.catacombae.hfs; 19 20 import java.util.LinkedList; 21 import java.util.List; 22 import org.catacombae.hfs.types.hfscommon.CommonBTHeaderNode; 23 import org.catacombae.hfs.types.hfscommon.CommonBTHeaderRecord; 24 import org.catacombae.hfs.types.hfscommon.CommonBTIndexRecord; 25 import org.catacombae.hfs.types.hfscommon.CommonBTKey; 26 import org.catacombae.hfs.types.hfscommon.CommonBTKeyedNode; 27 import org.catacombae.hfs.types.hfscommon.CommonBTKeyedRecord; 28 import org.catacombae.hfs.types.hfscommon.CommonBTLeafRecord; 29 import org.catacombae.hfs.types.hfscommon.CommonBTNode; 30 import org.catacombae.hfs.types.hfscommon.CommonBTNodeDescriptor; 31 import org.catacombae.hfs.types.hfscommon.CommonBTNodeDescriptor.NodeType; 32 import org.catacombae.hfs.types.hfscommon.CommonHFSVolumeHeader; 33 import org.catacombae.io.Readable; 34 import org.catacombae.io.ReadableRandomAccessStream; 35 import org.catacombae.io.RuntimeIOException; 36 37 /** 38 * @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a> 39 */ 40 public abstract class BTreeFile<K extends CommonBTKey<K>, 41 L extends CommonBTLeafRecord<K>> 42 { 43 final HFSVolume vol; 44 BTreeFile(HFSVolume vol)45 BTreeFile(HFSVolume vol) { 46 this.vol = vol; 47 } 48 49 abstract class BTreeFileSession { 50 final CommonHFSVolumeHeader header; 51 final CommonBTNodeDescriptor btnd; 52 final CommonBTHeaderRecord bthr; 53 final ReadableRandomAccessStream btreeStream; 54 BTreeFileSession()55 public BTreeFileSession() { 56 this.header = vol.getVolumeHeader(); 57 //header.print(System.err, " "); 58 this.btreeStream = getBTreeStream(header); 59 60 this.btreeStream.seek(0); 61 62 this.btnd = readNodeDescriptor(this.btreeStream); 63 //this.btnd.print(System.err, " "); 64 if(btnd.getNodeType() != NodeType.HEADER) { 65 throw new RuntimeIOException("Invalid node type for header " + 66 "node."); 67 } 68 69 this.bthr = readHeaderRecord(this.btreeStream); 70 //this.bthr.print(System.err, " "); 71 } 72 close()73 public final void close() { 74 this.btreeStream.close(); 75 } 76 getBTreeStream( CommonHFSVolumeHeader header)77 protected abstract ReadableRandomAccessStream getBTreeStream( 78 CommonHFSVolumeHeader header); 79 } 80 findLEKey( CommonBTKeyedNode<R> indexNode, K searchKey)81 protected <R extends CommonBTKeyedRecord<K>> R findLEKey( 82 CommonBTKeyedNode<R> indexNode, K searchKey) 83 { 84 /* 85 * Algorithm: 86 * input: Key searchKey 87 * variables: Key greatestMatchingKey 88 * For each n : records 89 * If n.key <= searchKey && n.key > greatestMatchingKey 90 * greatestMatchingKey = n.key 91 */ 92 R largestMatchingRecord = null; 93 94 //System.err.println("findLEKey(): Entering loop..."); 95 for(R record : indexNode.getBTKeyedRecords()) { 96 K recordKey = record.getKey(); 97 98 //System.err.print("findLEKey(): Processing record"); 99 //if(recordKey instanceof CommonHFSExtentKey) 100 // System.err.print(" with key " + getDebugString((CommonHFSExtentKey)recordKey)); 101 //System.err.print("..."); 102 103 if(recordKey.compareTo(searchKey) <= 0 && 104 (largestMatchingRecord == null || 105 recordKey.compareTo(largestMatchingRecord.getKey()) > 0)) { 106 107 largestMatchingRecord = record; 108 //System.err.print("match!"); 109 } 110 //else 111 // System.err.print("no match."); 112 //System.err.println(); 113 } 114 115 //System.err.println("findLEKey(): Returning..."); 116 return largestMatchingRecord; 117 } 118 119 /** 120 * Find records with keys <code>k</code> in the range 121 * <code>minKeyInclusive</code> <= <code>k</code> < 122 * <code>maxKeyExclusive</code> that exist in <code>keyedNode</code>.<br> 123 * 124 * If no matching records are found, then the record with the largest key 125 * that is less than <code>minKeyInclusive</code> (if any such record 126 * exists) is returned in <code>result</code>. If no such record exists, 127 * nothing is added to <code>result</code>. 128 * 129 * @param <R> The type of the records that we operate on. 130 * 131 * @param keyedNode 132 * <b>(in)</b> The keyed node to search. 133 * @param minKeyInclusive 134 * <b>(in)</b> The smallest key in the range (inclusive). 135 * @param maxKeyExclusive 136 * <b>(in)</b> The largest key in the range (exclusive). 137 * @param strict 138 * <b>(in)</b> If <code>false</code>, then the record before the first 139 * match is always included in the result. This is appropriate when 140 * searching index nodes, but not for leaf nodes. 141 * 142 * @return 143 * A {@link java.util.List} of records. 144 */ findLEKeys( CommonBTKeyedNode<R> keyedNode, K minKeyInclusive, K maxKeyExclusive, boolean strict)145 protected <R extends CommonBTKeyedRecord<K>> List<R> findLEKeys( 146 CommonBTKeyedNode<R> keyedNode, K minKeyInclusive, 147 K maxKeyExclusive, boolean strict) 148 { 149 final LinkedList<R> result = new LinkedList<R>(); 150 151 findLEKeys(keyedNode, minKeyInclusive, maxKeyExclusive, strict, result); 152 153 return result; 154 } 155 156 /** 157 * Find records with keys <code>k</code> in the range 158 * <code>minKeyInclusive</code> <= <code>k</code> < 159 * <code>maxKeyExclusive</code>) that exist in <code>keyedNode</code>.<br> 160 * 161 * If no matching records are found, then the record with the largest key 162 * that is less than <code>minKeyInclusive</code> (if any such record 163 * exists) is returned in <code>result</code> and the function returns 164 * <code>false</code>. If no such record exists, nothing is added to 165 * <code>result</code> (and <code>false</code> is still returned). 166 * 167 * @param <R> The type of the records that we operate on. 168 * 169 * @param keyedNode 170 * <b>(in)</b> The keyed node to search. 171 * @param minKeyInclusive 172 * <b>(in)</b> The smallest key in the range (inclusive). 173 * @param maxKeyExclusive 174 * <b>(in)</b> The largest key in the range (exclusive). 175 * @param strict 176 * <b>(in)</b> If <code>false</code>, then the record before the first 177 * match is always included in the result. This is appropriate when 178 * searching index nodes, but not for leaf nodes. 179 * @param result 180 * <b>(out)</b> A {@link java.util.LinkedList} that will receive the 181 * matching keys. 182 * 183 * @return 184 * <code>true</code> if at least one key matching the specified 185 * conditions was found, and <code>false</code> otherwise. 186 */ findLEKeys( CommonBTKeyedNode<R> keyedNode, K minKeyInclusive, K maxKeyExclusive, boolean strict, LinkedList<R> result)187 protected <R extends CommonBTKeyedRecord<K>> boolean findLEKeys( 188 CommonBTKeyedNode<R> keyedNode, K minKeyInclusive, 189 K maxKeyExclusive, boolean strict, LinkedList<R> result) 190 { 191 boolean found = false; 192 K largestLEKey = null; 193 R largestLERecord = null; 194 195 /* TODO: Iteration could be optimized to binary search since keys are 196 * (supposed to be) ordered. */ 197 for(R record : keyedNode.getBTKeyedRecords()) { 198 K key = record.getKey(); 199 200 if(key.compareTo(minKeyInclusive) < 0) { 201 if(largestLEKey == null || 202 key.compareTo(largestLEKey) > 0) 203 { 204 largestLEKey = key; 205 largestLERecord = record; 206 } 207 } 208 else if(key.compareTo(maxKeyExclusive) < 0) { 209 if(result != null) { 210 result.addLast(record); 211 } 212 213 found = true; 214 } 215 } 216 217 if(largestLEKey != null && (!found || !strict)) { 218 if(result != null) { 219 result.addFirst(largestLERecord); 220 } 221 } 222 223 return found; 224 } 225 createCommonBTHeaderNode(byte[] currentNodeData, int offset, int nodeSize)226 protected CommonBTHeaderNode createCommonBTHeaderNode(byte[] currentNodeData, 227 int offset, int nodeSize) { 228 return vol.createCommonBTHeaderNode(currentNodeData, offset, nodeSize); 229 } 230 231 protected abstract CommonBTKeyedNode<? extends CommonBTIndexRecord<K>> createIndexNode(byte[] nodeData, int offset, int nodeSize)232 createIndexNode(byte[] nodeData, int offset, int nodeSize); 233 createLeafNode(byte[] nodeData, int offset, int nodeSize)234 protected abstract CommonBTKeyedNode<L> createLeafNode(byte[] nodeData, 235 int offset, int nodeSize); 236 readNodeDescriptor(Readable rd)237 protected CommonBTNodeDescriptor readNodeDescriptor(Readable rd) { 238 return vol.readNodeDescriptor(rd); 239 } 240 readHeaderRecord(Readable rd)241 protected CommonBTHeaderRecord readHeaderRecord(Readable rd) { 242 return vol.readHeaderRecord(rd); 243 } 244 createCommonBTNodeDescriptor( byte[] currentNodeData, int offset)245 protected CommonBTNodeDescriptor createCommonBTNodeDescriptor( 246 byte[] currentNodeData, int offset) { 247 return vol.createCommonBTNodeDescriptor(currentNodeData, offset); 248 } 249 getVolume()250 public HFSVolume getVolume() { 251 return vol; 252 } 253 openSession()254 protected abstract BTreeFileSession openSession(); 255 256 /** 257 * Returns the root node of the B-tree file. If it does not exist 258 * <code>null</code> is returned. The B-tree file will have no meaningful 259 * content if there is no root node. 260 * 261 * @return the B-tree root node of the B-tree file. 262 */ getRootNode()263 public CommonBTNode getRootNode() { 264 BTreeFileSession ses = openSession(); 265 266 try { 267 long rootNode = ses.bthr.getRootNodeNumber(); 268 269 if(rootNode == 0) { 270 // There is no index node, or other content. So the node we 271 // seek does not exist. Return null. 272 return null; 273 } 274 else if(rootNode < 0 || rootNode > Integer.MAX_VALUE * 2L) { 275 throw new RuntimeException("Internal error - rootNode out of " + 276 "range: " + rootNode); 277 } 278 else { 279 return getNode(rootNode); 280 } 281 } finally { 282 ses.close(); 283 } 284 } 285 getRootNodeNumber()286 public long getRootNodeNumber() { 287 BTreeFileSession ses = openSession(); 288 289 try { 290 long rootNodeNumber = ses.bthr.getRootNodeNumber(); 291 return rootNodeNumber; 292 } finally { 293 ses.close(); 294 } 295 } 296 297 /** 298 * Returns the requested node in the B-tree file. If the requested node is 299 * not a header, index or leaf node, <code>null</code> is returned because 300 * they are the only ones that are implemented at the moment.<br> 301 * 302 * @param nodeNumber the node number of the requested node. 303 * @return the requested node if it exists and has type header, index node 304 * or leaf node, or <code>null</code> otherwise. 305 */ getNode(long nodeNumber)306 public CommonBTNode getNode(long nodeNumber) { 307 BTreeFileSession ses = openSession(); 308 309 try { 310 final String METHOD = "getNode"; 311 final int nodeSize = ses.bthr.getNodeSize(); 312 313 byte[] nodeData = new byte[nodeSize]; 314 try { 315 ses.btreeStream.seek(nodeNumber * nodeSize); 316 ses.btreeStream.readFully(nodeData); 317 } catch(RuntimeException e) { 318 System.err.println("RuntimeException in " + METHOD + ". " + 319 "Printing additional information:"); 320 System.err.println(" nodeNumber=" + nodeNumber); 321 System.err.println(" nodeSize=" + nodeSize); 322 System.err.println(" init.btreeStream.length()=" + 323 ses.btreeStream.length()); 324 System.err.println(" (currentNodeNumber * nodeSize)=" + 325 (nodeNumber * nodeSize)); 326 throw e; 327 } 328 329 CommonBTNodeDescriptor nodeDescriptor = 330 createCommonBTNodeDescriptor(nodeData, 0); 331 332 if(nodeDescriptor.getNodeType() == NodeType.HEADER) 333 return createCommonBTHeaderNode(nodeData, 0, nodeSize); 334 else if(nodeDescriptor.getNodeType() == NodeType.INDEX) 335 return createIndexNode(nodeData, 0, nodeSize); 336 else if(nodeDescriptor.getNodeType() == NodeType.LEAF) 337 return createLeafNode(nodeData, 0, nodeSize); 338 else 339 return null; 340 } finally { 341 ses.close(); 342 } 343 } 344 345 /** 346 * Get a record from the B* tree with the specified key.<br> 347 * 348 * If none is found, the method returns <code>null</code>.<br> 349 * Tis method should execute in <code>O(log n)</code> time, where 350 * <code>n</code> is the number of elements in the tree. 351 * 352 * @param searchKey the key of the record that we are looking for. 353 * 354 * @return the requested record, if any, or <code>null</code> if no such 355 * record was found. 356 */ getRecord(K searchKey)357 public L getRecord(K searchKey) { 358 BTreeFileSession ses = openSession(); 359 360 try { 361 final int nodeSize = ses.bthr.getNodeSize(); 362 363 long currentNodeOffset = ses.bthr.getRootNodeNumber() * nodeSize; 364 365 byte[] currentNodeData = new byte[nodeSize]; 366 ses.btreeStream.seek(currentNodeOffset); 367 ses.btreeStream.readFully(currentNodeData); 368 CommonBTNodeDescriptor nodeDescriptor = 369 createCommonBTNodeDescriptor(currentNodeData, 0); 370 371 /* Search down through the layers of indices (O(log n) steps, where 372 * n is the size of the tree) */ 373 while(nodeDescriptor.getNodeType() == NodeType.INDEX) { 374 CommonBTKeyedNode<? extends CommonBTIndexRecord<K>> 375 currentNode = 376 createIndexNode(currentNodeData, 0, nodeSize); 377 CommonBTIndexRecord<K> matchingRecord = 378 findLEKey(currentNode, searchKey); 379 380 if(matchingRecord == null) { 381 return null; 382 } 383 384 currentNodeOffset = matchingRecord.getIndex() * nodeSize; 385 ses.btreeStream.seek(currentNodeOffset); 386 ses.btreeStream.readFully(currentNodeData); 387 nodeDescriptor = 388 createCommonBTNodeDescriptor(currentNodeData, 0); 389 } 390 391 /* Leaf node reached. Find record. */ 392 if(nodeDescriptor.getNodeType() == NodeType.LEAF) { 393 CommonBTKeyedNode<L> leaf = 394 createLeafNode(currentNodeData, 0, nodeSize); 395 396 for(L rec : leaf.getBTRecords()) { 397 if(rec.getKey().compareTo(searchKey) == 0) { 398 return rec; 399 } 400 } 401 402 return null; 403 } 404 else { 405 throw new RuntimeException("Expected leaf node. Found other " + 406 "kind: " + nodeDescriptor.getNodeType()); 407 } 408 } finally { 409 ses.close(); 410 } 411 } 412 } 413