/**
* edu.utexas.GeDBIT.mckoi.store.AbstractStore 30 Aug 2002
*
* Mckoi SQL Database ( http://www.mckoi.com/database )
* Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* Version 2 as published by the Free Software Foundation.
*
* This program 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 General Public License Version 2 for more details.
*
* You should have received a copy of the GNU General Public License
* Version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Change Log:
* 2003.07.29: Add iterator(), by Rui Mao
*
*
*/
package GeDBIT.mckoi.store;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.io.*;
import GeDBIT.mckoi.util.ByteArrayUtil;
import GeDBIT.mckoi.util.UserTerminal;
/**
* Provides an abstract implementation of Store. This implements a bin based
* best-fit recycling algorithm. The store manages a structure that points to
* bins of freed space of specific sizes. When an allocation is requested the
* structure is searched for the first bin that contains an area that best fits
* the size requested.
* <p>
* Provided the derived class supports safe atomic IO operations, this store is
* designed for robustness to the level that at no point is the store left in a
* unworkable (corrupt) state.
*
* @author Tobias Downer, Rui Mao
* @version 2003.07.29
*/
public abstract class AbstractStore implements Store {
/**
* The free bin list contains 128 entries pointing to the first available
* block in the bin. If the list item contains -1 then there are no free
* blocks in the bin.
*/
protected long[] free_bin_list;
/**
* A pointer to the wilderness area (the last deleted area in the store), or
* -1 if there is no wilderness area.
*/
protected long wilderness_pointer;
/**
* True if this is read-only.
*/
protected boolean read_only;
/**
* The total amount of allocated space within this store since the store was
* openned. Note that this could be a negative amount if more space was
* freed than allocated.
*/
protected long total_allocated_space;
/**
* True if the store was opened dirtily (was not previously closed cleanly).
*/
private boolean dirty_open;
// ---------- Statics ----------
/**
* The offset into the file that the data areas start.
*/
protected static final long DATA_AREA_OFFSET = 256 + 1024 + 32;
/**
* The offset into the file of the 64 byte fixed area.
*/
protected static final long FIXED_AREA_OFFSET = 128;
/**
* The offset into the file that the bin area starts.
*/
protected static final long BIN_AREA_OFFSET = 256;
/**
* The magic value.
*/
protected static final int MAGIC = 0x0AEAE91;
/**
* Constructs the store.
*/
protected AbstractStore(boolean read_only) {
free_bin_list = new long[BIN_ENTRIES + 1];
for (int i = 0; i < BIN_ENTRIES + 1; ++i) {
free_bin_list[i] = (long) -1;
}
wilderness_pointer = -1;
this.read_only = read_only;
}
/**
* Initializes the store to an empty state.
*/
private synchronized void initializeToEmpty() throws IOException {
setDataAreaSize(DATA_AREA_OFFSET);
// New file so write out the initial file area,
ByteArrayOutputStream bout = new ByteArrayOutputStream(
(int) BIN_AREA_OFFSET);
DataOutputStream out = new DataOutputStream(bout);
// The file MAGIC
out.writeInt(MAGIC); // 0
// The file version
out.writeInt(1); // 4
// The number of areas (chunks) in the file (currently unused)
out.writeLong(-1); // 8
// File open/close status byte
out.writeByte(0); // 16
out.flush();
byte[] buf = new byte[(int) DATA_AREA_OFFSET];
byte[] buf2 = bout.toByteArray();
System.arraycopy(buf2, 0, buf, 0, buf2.length);
;
for (int i = (int) BIN_AREA_OFFSET; i < (int) DATA_AREA_OFFSET; ++i) {
buf[i] = (byte) 255;
}
writeByteArrayToPT(0, buf, 0, buf.length);
}
/**
* Opens the data store. Returns true if the store did not close cleanly.
*/
public synchronized boolean open() throws IOException {
internalOpen(read_only);
// If it's small, initialize to empty
if (endOfDataAreaPointer() < DATA_AREA_OFFSET) {
initializeToEmpty();
}
byte[] read_buf = new byte[(int) BIN_AREA_OFFSET];
readByteArrayFrom(0, read_buf, 0, read_buf.length);
ByteArrayInputStream b_in = new ByteArrayInputStream(read_buf);
DataInputStream din = new DataInputStream(b_in);
int magic = din.readInt();
if (magic != MAGIC) {
throw new IOException(
"Format invalid: Magic value is not as expected.");
}
int version = din.readInt();
if (version != 1) {
throw new IOException("Format invalid: unrecognised version.");
}
din.readLong(); // ignore
byte status = din.readByte();
dirty_open = false;
if (status == 1) {
// This means the store wasn't closed cleanly.
dirty_open = true;
}
// Read the bins
readBins();
// Mark the file as open
if (!read_only) {
writeByteToPT(16, 1);
}
long file_length = endOfDataAreaPointer();
// Set the wilderness pointer.
if (file_length == DATA_AREA_OFFSET) {
wilderness_pointer = -1;
} else {
readByteArrayFrom(file_length - 8, read_buf, 0, 8);
long last_boundary = ByteArrayUtil.getLong(read_buf, 0);
long last_area_pointer = file_length - last_boundary;
if (last_area_pointer < DATA_AREA_OFFSET) {
throw new IOException(
"File corrupt: last_area_pointer is before data part of file.");
}
if (last_area_pointer > file_length - 8) {
throw new IOException(
"File corrupt: last_area_pointer at the end of the file.");
}
readByteArrayFrom(last_area_pointer, read_buf, 0, 8);
long last_area_header = ByteArrayUtil.getLong(read_buf, 0);
// If this is a freed block, then set this are the wilderness
// pointer.
if ((last_area_header & 0x08000000000000000L) != 0) {
wilderness_pointer = last_area_pointer;
} else {
wilderness_pointer = -1;
}
}
return dirty_open;
}
/**
* Closes the store.
*/
public synchronized void close() throws IOException {
// Mark the file as closed
if (!read_only) {
writeByteToPT(16, 0);
}
internalClose();
}
/**
* Returns true if the given area size is valid. Currently the criteria for
* a valid boundary size is (size >= 24) and (size % 8 == 0) and (size < 200
* gigabytes)
*/
protected static boolean isValidBoundarySize(long size) {
long MAX_AREA_SIZE = (long) Integer.MAX_VALUE * 200;
size = size & 0x07FFFFFFFFFFFFFFFL;
return ((size < MAX_AREA_SIZE) && (size >= 24) && ((size & 0x07) == 0));
}
/**
* Reads an 8 byte long at the given position in the data area.
*/
private byte[] buf = new byte[8];
private long readLongAt(long position) throws IOException {
readByteArrayFrom(position, buf, 0, 8);
return ByteArrayUtil.getLong(buf, 0);
}
/**
* Performs a repair scan from the given pointer. This is a recursive
* algorithm that looks for at most 'n' number of repairs before giving up.
* Returns false if a repair path could not be found.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private boolean repairScan(final ArrayList areas_to_fix,
final long pointer, final long end_pointer,
final boolean scan_forward, final int max_repairs)
throws IOException {
// Recurse end conditions;
// If the end is reached, success!
if (pointer == end_pointer) {
return true;
}
// If max repairs exhausted, failure!
if (pointer > end_pointer || max_repairs <= 0) {
return false;
}
long pointer_to_head = scan_forward ? pointer : end_pointer - 8;
// Does the pointer at least look right?
long first_header = readLongAt(pointer_to_head) & 0x07FFFFFFFFFFFFFFFL;
// If it's a valid boundary size, and the header points inside the
// end boundary
long max_bound_size = end_pointer - pointer;
if (isValidBoundarySize(first_header) && first_header <= max_bound_size) {
long pointer_to_tail = scan_forward ? (pointer + first_header) - 8
: end_pointer - first_header;
// If the end doesn't look okay,
long end_area_pointer = pointer_to_tail;
long end_header = readLongAt(end_area_pointer) & 0x07FFFFFFFFFFFFFFFL;
boolean valid_end_header = (first_header == end_header);
long scan_area_p1 = scan_forward ? (pointer + first_header)
: pointer;
long scan_area_p2 = scan_forward ? end_pointer
: (end_pointer - first_header);
if (!valid_end_header) {
// First and ends are invalid, so lets first assume we make the
// end
// valid and recurse,
long area_p = scan_forward ? pointer_to_head : pointer_to_tail;
areas_to_fix.add(new Long(area_p));
areas_to_fix.add(new Long(first_header));
boolean b = repairScan(areas_to_fix, scan_area_p1,
scan_area_p2, true, max_repairs - 1);
// If success
if (b) {
return true;
}
// If failure, take that repair off the top
areas_to_fix.remove(areas_to_fix.size() - 1);
areas_to_fix.remove(areas_to_fix.size() - 1);
// And keep searching
} else {
// Looks okay, so keep going,
// This really does the same thing as recursing through the scan
// area
// however, we have to reduce the stack usage for large files
// which
// makes this iterative solution necessary. Basically, this
// looks for
// the first broken area and reverts back to applying the
// recursive
// algorithm on it.
boolean something_broken = false;
long previous1_scan_area_p1 = scan_area_p1;
long previous2_scan_area_p1 = scan_area_p1;
long previous3_scan_area_p1 = scan_area_p1;
while (scan_area_p1 < scan_area_p2 && !something_broken) {
// Assume something broken,
something_broken = true;
// Does the pointer at least look right?
long scanning_header = readLongAt(scan_area_p1) & 0x07FFFFFFFFFFFFFFFL;
long scan_max_bound_size = scan_area_p2 - scan_area_p1;
if (isValidBoundarySize(scanning_header)
&& scanning_header <= scan_max_bound_size) {
long scan_end_header = readLongAt((scan_area_p1 + scanning_header) - 8) & 0x07FFFFFFFFFFFFFFFL;
if (scan_end_header == scanning_header) {
// Cycle the scanned areas
previous3_scan_area_p1 = previous2_scan_area_p1;
previous2_scan_area_p1 = previous1_scan_area_p1;
previous1_scan_area_p1 = scan_area_p1;
scan_area_p1 = (scan_area_p1 + scanning_header);
// Enough evidence that area is not broken, so
// continue scan
something_broken = false;
}
}
}
if (something_broken) {
// Back track to the last 3 scanned areas and perform a
// repair on
// this area. This allows for the scan to have more choices
// on
// repair paths.
scan_area_p1 = previous3_scan_area_p1;
}
// The recursive scan on the (potentially) broken area.
boolean b = repairScan(areas_to_fix, scan_area_p1,
scan_area_p2, true, max_repairs);
if (b) {
// Repair succeeded!
return b;
}
// Repair didn't succeed so keep searching.
}
}
// Try reversing the scan and see if that comes up with something valid.
if (scan_forward) {
boolean b = repairScan(areas_to_fix, pointer, end_pointer, false,
max_repairs);
// Success
if (b) {
return b;
}
} else {
return false;
}
// We guarenteed to be scan forward if we get here....
// Facts: we know that the start and end pointers are invalid....
// Search forward for something that looks like a boundary. If we don't
// find it, search backwards for something that looks like a boundary.
final long max_size = end_pointer - pointer;
for (long i = 16; i < max_size; i += 8) {
long v = readLongAt(pointer + i) & 0x07FFFFFFFFFFFFFFFL;
if (v == i + 8) {
// This looks like a boundary, so try this...
areas_to_fix.add(new Long(pointer));
areas_to_fix.add(new Long(i + 8));
boolean b = repairScan(areas_to_fix, pointer + i + 8,
end_pointer, true, max_repairs - 1);
if (b) {
return true;
}
areas_to_fix.remove(areas_to_fix.size() - 1);
areas_to_fix.remove(areas_to_fix.size() - 1);
}
}
// Scan backwards....
for (long i = max_size - 8 - 16; i >= 0; i -= 8) {
long v = readLongAt(pointer + i) & 0x07FFFFFFFFFFFFFFFL;
if (v == (max_size - i)) {
// This looks like a boundary, so try this...
areas_to_fix.add(new Long(pointer + i));
areas_to_fix.add(new Long((max_size - i)));
boolean b = repairScan(areas_to_fix, pointer, pointer + i,
true, max_repairs - 1);
if (b) {
return true;
}
areas_to_fix.remove(areas_to_fix.size() - 1);
areas_to_fix.remove(areas_to_fix.size() - 1);
}
}
// No luck, so simply set this as a final big area and return true.
// NOTE: There are other tests possible here but I think what we have
// will
// find fixes for 99% of corruption cases.
areas_to_fix.add(new Long(pointer));
areas_to_fix.add(new Long(end_pointer - pointer));
return true;
}
/**
* Opens/scans the store looking for any errors with the layout. If a
* problem with the store is detected, it attempts to fix it.
*/
@SuppressWarnings("rawtypes")
public synchronized void openScanAndFix(UserTerminal terminal)
throws IOException {
internalOpen(read_only);
terminal.println("- Store: " + toString());
// If it's small, initialize to empty
if (endOfDataAreaPointer() < DATA_AREA_OFFSET) {
terminal.println("+ Store too small - initializing to empty.");
initializeToEmpty();
return;
}
byte[] read_buf = new byte[(int) BIN_AREA_OFFSET];
readByteArrayFrom(0, read_buf, 0, read_buf.length);
ByteArrayInputStream b_in = new ByteArrayInputStream(read_buf);
DataInputStream din = new DataInputStream(b_in);
int magic = din.readInt();
if (magic != MAGIC) {
terminal.println("! Store magic value not present - not fixable.");
return;
}
int version = din.readInt();
if (version != 1) {
terminal.println("! Store version is invalid - not fixable.");
return;
}
// Check the size
long end_of_data_area = endOfDataAreaPointer();
if (end_of_data_area < DATA_AREA_OFFSET + 16) {
// Store size is too small. There's nothing to be lost be simply
// reinitializing it to a blank state.
terminal.println("! Store is too small, reinitializing store to blank state.");
initializeToEmpty();
return;
}
// Do a recursive scan over the store.
ArrayList repairs = new ArrayList();
boolean b = repairScan(repairs, DATA_AREA_OFFSET,
endOfDataAreaPointer(), true, 20);
if (b) {
if (repairs.size() == 0) {
terminal.println("- Store areas are intact.");
} else {
terminal.println("+ " + (repairs.size() / 2) + " area repairs:");
for (int i = 0; i < repairs.size(); i += 2) {
terminal.println(" Area pointer: " + repairs.get(i));
terminal.println(" Area size: " + repairs.get(i + 1));
long pointer = ((Long) repairs.get(i)).longValue();
long size = ((Long) repairs.get(i + 1)).longValue();
coalescArea(pointer, size);
}
}
} else {
terminal.println("- Store is not repairable!");
}
// Rebuild the free bins,
free_bin_list = new long[BIN_ENTRIES + 1];
for (int i = 0; i < BIN_ENTRIES + 1; ++i) {
free_bin_list[i] = (long) -1;
}
terminal.println("+ Rebuilding free bins.");
long[] header = new long[2];
// Scan for all free areas in the store.
long pointer = DATA_AREA_OFFSET;
while (pointer < end_of_data_area) {
getAreaHeader(pointer, header);
long area_size = (header[0] & 0x07FFFFFFFFFFFFFFFL);
boolean is_free = ((header[0] & 0x08000000000000000L) != 0);
if (is_free) {
addToBinChain(pointer, area_size);
}
pointer += area_size;
}
// Update all the bins
writeAllBins();
terminal.println("- Store repair complete.");
// Flush changed
flush();
// Open the store for real,
open();
}
/**
* Performs an extensive lookup on all the tables in this store and sets a
* number of properties in the given HashMap (property name(String) ->
* property description(Object)). This should be used for store diagnostics.
* <p>
* Assume the store is open.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public synchronized void statsScan(HashMap properties) throws IOException {
long free_areas = 0;
long free_total = 0;
long allocated_areas = 0;
long allocated_total = 0;
final long end_of_data_area = endOfDataAreaPointer();
long[] header = new long[2];
// The first header
long pointer = DATA_AREA_OFFSET;
while (pointer < end_of_data_area) {
getAreaHeader(pointer, header);
long area_size = (header[0] & 0x07FFFFFFFFFFFFFFFL);
if ((header[0] & 0x08000000000000000L) != 0) {
++free_areas;
free_total += area_size;
} else {
++allocated_areas;
allocated_total += area_size;
}
pointer += area_size;
}
if (wilderness_pointer != -1) {
getAreaHeader(wilderness_pointer, header);
long wilderness_size = (header[0] & 0x07FFFFFFFFFFFFFFFL);
properties.put("AbstractStore.wilderness_size", new Long(
wilderness_size));
}
properties.put("AbstractStore.end_of_data_area", new Long(
end_of_data_area));
properties.put("AbstractStore.free_areas", new Long(free_areas));
properties.put("AbstractStore.free_total", new Long(free_total));
properties.put("AbstractStore.allocated_areas", new Long(
allocated_areas));
properties.put("AbstractStore.allocated_total", new Long(
allocated_total));
}
/**
* Returns a List of Long objects that contain a complete list of all areas
* in the store. This is useful for checking if a given pointer is valid or
* not. The returned list is sorted from start area to end area.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public List getAllAreas() throws IOException {
ArrayList list = new ArrayList();
final long end_of_data_area = endOfDataAreaPointer();
long[] header = new long[2];
// The first header
long pointer = DATA_AREA_OFFSET;
while (pointer < end_of_data_area) {
getAreaHeader(pointer, header);
long area_size = (header[0] & 0x07FFFFFFFFFFFFFFFL);
if ((header[0] & 0x08000000000000000L) == 0) {
list.add(new Long(pointer));
}
pointer += area_size;
}
return list;
}
/**
* Scans the area list, and any areas that aren't deleted and aren't found
* in the given ArrayList are returned as leaked areas. This is a useful
* method for finding any leaks in the store.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public ArrayList findAllocatedAreasNotIn(ArrayList list) throws IOException {
// Sort the list
Collections.sort(list);
// The list of leaked areas
ArrayList leaked_areas = new ArrayList();
int list_index = 0;
// What area are we looking for?
long looking_for = Long.MAX_VALUE;
if (list_index < list.size()) {
looking_for = ((Long) list.get(list_index)).longValue();
++list_index;
}
final long end_of_data_area = endOfDataAreaPointer();
long[] header = new long[2];
long pointer = DATA_AREA_OFFSET;
while (pointer < end_of_data_area) {
getAreaHeader(pointer, header);
long area_size = (header[0] & 0x07FFFFFFFFFFFFFFFL);
boolean area_free = (header[0] & 0x08000000000000000L) != 0;
if (pointer == looking_for) {
if (area_free) {
throw new IOException("Area (pointer = " + pointer
+ ") is not allocated!");
}
// Update the 'looking_for' pointer
if (list_index < list.size()) {
looking_for = ((Long) list.get(list_index)).longValue();
++list_index;
} else {
looking_for = Long.MAX_VALUE;
}
} else if (pointer > looking_for) {
throw new IOException("Area (pointer = " + looking_for
+ ") wasn't found in store!");
} else {
// An area that isn't in the list
if (!area_free) {
// This is a leaked area.
// It isn't free and it isn't in the list
leaked_areas.add(new Long(pointer));
}
}
pointer += area_size;
}
return leaked_areas;
}
/**
* Returns the total allocated space since the file was openned.
*/
public synchronized long totalAllocatedSinceStart() {
return total_allocated_space;
}
// /**
// * Copies the contents of the store to the given file.
// */
// public synchronized void copyTo(File file) throws IOException {
// FileOutputStream fout = new FileOutputStream(file);
// final int BUF_SIZE = 16384;
// byte[] buf = new byte[BUF_SIZE];
// long end = endOfDataAreaPointer();
// for (long i = 0; i < end; i += BUF_SIZE) {
// int read_size = (int) Math.min((long) BUF_SIZE, end - i);
// readByteArrayFrom(i, buf, 0, read_size);
// fout.write(buf, 0, read_size);
// }
// fout.close();
// }
/**
* Returns the bin index that would be the minimum size to store the given
* object.
*/
private int minimumBinSizeIndex(long size) {
int i = Arrays.binarySearch(BIN_SIZES, (int) size);
if (i < 0) {
i = -(i + 1);
}
return i;
}
/**
* Internally opens the backing area. If 'read_only' is true then the store
* is openned in read only mode.
*/
protected abstract void internalOpen(boolean read_only) throws IOException;
/**
* Internally closes the backing area.
*/
protected abstract void internalClose() throws IOException;
/**
* Reads a byte from the given position in the file.
*/
protected abstract int readByteFrom(long position) throws IOException;
/**
* Reads a byte array from the given position in the file. Returns the
* number of bytes read.
*/
protected abstract int readByteArrayFrom(long position, byte[] buf,
int off, int len) throws IOException;
/**
* Writes a byte to the given position in the file.
*/
protected abstract void writeByteTo(long position, int b)
throws IOException;
/**
* Writes a byte array to the given position in the file.
*/
protected abstract void writeByteArrayTo(long position, byte[] buf,
int off, int len) throws IOException;
/**
* Returns a pointer to the end of the current data area.
*/
protected abstract long endOfDataAreaPointer() throws IOException;
/**
* Sets the size of the data area.
*/
protected abstract void setDataAreaSize(long length) throws IOException;
/**
* WriteByteTo pass-through method.
*/
private final void writeByteToPT(long position, int b) throws IOException {
writeByteTo(position, b);
}
/**
* WriteByteArrayTo pass-through method.
*/
private final void writeByteArrayToPT(long position, byte[] buf, int off,
int len) throws IOException {
writeByteArrayTo(position, buf, off, len);
}
// ----------
/**
* Checks the pointer is valid.
*/
protected void checkPointer(long pointer) throws IOException {
if (pointer < DATA_AREA_OFFSET || pointer >= endOfDataAreaPointer()) {
throw new IOException("Pointer out of range: " + DATA_AREA_OFFSET
+ " > " + pointer + " > " + endOfDataAreaPointer());
}
}
/**
* A buffered work area we work with when reading/writing bin pointers from
* the file header.
*/
private final byte[] bin_area = new byte[128 * 8];
/**
* Reads the bins from the header information in the file.
*/
protected void readBins() throws IOException {
readByteArrayFrom(BIN_AREA_OFFSET, bin_area, 0, 128 * 8);
ByteArrayInputStream bin = new ByteArrayInputStream(bin_area);
DataInputStream in = new DataInputStream(bin);
for (int i = 0; i < 128; ++i) {
free_bin_list[i] = in.readLong();
}
}
/**
* Updates all bins to the data area header area.
*/
protected void writeAllBins() throws IOException {
int p = 0;
for (int i = 0; i < 128; ++i, p += 8) {
long val = free_bin_list[i];
ByteArrayUtil.setLong(val, bin_area, p);
}
writeByteArrayToPT(BIN_AREA_OFFSET, bin_area, 0, 128 * 8);
}
/**
* Updates the given bin index to the data area header area.
*/
protected void writeBinIndex(int index) throws IOException {
int p = index * 8;
long val = free_bin_list[index];
ByteArrayUtil.setLong(val, bin_area, p);
writeByteArrayToPT(BIN_AREA_OFFSET + p, bin_area, p, 8);
}
protected final byte[] header_buf = new byte[16];
/**
* Sets the 'header' array with information from the header of the given
* pointer.
*/
protected void getAreaHeader(long pointer, long[] header)
throws IOException {
readByteArrayFrom(pointer, header_buf, 0, 16);
header[0] = ByteArrayUtil.getLong(header_buf, 0);
header[1] = ByteArrayUtil.getLong(header_buf, 8);
}
/**
* Sets the 'header' array with information from the previous header to the
* given pointer, and returns a pointer to the previous area.
*/
protected long getPreviousAreaHeader(long pointer, long[] header)
throws IOException {
// If the pointer is the start of the file area
if (pointer == DATA_AREA_OFFSET) {
// Return a 0 sized block
header[0] = 0;
return -1;
} else {
readByteArrayFrom(pointer - 8, header_buf, 0, 8);
long sz = ByteArrayUtil.getLong(header_buf, 0);
sz = sz & 0x07FFFFFFFFFFFFFFFL;
long previous_pointer = pointer - sz;
readByteArrayFrom(previous_pointer, header_buf, 0, 8);
header[0] = ByteArrayUtil.getLong(header_buf, 0);
return previous_pointer;
}
}
/**
* Sets the 'header' array with information from the next header to the
* given pointer, and returns a pointer to the next area.
*/
protected long getNextAreaHeader(long pointer, long[] header)
throws IOException {
readByteArrayFrom(pointer, header_buf, 0, 8);
long sz = ByteArrayUtil.getLong(header_buf, 0);
sz = sz & 0x07FFFFFFFFFFFFFFFL;
long next_pointer = pointer + sz;
if (next_pointer >= endOfDataAreaPointer()) {
// Return a 0 sized block
header[0] = 0;
return -1;
}
readByteArrayFrom(next_pointer, header_buf, 0, 8);
header[0] = ByteArrayUtil.getLong(header_buf, 0);
return next_pointer;
}
/**
* Rebounds the given area with the given header information. If
* 'write_headers' is true, the header (header[0]) is changed. Note that
* this shouldn't be used to change the size of a chunk.
*/
protected void reboundArea(long pointer, long[] header,
boolean write_headers) throws IOException {
if (write_headers) {
ByteArrayUtil.setLong(header[0], header_buf, 0);
ByteArrayUtil.setLong(header[1], header_buf, 8);
writeByteArrayToPT(pointer, header_buf, 0, 16);
} else {
ByteArrayUtil.setLong(header[1], header_buf, 8);
writeByteArrayToPT(pointer + 8, header_buf, 8, 8);
}
}
/**
* Coalesc one or more areas into a larger area. This alters the boundary of
* the area to encompass the given size.
*/
protected void coalescArea(long pointer, long size) throws IOException {
ByteArrayUtil.setLong(size, header_buf, 0);
// ISSUE: Boundary alteration is a moment when corruption could occur.
// There are two seeks and writes here and when we are setting the
// end points, there is a risk of failure.
writeByteArrayToPT(pointer, header_buf, 0, 8);
writeByteArrayToPT((pointer + size) - 8, header_buf, 0, 8);
}
/**
* Expands the data area by at least the minimum size given. Returns the
* actual size the data area was expanded by.
*/
protected long expandDataArea(long minimum_size) throws IOException {
long end_of_data_area = endOfDataAreaPointer();
// Round all sizes up to the nearest 8
// We grow only by a small amount if the area is small, and a large
// amount
// if the area is large.
long over_grow = end_of_data_area / 64;
long d = (over_grow & 0x07L);
if (d != 0) {
over_grow = over_grow + (8 - d);
}
over_grow = Math.min(over_grow, 262144L);
if (over_grow < 1024) {
over_grow = 1024;
}
long grow_by = minimum_size + over_grow;
long new_file_length = end_of_data_area + grow_by;
setDataAreaSize(new_file_length);
return grow_by;
}
/**
* Splits an area pointed to by 'pointer' at a new boundary point.
*/
protected void splitArea(long pointer, long new_boundary)
throws IOException {
// Split the area pointed to by the pointer.
readByteArrayFrom(pointer, header_buf, 0, 8);
long cur_size = ByteArrayUtil.getLong(header_buf, 0) & 0x07FFFFFFFFFFFFFFFL;
long left_size = new_boundary;
long right_size = cur_size - new_boundary;
if (right_size < 0) {
throw new Error("right_size < 0");
}
ByteArrayUtil.setLong(left_size, header_buf, 0);
ByteArrayUtil.setLong(right_size, header_buf, 8);
// ISSUE: Boundary alteration is a moment when corruption could occur.
// There are three seeks and writes here and when we are setting the
// end points, there is a risk of failure.
// First set the boundary
writeByteArrayToPT((pointer + new_boundary) - 8, header_buf, 0, 16);
// Now set the end points
writeByteArrayToPT(pointer, header_buf, 0, 8);
writeByteArrayToPT((pointer + cur_size) - 8, header_buf, 8, 8);
}
private long[] header_info = new long[2];
private long[] header_info2 = new long[2];
/**
* Adds the given area to the bin represented by the bin_chain_index.
*/
private void addToBinChain(long pointer, long size) throws IOException {
checkPointer(pointer);
// What bin would this area fit into?
int bin_chain_index = minimumBinSizeIndex(size);
// System.out.println("+ Adding to bin chain: " + pointer + " size: " +
// size);
// System.out.println("+ Adding to index: " + bin_chain_index);
long cur_pointer = free_bin_list[bin_chain_index];
if (cur_pointer == -1) {
// If the bin chain has no elements,
header_info[0] = (size | 0x08000000000000000L);
header_info[1] = -1;
reboundArea(pointer, header_info, true);
free_bin_list[bin_chain_index] = pointer;
writeBinIndex(bin_chain_index);
} else {
boolean inserted = false;
long last_pointer = -1;
int searches = 0;
while (cur_pointer != -1 && inserted == false) {
// Get the current pointer
getAreaHeader(cur_pointer, header_info);
long header = header_info[0];
long next = header_info[1];
// Assert - the header must have deleted flag
if ((header & 0x08000000000000000L) == 0) {
throw new Error(
"Assert failed - area not marked as deleted.");
}
long area_size = header ^ 0x08000000000000000L;
if (area_size >= size || searches >= 12) {
// Insert if the area size is >= than the size we are
// adding.
// Set the previous header to point to this
long previous = last_pointer;
// Set up the deleted area
header_info[0] = (size | 0x08000000000000000L);
header_info[1] = cur_pointer;
reboundArea(pointer, header_info, true);
if (last_pointer != -1) {
// Set the previous in the chain to point to the deleted
// area
getAreaHeader(previous, header_info);
header_info[1] = pointer;
reboundArea(previous, header_info, false);
} else {
// Otherwise set the head bin item
free_bin_list[bin_chain_index] = pointer;
writeBinIndex(bin_chain_index);
}
inserted = true;
}
last_pointer = cur_pointer;
cur_pointer = next;
++searches;
}
// If we reach the end and we haven't inserted,
if (!inserted) {
// Set the new deleted area.
header_info[0] = (size | 0x08000000000000000L);
header_info[1] = -1;
reboundArea(pointer, header_info, true);
// Set the previous entry to this
getAreaHeader(last_pointer, header_info);
header_info[1] = pointer;
reboundArea(last_pointer, header_info, false);
}
}
}
/**
* Removes the given area from the bin chain. This requires a search of the
* bin chain for the given size.
*/
private void removeFromBinChain(long pointer, long size) throws IOException {
// What bin index should we be looking in?
int bin_chain_index = minimumBinSizeIndex(size);
// System.out.println("- Removing from bin chain " + pointer + " size "
// + size);
// System.out.println("- Removing from index " + bin_chain_index);
long previous_pointer = -1;
long cur_pointer = free_bin_list[bin_chain_index];
// Search this bin for the pointer
// NOTE: This is an iterative search through the bin chain
while (pointer != cur_pointer) {
if (cur_pointer == -1) {
throw new IOException("Area not found in bin chain!");
}
// Move to the next in the chain
getAreaHeader(cur_pointer, header_info);
previous_pointer = cur_pointer;
cur_pointer = header_info[1];
}
// Found the pointer, so remove it,
if (previous_pointer == -1) {
getAreaHeader(pointer, header_info);
free_bin_list[bin_chain_index] = header_info[1];
writeBinIndex(bin_chain_index);
} else {
getAreaHeader(previous_pointer, header_info2);
getAreaHeader(pointer, header_info);
header_info2[1] = header_info[1];
reboundArea(previous_pointer, header_info2, false);
}
}
/**
* Crops the area to the given size. This is used after an area is pulled
* from a bin. This method decides if it's worth reusing any space left over
* and the end of the area.
*/
private void cropArea(long pointer, long allocated_size) throws IOException {
// Get the header info
getAreaHeader(pointer, header_info);
long header = header_info[0];
// Can we recycle the difference in area size?
final long free_area_size = header;
// The difference between the size of the free area and the size
// of the allocated area?
final long size_difference = free_area_size - allocated_size;
// If the difference is greater than 512 bytes, add the excess space to
// a free bin.
boolean is_wilderness = (pointer == wilderness_pointer);
if ((is_wilderness && size_difference >= 32) || size_difference >= 512) {
// Split the area into two areas.
splitArea(pointer, allocated_size);
long left_over_pointer = pointer + allocated_size;
// Add this area to the bin chain
addToBinChain(left_over_pointer, size_difference);
// If pointer is the wilderness area, set this as the new wilderness
if (is_wilderness
|| (left_over_pointer + size_difference) >= endOfDataAreaPointer()) {
wilderness_pointer = left_over_pointer;
}
} else {
// If pointer is the wilderness area, set wilderness to -1
if (is_wilderness) {
wilderness_pointer = -1;
}
}
}
// ---------- Implemented from Store ----------
public synchronized long alloc(long size) throws IOException {
// Negative allocations are not allowed
if (size < 0) {
throw new IOException("Negative size allocation");
}
// Add 16 bytes for headers
size = size + 16;
// If size < 32, make size = 32
if (size < 32) {
size = 32;
}
// Round all sizes up to the nearest 8
long d = size & 0x07L;
if (d != 0) {
size = size + (8 - d);
}
final long real_alloc_size = size;
// Search the free bin list for the first bin that matches the given
// size.
int bin_chain_index;
if (size > MAX_BIN_SIZE) {
bin_chain_index = BIN_ENTRIES;
} else {
int i = minimumBinSizeIndex(size);
bin_chain_index = i;
}
// Search the bins until we find the first area that is the nearest fit
// to
// the size requested.
int found_bin_index = -1;
long previous_pointer = -1;
boolean first = true;
for (int i = bin_chain_index; i < BIN_ENTRIES + 1
&& found_bin_index == -1; ++i) {
long cur_pointer = free_bin_list[i];
if (cur_pointer != -1) {
if (!first) {
// Pick this..
found_bin_index = i;
previous_pointer = -1;
}
// Search this bin for the first that's big enough.
// We only search the first 12 entries in the bin before giving
// up.
else {
long last_pointer = -1;
int searches = 0;
while (cur_pointer != -1 && found_bin_index == -1
&& searches < 12) {
getAreaHeader(cur_pointer, header_info);
long area_size = (header_info[0] & 0x07FFFFFFFFFFFFFFFL);
// Is this area is greater or equal than the required
// size
// and is not the wilderness area, pick it.
if (cur_pointer != wilderness_pointer
&& area_size >= size) {
found_bin_index = i;
previous_pointer = last_pointer;
}
// Go to next in chain.
last_pointer = cur_pointer;
cur_pointer = header_info[1];
++searches;
}
}
}
first = false;
}
// If no area can be recycled,
if (found_bin_index == -1) {
// Allocate a new area of the given size.
// If there is a wilderness, grow the wilderness area to the new
// size,
long working_pointer;
long size_to_grow;
long current_area_size;
if (wilderness_pointer != -1) {
working_pointer = wilderness_pointer;
getAreaHeader(wilderness_pointer, header_info);
long wilderness_size = (header_info[0] & 0x07FFFFFFFFFFFFFFFL);
// Remove this from the bins
removeFromBinChain(working_pointer, wilderness_size);
// For safety, we set wilderness_pointer to -1
wilderness_pointer = -1;
size_to_grow = size - wilderness_size;
current_area_size = wilderness_size;
} else {
// wilderness_pointer == -1 so add to the end of the data area.
working_pointer = endOfDataAreaPointer();
size_to_grow = size;
current_area_size = 0;
}
long expanded_size = 0;
if (size_to_grow > 0) {
// Expand the data area to the new size.
expanded_size = expandDataArea(size_to_grow);
}
// Coalesc the new area to the given size
coalescArea(working_pointer, current_area_size + expanded_size);
// crop the area
cropArea(working_pointer, size);
// Add to the total allocated space
total_allocated_space += real_alloc_size;
return working_pointer;
} else {
// An area is taken from the bins,
long free_area_pointer;
// Remove this area from the bin chain and possibly add any excess
// space
// left over to a new bin.
if (previous_pointer == -1) {
free_area_pointer = free_bin_list[found_bin_index];
getAreaHeader(free_area_pointer, header_info);
free_bin_list[found_bin_index] = header_info[1];
writeBinIndex(found_bin_index);
} else {
getAreaHeader(previous_pointer, header_info2);
free_area_pointer = header_info2[1];
getAreaHeader(free_area_pointer, header_info);
header_info2[1] = header_info[1];
reboundArea(previous_pointer, header_info2, false);
}
// Reset the header of the recycled area.
header_info[0] = (header_info[0] & 0x07FFFFFFFFFFFFFFFL);
reboundArea(free_area_pointer, header_info, true);
// Crop the area to the given size.
cropArea(free_area_pointer, size);
// Add to the total allocated space
total_allocated_space += real_alloc_size;
return free_area_pointer;
}
}
public synchronized void free(long pointer) throws IOException {
// Get the area header
getAreaHeader(pointer, header_info);
if ((header_info[0] & 0x08000000000000000L) != 0) {
throw new IOException("Area already marked as unallocated.");
}
// If (pointer + size) reaches the end of the header area, set this as
// the
// wilderness.
boolean set_as_wilderness = ((pointer + header_info[0]) >= endOfDataAreaPointer());
long r_pointer = pointer;
final long freeing_area_size = header_info[0];
long r_size = freeing_area_size;
// Can this area coalesc?
long left_pointer = getPreviousAreaHeader(pointer, header_info2);
boolean coalesc = false;
if ((header_info2[0] & 0x08000000000000000L) != 0) {
// Yes, we can coalesc left
long area_size = (header_info2[0] & 0x07FFFFFFFFFFFFFFFL);
r_pointer = left_pointer;
r_size = r_size + area_size;
// Remove left area from the bin
removeFromBinChain(left_pointer, area_size);
coalesc = true;
}
if (!set_as_wilderness) {
long right_pointer = getNextAreaHeader(pointer, header_info2);
if ((header_info2[0] & 0x08000000000000000L) != 0) {
// Yes, we can coalesc right
long area_size = (header_info2[0] & 0x07FFFFFFFFFFFFFFFL);
r_size = r_size + area_size;
// Remove right from the bin
removeFromBinChain(right_pointer, area_size);
set_as_wilderness = (right_pointer == wilderness_pointer);
coalesc = true;
}
}
// If we are coalescing parent areas
if (coalesc) {
coalescArea(r_pointer, r_size);
}
// Add this new area to the bin chain,
addToBinChain(r_pointer, r_size);
// Do we set this as the wilderness?
if (set_as_wilderness) {
wilderness_pointer = r_pointer;
}
total_allocated_space -= freeing_area_size;
}
public void setFixedArea(byte[] buf, int off, int len) throws IOException {
if (len > 64) {
throw new IOException(
"Fixed area size can be no greater than 64 bytes.");
}
writeByteArrayToPT(FIXED_AREA_OFFSET, buf, off, len);
}
public void getFixedArea(byte[] buf, int off, int len) throws IOException {
if (len > 64) {
throw new IOException(
"Fixed area size is no greater than 64 bytes.");
}
readByteArrayFrom(FIXED_AREA_OFFSET, buf, off, len);
}
public Area getFixedArea() throws IOException {
return new StoreArea(FIXED_AREA_OFFSET, 64);
}
/**
* Convenience for finding the size of an area. If the area is deleted
* throws an exception.
*/
private long getAreaSize(final long pointer) throws IOException {
final byte[] buf = new byte[8];
readByteArrayFrom(pointer, buf, 0, 8);
final long v = ByteArrayUtil.getLong(buf, 0);
if ((v & 0x08000000000000000L) != 0) {
throw new IOException("Area is deleted.");
}
return v - 16;
}
public InputStream getInputStream(long pointer) throws IOException {
return new StoreInputStream(pointer + 8, getAreaSize(pointer));
}
public OutputStream getOutputStream(long pointer) throws IOException {
return new StoreOutputStream(pointer + 8, getAreaSize(pointer));
}
public Area getArea(long pointer) throws IOException {
return new StoreArea(pointer);
}
public boolean lastCloseClean() {
return !dirty_open;
}
// ---------- Inner classes ----------
private class StoreInputStream extends InputStream {
private long pointer;
private long end_pointer;
private long mark;
public StoreInputStream(long pointer, long max_size) {
this.pointer = pointer;
this.end_pointer = pointer + max_size;
this.mark = -1;
}
public int read() throws IOException {
if (pointer >= end_pointer) {
return -1;
}
int b = readByteFrom(pointer);
++pointer;
return b;
}
public int read(byte[] buf) throws IOException {
return read(buf, 0, buf.length);
}
public int read(byte[] buf, int off, int len) throws IOException {
// Is the end of the stream reached?
if (pointer >= end_pointer) {
return -1;
}
// How much can we read?
int read_count = Math.min(len, (int) (end_pointer - pointer));
int act_read_count;
act_read_count = readByteArrayFrom(pointer, buf, off, read_count);
if (act_read_count != read_count) {
throw new IOException("act_read_count != read_count");
}
pointer += read_count;
return read_count;
}
public long skip(long skip) throws IOException {
long to_skip = Math.min(end_pointer - pointer, skip);
pointer += to_skip;
return to_skip;
}
public int available() throws IOException {
return (int) (end_pointer - pointer);
}
public void close() throws IOException {
// Do nothing
}
public void mark(int read_limit) {
mark = pointer;
}
public void reset() throws IOException {
pointer = mark;
}
public boolean markSupported() {
return true;
}
}
private class StoreOutputStream extends OutputStream {
private long pointer;
private long end_pointer;
public StoreOutputStream(long pointer, long max_size) {
this.pointer = pointer;
this.end_pointer = pointer + max_size;
}
public void write(int b) throws IOException {
if (pointer >= end_pointer) {
throw new IOException(
"Attempt to write past bounds of allocated area.");
}
writeByteToPT(pointer, b);
++pointer;
}
public void write(byte[] buf) throws IOException {
write(buf, 0, buf.length);
}
public void write(byte[] buf, int off, int len) throws IOException {
long max_can_write = (end_pointer - pointer);
if (len > max_can_write) {
throw new IOException(
"Attempt to write past bounds of allocated area.");
}
writeByteArrayToPT(pointer, buf, off, len);
pointer += len;
}
public void flush() throws IOException {
// do nothing
}
public void close() throws IOException {
// do nothing
}
}
private class StoreArea implements Area {
private static final int BUFFER_SIZE = 8;
private final long start_pointer;
private final long end_pointer;
private long position;
// A small buffer used when accessing the underlying data
private final byte[] buffer = new byte[BUFFER_SIZE];
public StoreArea(final long pointer) throws IOException {
readByteArrayFrom(pointer, buffer, 0, 8);
final long v = ByteArrayUtil.getLong(buffer, 0);
if ((v & 0x08000000000000000L) != 0) {
throw new IOException(
"Store being constructed on deleted area.");
}
final long max_size = v - 16;
this.start_pointer = pointer + 8;
this.position = start_pointer;
this.end_pointer = start_pointer + max_size;
}
public StoreArea(final long pointer, final long fixed_size)
throws IOException {
this.start_pointer = pointer;
this.position = start_pointer;
this.end_pointer = start_pointer + fixed_size;
}
private long checkPositionBounds(int diff) throws IOException {
long new_pos = position + diff;
if (new_pos > end_pointer) {
throw new IOException("Position out of bounds. " + " start="
+ start_pointer + " end=" + end_pointer + " pos="
+ position + " new_pos=" + new_pos);
}
long old_pos = position;
position = new_pos;
return old_pos;
}
public int position() {
return (int) (position - start_pointer);
}
public int capacity() {
return (int) (end_pointer - start_pointer);
}
public Area position(int position) throws IOException {
long act_position = start_pointer + position;
if (act_position >= 0 && act_position < end_pointer) {
this.position = act_position;
return this;
}
throw new IOException("Moved position out of bounds.");
}
public Area copyTo(Area destination, int size) throws IOException {
// NOTE: Assuming 'destination' is a StoreArea, the temporary buffer
// could be optimized away to a direct System.arraycopy. However,
// this
// function would need to be written as a lower level IO function.
final int BUFFER_SIZE = 2048;
byte[] buf = new byte[BUFFER_SIZE];
int to_copy = Math.min(size, BUFFER_SIZE);
while (to_copy > 0) {
get(buf, 0, to_copy);
destination.put(buf, 0, to_copy);
size -= to_copy;
to_copy = Math.min(size, BUFFER_SIZE);
}
return this;
}
public byte get() throws IOException {
return (byte) readByteFrom(checkPositionBounds(1));
}
public Area put(byte b) throws IOException {
writeByteToPT(checkPositionBounds(1), b);
return this;
}
public Area get(byte[] buf, int off, int len) throws IOException {
readByteArrayFrom(checkPositionBounds(len), buf, off, len);
return this;
}
public Area put(byte[] buf, int off, int len) throws IOException {
writeByteArrayToPT(checkPositionBounds(len), buf, off, len);
return this;
}
public short getShort() throws IOException {
readByteArrayFrom(checkPositionBounds(2), buffer, 0, 2);
return ByteArrayUtil.getShort(buffer, 0);
}
public Area putShort(short s) throws IOException {
ByteArrayUtil.setShort(s, buffer, 0);
writeByteArrayToPT(checkPositionBounds(2), buffer, 0, 2);
return this;
}
public int getInt() throws IOException {
readByteArrayFrom(checkPositionBounds(4), buffer, 0, 4);
return ByteArrayUtil.getInt(buffer, 0);
}
public Area putInt(int i) throws IOException {
ByteArrayUtil.setInt(i, buffer, 0);
writeByteArrayToPT(checkPositionBounds(4), buffer, 0, 4);
return this;
}
public long getLong() throws IOException {
readByteArrayFrom(checkPositionBounds(8), buffer, 0, 8);
return ByteArrayUtil.getLong(buffer, 0);
}
public Area putLong(long l) throws IOException {
ByteArrayUtil.setLong(l, buffer, 0);
writeByteArrayToPT(checkPositionBounds(8), buffer, 0, 8);
return this;
}
public char getChar() throws IOException {
readByteArrayFrom(checkPositionBounds(2), buffer, 0, 2);
return ByteArrayUtil.getChar(buffer, 0);
}
public Area putChar(char c) throws IOException {
ByteArrayUtil.setChar(c, buffer, 0);
writeByteArrayToPT(checkPositionBounds(2), buffer, 0, 2);
return this;
}
public String toString() {
return "[Area start_pointer=" + start_pointer + " end_pointer="
+ end_pointer + " position=" + position + "]";
}
}
// ---------- Static methods ----------
/**
* The default bin sizes in bytes. The minimum size of a bin is 32 and the
* maximum size is 2252832.
*/
private final static int[] BIN_SIZES = { 32, 64, 96, 128, 160, 192, 224,
256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640,
672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024, 1056,
1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312, 1344, 1376, 1408,
1440, 1472, 1504, 1536, 1568, 1600, 1632, 1664, 1696, 1728, 1760,
1792, 1824, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080, 2144,
2208, 2272, 2336, 2400, 2464, 2528, 2592, 2656, 2720, 2784, 2848,
2912, 2976, 3040, 3104, 3168, 3232, 3296, 3360, 3424, 3488, 3552,
3616, 3680, 3744, 3808, 3872, 3936, 4000, 4064, 4128, 4384, 4640,
4896, 5152, 5408, 5664, 5920, 6176, 6432, 6688, 6944, 7200, 7456,
7712, 7968, 8224, 10272, 12320, 14368, 16416, 18464, 20512, 22560,
24608, 57376, 90144, 122912, 155680, 1204256, 2252832 };
protected final static int BIN_ENTRIES = BIN_SIZES.length;
private final static int MAX_BIN_SIZE = BIN_SIZES[BIN_ENTRIES - 1];
@SuppressWarnings("rawtypes")
public Iterator iterator() {
return new StoreIterator();
}
// ------------------ Inner class StoreIterator ----------------------//
@SuppressWarnings("rawtypes")
class StoreIterator implements Iterator {
private long position; // current position in the store
private boolean obsolete; // whether current position is already
// returned by next()
private long areaSize; // size of current area
StoreIterator() {
long end_of_data_area = -1;
try {
end_of_data_area = endOfDataAreaPointer();
} catch (Exception e) {
e.printStackTrace();
}
long[] header = new long[2];
// The first header
position = DATA_AREA_OFFSET;
obsolete = true;
while (position < end_of_data_area) {
try {
getAreaHeader(position, header);
} catch (Exception e) {
e.printStackTrace();
}
areaSize = (header[0] & 0x07FFFFFFFFFFFFFFFL);
if ((header[0] & 0x08000000000000000L) == 0) {
obsolete = false;
break;
}
position += areaSize;
}
if (obsolete)
position = -1;
}
public boolean hasNext() {
if (position == -1)
return false;
if (!obsolete)
return true;
else {
position = getNext();
if (position == -1)
return false;
else {
obsolete = false;
return true;
}
}
}
public Object next() throws java.util.NoSuchElementException {
if (!hasNext())
throw new java.util.NoSuchElementException(
"Store has no next !");
obsolete = true;
Object o = null;
try {
o = getArea(position);
} catch (Exception e) {
e.printStackTrace();
}
return (o);
}
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"remove() in StoreIterator is not supported yet!");
}
private long getNext() {
if (position == -1)
return -1;
long[] header = new long[2];
long end_of_data_area = -1;
try {
end_of_data_area = endOfDataAreaPointer();
} catch (Exception e) {
e.printStackTrace();
}
position += areaSize;
while (position < end_of_data_area) {
try {
getAreaHeader(position, header);
} catch (Exception e) {
e.printStackTrace();
}
areaSize = (header[0] & 0x07FFFFFFFFFFFFFFFL);
if ((header[0] & 0x08000000000000000L) == 0)
break;
position += areaSize;
}
if (position >= end_of_data_area)
position = -1;
return position;
}
}
}