// Copyright FreeHEP 2009
package org.freehep.util.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.AccessControlException;
/**
* Properties which are kept and searched on disk.
*
* Properties are kept sorted, and thus can only be added in sorted order.
* When read or searched either a RandomAccessFile or ResourceAsStream is used.
* Searching is done with a binarySearch.
*
* @author Mark Donszelmann (Mark.Donszelmann@gmail.com)
*/
public class OnDiskProperties {
private char equals = '=';
private char separator = ':';
private char pad = '_';
private BufferedOutputStream bos;
private String file;
private RandomAccessFile raf;
private int recordLength;
private long size = -1;
public OnDiskProperties(String file, int recordLength, boolean write)
throws FileNotFoundException {
this.file = file;
if (write) {
bos = new BufferedOutputStream(new FileOutputStream(file));
} else {
try {
if (new File(file).exists()) {
raf = new RandomAccessFile(file, "r");
}
} catch (AccessControlException ace) {
// ignore, will use input stream
}
}
if (recordLength < 2)
throw new IllegalArgumentException(
"OnDiskProperties: minimum recordlength is 2");
this.recordLength = recordLength + 1;
}
public void add(String key, String value) throws IOException {
if (bos == null)
throw new IllegalStateException(getClass() + ": opened for reading");
StringBuffer record = new StringBuffer();
record.append(key);
record.append("=");
record.append(value);
record.append(separator);
while (record.length() < recordLength - 1) {
record.append(pad);
}
bos.write(record.toString().getBytes());
bos.write('\n');
}
public String getProperty(String key) throws IOException {
if (bos != null) {
throw new IllegalStateException(getClass() + ": opened for writing");
}
byte[] record = binarySearch(key.getBytes());
if (record == null)
return null;
int equalsIndex = 1;
while ((record[equalsIndex] != equals) && (equalsIndex < record.length)) {
equalsIndex++;
}
if (equalsIndex >= record.length)
throw new IOException("Record without equal '" + equals + "' sign.");
equalsIndex++;
int length = 0;
while ((record[equalsIndex + length] != separator)
&& (equalsIndex + length) < record.length) {
length++;
}
if (equalsIndex >= record.length)
throw new IOException("Record without separator '" + separator
+ "' sign.");
return new String(record, equalsIndex, length);
}
public long size() throws IOException {
if (bos != null)
throw new IllegalStateException(getClass() + ": opened for writing");
if (size >= 0) return size;
if (raf != null) {
size = raf.length() / recordLength;
return size;
}
BufferedInputStream bis = new BufferedInputStream(getClass().getResourceAsStream(file));
int length = 0;
int s;
byte[] buffer = new byte[1000000];
while ((s = bis.read(buffer)) >= 0) {
length += s;
}
bis.close();
size = length / recordLength;
return size;
}
/**
* @throws IOException
*
*/
public void close() throws IOException {
if (bos != null)
bos.close();
if (raf != null)
raf.close();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
raf.close();
}
private byte[] binarySearch(byte[] name) throws IOException {
byte[] record = new byte[recordLength];
long low = 0;
long high = size() - 1;
while (low <= high) {
long mid = (low + high) >>> 1;
if (raf != null) {
raf.seek(mid * recordLength);
raf.read(record, 0, recordLength);
} else {
BufferedInputStream bis = new BufferedInputStream(getClass().getResourceAsStream(file));
bis.skip(mid * recordLength);
bis.read(record, 0, recordLength);
bis.close();
}
// compare(record, name)
for (int j = 0; j < name.length; j++) {
if (record[j] < name[j]) {
low = mid + 1;
break;
} else if (record[j] > name[j]) {
high = mid - 1;
break;
}
}
if (record[name.length - 1] == name[name.length - 1]) {
return record; // key found
}
}
return null; // key not found
}
}