/*
* Copyright 2011 Paula Gearon
*
* 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.mulgara.util.io;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.apache.log4j.Logger;
/**
* A memory mapped read-only version of LBufferedFile
* @author pag
*/
public class LMappedBufferedFileRO extends LMappedBufferedFile {
/** Logger. */
static final Logger logger = Logger.getLogger(LMappedBufferedFileRO.class);
/** The page size to use. */
public static final int PAGE_SIZE;
/** The size of a page to be mapped */
private static final int DEFAULT_PAGE_SIZE = 33554432; // 32 MB
static {
String pageSizeStr = System.getProperty(PAGE_SIZE_PROP);
int tmp = DEFAULT_PAGE_SIZE;
try {
if (pageSizeStr != null) tmp = Integer.parseInt(pageSizeStr);
} catch (NumberFormatException e) {
logger.warn("Property [" + PAGE_SIZE_PROP + "] is not a number [" + pageSizeStr + "]. Using default: " + tmp);
}
PAGE_SIZE = tmp;
}
/**
* Create a new buffered file for Long seeks.
* @param file The file to buffer.
* @throws IOException There was an error setting up initial mapping of the file.
*/
public LMappedBufferedFileRO(RandomAccessFile file) throws IOException {
super(file);
fc = file.getChannel();
mapFile();
if (logger.isDebugEnabled()) logger.debug("Mapping files with pages of: " + PAGE_SIZE);
}
@Override
public int getPageSize() {
return PAGE_SIZE;
}
@Override
public ByteBuffer read(long offset, int length) throws IOException {
// get the offset into the last buffer, this may be negative if before the last page
long lastPageOffset = (offset + length) - (PAGE_SIZE * (buffers.length - 1));
// if the offset is larger than the final page, then remap
if (lastPageOffset > PAGE_SIZE || lastPageOffset > buffers[buffers.length - 1].limit()) {
mapFile();
}
int page = (int)(offset / PAGE_SIZE);
int page_offset = (int)(offset % PAGE_SIZE);
if (page_offset + length <= PAGE_SIZE) {
ByteBuffer bb = buffers[page].asReadOnlyBuffer();
bb.position(page_offset);
bb.limit(page_offset + length);
if (page == buffers.length - 1) {
// In the final page, so make a copy
// This is needed in case of rollback, because the final page
// has the potential of being needed by a read-only transaction
// even when we need to remove that page to truncate the file.
// We have not synched on this page, but the GC will retry removing it
// with short delays, so it should get picked up despite our brief use.
ByteBuffer data = ByteBuffer.allocate(length);
bb.get(data.array(), 0, length);
return data;
} else {
// normal return of the mapped region slice
return bb.slice();
}
} else {
// TODO data must become a new write-through type
ByteBuffer data = ByteBuffer.allocate(length);
byte[] dataArray = data.array();
ByteBuffer tmp = buffers[page].asReadOnlyBuffer();
tmp.position(page_offset);
int firstSlice = PAGE_SIZE - page_offset;
tmp.get(dataArray, 0, firstSlice);
tmp = buffers[page + 1].asReadOnlyBuffer();
tmp.get(dataArray, firstSlice, length - firstSlice);
return data;
}
}
@Override
FileChannel.MapMode getMode() {
return FileChannel.MapMode.READ_ONLY;
}
byte[] dump() {
byte[] d = new byte[buffers[0].capacity()];
int p = buffers[0].position();
buffers[0].position(0);
buffers[0].get(d);
buffers[0].position(p);
return d;
}
}