/*
* Copyright 2011 Future Systems, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.confdb.file;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Iterator;
import java.util.ListIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class RevLogReader {
private static final byte[] MAGIC_STRING = "KRAKEN_CONFDB".getBytes();
private static final int REV_LOG_SIZE = 34;
private final Logger logger = LoggerFactory.getLogger(RevLogReader.class.getName());
/**
* collection log file handle
*/
private RandomAccessFile logRaf;
private int logHeaderLength;
/**
* doc file handle
*/
private RandomAccessFile docRaf;
private int docHeaderLength;
/**
* collection log buffer
*/
private byte[] buffer;
public RevLogReader(File logFile, File docFile) throws IOException {
this.logRaf = new RandomAccessFile(logFile, "r");
byte[] logHeader = new byte[16];
this.logRaf.read(logHeader);
if (!Arrays.equals(Arrays.copyOf(logHeader, 13), MAGIC_STRING))
throw new IOException("invalid log file");
if (logHeader[13] != 0x2)
throw new IOException("invalid log file version");
this.logHeaderLength = 16 + ((logHeader[14] & 0xFF) << 8 + (logHeader[15] & 0xFF));
this.docRaf = new RandomAccessFile(docFile, "r");
byte[] docHeader = new byte[16];
this.docRaf.read(docHeader);
if (!Arrays.equals(Arrays.copyOf(docHeader, 13), MAGIC_STRING))
throw new IOException("invalid doc file");
if (docHeader[13] != 0x3)
throw new IOException("invalid doc file version");
this.docHeaderLength = 16 + ((docHeader[14] & 0xFF) << 8 + (docHeader[15] & 0xFF));
this.buffer = new byte[REV_LOG_SIZE];
}
public long count() throws IOException {
return (logRaf.length() - logHeaderLength) / REV_LOG_SIZE;
}
public RevLog findDoc(int docId) throws IOException {
Iterator<RevLog> it = iterator();
while (it.hasNext()) {
RevLog log = it.next();
if (log.getDocId() == docId)
return log;
}
return null;
}
public RevLog findRev(long rev) throws IOException {
Iterator<RevLog> it = iterator();
while (it.hasNext()) {
RevLog log = it.next();
if (log.getRev() == rev)
return log;
}
return null;
}
/**
* read() does not return doc binary data. You should explicitly read doc
* binary using readDoc()
*
* @param index
* the index of items
* @return the revision log
* @throws IOException
*/
public RevLog read(long index) throws IOException {
logRaf.seek(logHeaderLength + index * REV_LOG_SIZE);
logRaf.read(buffer);
ByteBuffer bb = ByteBuffer.wrap(buffer);
return RevLog.deserialize(bb);
}
public byte[] readDoc(long offset, int length) throws IOException {
byte[] buf = new byte[length];
// skip also len(4) and option(4) field
docRaf.seek(docHeaderLength + offset + 8);
docRaf.read(buf);
return buf;
}
public ListIterator<RevLog> iterator() throws IOException {
return new RevLogIterator();
}
public ListIterator<RevLog> iterator(long index) throws IOException {
return new RevLogIterator(index);
}
public void close() {
try {
logRaf.close();
docRaf.close();
} catch (IOException e) {
logger.error("kraken confdb: cannot close index file", e);
}
}
public class RevLogIterator implements ListIterator<RevLog> {
private long count;
private long index;
public RevLogIterator() throws IOException {
this(0L);
}
public RevLogIterator(long index) throws IOException {
this.count = count();
this.index = this.count - index;
}
@Override
public boolean hasNext() {
return (index > 0);
}
@Override
public RevLog next() {
try {
return read(--index);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean hasPrevious() {
try {
if (index < count)
this.count = count();
} catch (IOException e) {
}
return (index < count);
}
@Override
public RevLog previous() {
try {
return read(index++);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public int nextIndex() {
return (int) (index - 1);
}
@Override
public int previousIndex() {
return (int) index;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void set(RevLog e) {
throw new UnsupportedOperationException();
}
@Override
public void add(RevLog e) {
throw new UnsupportedOperationException();
}
}
}