/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.fs.hfsplus.tree;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.jnode.util.BigEndian;
public abstract class AbstractNode<K extends Key, T extends NodeRecord> implements Node<T> {
private final Logger log = Logger.getLogger(getClass());
protected NodeDescriptor descriptor;
protected List<T> records;
protected List<Integer> offsets;
protected int size;
public AbstractNode(NodeDescriptor descriptor, final int nodeSize) {
this.descriptor = descriptor;
this.size = nodeSize;
this.records = new ArrayList<T>(descriptor.getNumRecords());
this.offsets = new ArrayList<Integer>(descriptor.getNumRecords() + 1);
this.offsets.add(Integer.valueOf(NodeDescriptor.BT_NODE_DESCRIPTOR_LENGTH));
}
public AbstractNode(final byte[] nodeData, final int nodeSize) {
this.descriptor = new NodeDescriptor(nodeData, 0);
this.size = nodeSize;
this.records = new ArrayList<T>(this.descriptor.getNumRecords());
this.offsets = new ArrayList<Integer>(this.descriptor.getNumRecords() + 1);
int offset;
for (int i = 0; i < this.descriptor.getNumRecords() + 1; i++) {
offset = BigEndian.getUInt16(nodeData, size - ((i + 1) * 2));
offsets.add(Integer.valueOf(offset));
}
if (log.isDebugEnabled()) {
log.debug("Creating node for: " + descriptor.toString() + " offsets: " + offsets);
}
loadRecords(nodeData);
}
/**
* Loads the node's records.
*
* @param nodeData the node data.
*/
private void loadRecords(final byte[] nodeData) {
int offset;
for (int i = 0; i < this.descriptor.getNumRecords(); i++) {
offset = offsets.get(i);
Key key = createKey(nodeData, offset);
int recordSize = offsets.get(i + 1) - offset;
records.add(createRecord(key, nodeData, offset, recordSize));
if (log.isDebugEnabled()) {
log.debug("Loading record: " + key);
}
}
}
/**
* Creates a key for the node.
*
* @param nodeData the node data.
* @param offset the offset the key is at.
* @return the key.
*/
protected abstract K createKey(byte[] nodeData, int offset);
/**
* Creates a record.
*
* @param key the key.
* @param nodeData the node data.
* @param offset the offset.
* @param recordSize the record size.
* @return the record.
*/
protected abstract T createRecord(Key key, byte[] nodeData, int offset, int recordSize);
@Override
public NodeDescriptor getNodeDescriptor() {
return descriptor;
}
@Override
public int getRecordOffset(int index) {
return offsets.get(index);
}
@Override
public T getNodeRecord(int index) {
return records.get(index);
}
/**
* Find a matching record.
*
* @param key the key to match.
* @return a NodeRecord or {@code null}
*/
public final T find(K key) {
for (T record : records) {
log.debug("Record: " + record.toString() + " Key: " + key);
K recordKey = (K) record.getKey();
if (recordKey != null && recordKey.equals(key)) {
return record;
}
}
return null;
}
@Override
public boolean addNodeRecord(T record) {
int freeSpace = getFreeSize();
if (freeSpace < record.getSize() + 2) {
return false;
}
Integer lastOffset = offsets.get(offsets.size() - 1);
Integer newOffset = lastOffset + record.getSize();
offsets.add(newOffset);
records.add(record);
return true;
}
public boolean check(int treeHeight) {
// Node type is correct.
if (this.getNodeDescriptor().getKind() < NodeDescriptor.BT_LEAF_NODE ||
this.getNodeDescriptor().getKind() > NodeDescriptor.BT_MAP_NODE) {
return false;
}
if (this.getNodeDescriptor().getHeight() > treeHeight) {
return false;
}
return true;
}
/**
* Return amount of free space remaining.
*
* @return remaining free space.
*/
protected int getFreeSize() {
int freeOffset = offsets.get(offsets.size() - 1);
int freeSize = size - freeOffset - (descriptor.getNumRecords() << 1) - OFFSET_SIZE;
return freeSize;
}
public byte[] getBytes() {
byte[] datas = new byte[size];
System.arraycopy(descriptor.getBytes(), 0, datas, 0,
NodeDescriptor.BT_NODE_DESCRIPTOR_LENGTH);
int offsetIndex = 0;
int offset;
for (NodeRecord record : records) {
offset = offsets.get(offsetIndex);
System.arraycopy(record.getBytes(), 0, datas, offset, record.getSize());
BigEndian.setInt16(datas, size - ((offsetIndex + 1) * 2), offset);
offsetIndex++;
}
offset = offsets.get(offsets.size() - 1);
BigEndian.setInt16(datas, size - ((offsetIndex + 1) * 2), offset);
return datas;
}
@Override
public String toString() {
StringBuffer b = new StringBuffer();
b.append((this.getNodeDescriptor().isLeafNode()) ? "Leaf node" : "Index node").append("\n");
b.append(this.getNodeDescriptor().toString()).append("\n");
b.append("Offsets : ").append(offsets.toString());
return b.toString();
}
}