/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.util;
// Java 2 standard packages
import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import org.apache.log4j.Logger;
import org.mulgara.util.io.MappingUtil;
/**
* @created 2003-01-09
*
* @author David Makepeace
*
* @version $Revision: 1.9 $
*
* @modified $Date: 2005/01/05 04:59:29 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2001-2003 <A href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</A>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public final class MappedIntFile extends IntFile {
private static final Logger logger = Logger.getLogger(MappedIntFile.class);
private final static long REGION_SIZE_B = 8 * 1024 * 1024;
private final static long REGION_SIZE_I = REGION_SIZE_B / SIZEOF_INT;
private final static long REGION_SIZE = REGION_SIZE_B / SIZEOF_LONG;
/* The initial size of each of the Buffer arrays. */
private final static int INITIAL_NR_REGIONS = 1024;
private MappedByteBuffer[] mappedByteBuffers;
private IntBuffer[] intBuffers;
private LongBuffer[] longBuffers;
/* The number of regions that are currently mapped. */
private int nrMappedRegions = 0;
/**
* Constructs a MappedIntFile for the specified file.
*
* @param file The file to open.
* @throws IOException if an I/O error occurs.
*/
MappedIntFile(File file) throws IOException {
super(file);
int nrRegions = (int) (((size + REGION_SIZE) - 1) / REGION_SIZE);
int maxNrRegions = INITIAL_NR_REGIONS;
while (maxNrRegions < nrRegions) {
maxNrRegions *= 2;
}
mappedByteBuffers = new MappedByteBuffer[maxNrRegions];
intBuffers = new IntBuffer[maxNrRegions];
longBuffers = new LongBuffer[maxNrRegions];
if (nrRegions > 0) {
mapFile(nrRegions);
}
}
/**
* Sets the size of the file in longs. The file will not be expanded if it is
* already large enough. The file will be truncated to the correct length
* when {@link #close} is called.
*
* @param newSize the new size of the file in longs.
* @throws IOException if an I/O error occurs.
*/
public void setSize(long newSize) throws IOException {
if (newSize < 0) {
throw new IllegalArgumentException("newSize is negative.");
}
long prevSize = size;
super.setSize(newSize);
if (newSize <= prevSize) {
return;
}
// Call mapFile() if the file must grow in size.
int nrRegions = (int) (((newSize + REGION_SIZE) - 1) / REGION_SIZE);
if (nrRegions > nrMappedRegions) {
mapFile(nrRegions);
}
long key = prevSize;
try {
for (; key < newSize; ++key) {
putLong(key, 0);
}
} catch (NullPointerException e) {
logger.error("Out of range during resize. prevSize=" + prevSize + ", newSize=" + newSize + ", bad_offset=" + key);
throw new IOException("Out of range during resize. prevSize=" + prevSize + ", newSize=" + newSize + ", bad_offset=" + key);
}
}
/**
* Reads the long at the specified offset into the file.
*
* @param key The offset into the file in longs.
* @return the long at the specified offset.
*/
public long getLong(long key) {
assert key >= 0;
if (key >= size) {
return 0;
}
int regionNr = (int) (key / REGION_SIZE);
int offset = (int) (key % REGION_SIZE);
return longBuffers[regionNr].get(offset);
}
/**
* Reads the int at the specified offset into the file.
*
* @param key The offset into the file in ints.
* @return the int at the specified offset.
*/
public int getInt(long key) {
assert key >= 0;
if ((key / (SIZEOF_LONG / SIZEOF_INT)) >= size) {
return 0;
}
int regionNr = (int) (key / REGION_SIZE_I);
int offset = (int) (key % REGION_SIZE_I);
return intBuffers[regionNr].get(offset);
}
/**
* Reads the byte at the specified offset into the file.
*
* @param key The offset into the file in bytes.
* @return the byte at the specified offset.
*/
public byte getByte(long key) {
assert key >= 0;
if ((key / SIZEOF_LONG) >= size) {
return 0;
}
int regionNr = (int) (key / REGION_SIZE_B);
int offset = (int) (key % REGION_SIZE_B);
return mappedByteBuffers[regionNr].get(offset);
}
/**
* Writes a long to the file at the specified offset.
*
* @param key The offset into the file in longs.
* @param l The long to write.
* @throws IOException if an I/O error occurs.
*/
public void putLong(long key, long l) throws IOException {
assert key >= 0;
// Auto-expand.
if (key >= size) {
if (l == 0) {
return;
}
try {
setSize(key + 1);
} catch (IOException e) {
String m = "Exception mapping " + key + "=>" + l + ". ";
logger.fatal(m, e);
throw new IOException(m + e.getMessage());
}
}
int regionNr = (int) (key / REGION_SIZE);
int offset = (int) (key % REGION_SIZE);
longBuffers[regionNr].put(offset, l);
}
/**
* Writes an int to the file at the specified offset.
*
* @param key The offset into the file in ints.
* @param i The int to write.
* @throws IOException if an I/O error occurs.
*/
public void putInt(long key, int i) throws IOException {
assert key >= 0;
// Auto-expand.
long lKey = key / (SIZEOF_LONG / SIZEOF_INT);
if (lKey >= size) {
if (i == 0) {
return;
}
setSize(lKey + 1);
}
int regionNr = (int) (key / REGION_SIZE_I);
int offset = (int) (key % REGION_SIZE_I);
intBuffers[regionNr].put(offset, i);
}
/**
* Writes a byte to the file at the specified offset.
*
* @param key The offset into the file in bytes.
* @param b The byte to write.
* @throws IOException if an I/O error occurs.
*/
public void putByte(long key, byte b) throws IOException {
assert key >= 0;
// Auto-expand.
long lKey = key / SIZEOF_LONG;
if (lKey >= size) {
if (b == 0) {
return;
}
setSize(lKey + 1);
}
int regionNr = (int) (key / REGION_SIZE_B);
int offset = (int) (key % REGION_SIZE_B);
mappedByteBuffers[regionNr].put(offset, b);
}
/**
* Ensures that all written data has been forced to physical storage
* before returning.
*
* @throws IOException if an I/O error occurs.
*/
public synchronized void force() throws IOException {
for (int i = 0; i < nrMappedRegions; ++i) {
mappedByteBuffers[i].force();
}
}
/**
* Truncates the file to zero length.
*
* @throws IOException if an I/O error occurs.
*/
public void clear() throws IOException {
int maxNrRegions = INITIAL_NR_REGIONS;
mappedByteBuffers = new MappedByteBuffer[maxNrRegions];
intBuffers = new IntBuffer[maxNrRegions];
longBuffers = new LongBuffer[maxNrRegions];
nrMappedRegions = 0;
super.clear();
}
/**
* Sets references to MappedByteBuffers and any other Buffers that directly
* or indirectly wrap a MappedByteBuffer to null so that the
* MappedByteBuffers can be garbage collected and cause the underlying
* OS file mappings to be unmapped. Some operating systems will not allow
* a file to be deleted or truncated while it is mapped.
*/
public synchronized void unmap() {
// Discard the file mappings.
MappingUtil.release(mappedByteBuffers);
mappedByteBuffers = null;
intBuffers = null;
longBuffers = null;
nrMappedRegions = 0;
}
/**
* Expands the file to contain nrRegions regions and maps the additional
* regions.
*
* @param nrRegions PARAMETER TO DO
* @throws IOException if an I/O error occurs.
*/
private synchronized void mapFile(int nrRegions) throws IOException {
assert nrRegions > nrMappedRegions;
// Check if the buffer arrays need to be increased in size.
int maxNrRegions = mappedByteBuffers.length;
if (maxNrRegions < nrRegions) {
do {
maxNrRegions *= 2;
} while (maxNrRegions < nrRegions);
MappedByteBuffer[] mbbs = new MappedByteBuffer[maxNrRegions];
IntBuffer[] ibs = new IntBuffer[maxNrRegions];
LongBuffer[] lbs = new LongBuffer[maxNrRegions];
System.arraycopy(mappedByteBuffers, 0, mbbs, 0, nrMappedRegions);
System.arraycopy(intBuffers, 0, ibs, 0, nrMappedRegions);
System.arraycopy(longBuffers, 0, lbs, 0, nrMappedRegions);
mappedByteBuffers = mbbs;
intBuffers = ibs;
longBuffers = lbs;
}
long mappedSize = nrMappedRegions * REGION_SIZE_B;
// Get the current file size.
long currentFileSize = fc.size();
if (currentFileSize < mappedSize) {
throw new Error("File has shrunk: " + file);
}
long fileSize = nrRegions * REGION_SIZE_B;
// Expand the file.
raf.setLength(fileSize);
for (int regionNr = nrMappedRegions; regionNr < nrRegions; ++regionNr) {
int retries = 10;
for (;;) {
try {
MappedByteBuffer mbb = fc.map(
FileChannel.MapMode.READ_WRITE, regionNr * REGION_SIZE_B,
REGION_SIZE_B
);
mbb.order(byteOrder);
mappedByteBuffers[regionNr] = mbb;
mbb.rewind();
intBuffers[regionNr] = mbb.asIntBuffer();
longBuffers[regionNr] = mbb.asLongBuffer();
break;
} catch (IOException ex) {
// Rethrow the exception if we are out of retries.
if (retries-- == 0) throw ex;
// Let some old mappings go away and try again.
MappingUtil.systemCleanup();
}
}
}
nrMappedRegions = nrRegions;
}
}