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> &lt;= <code>k</code> &lt;
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> &lt;= <code>k</code> &lt;
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