/* * 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.io.RandomAccessFile; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import org.apache.log4j.Logger; import org.mulgara.util.io.MappingUtil; /** * A file consisting of longs, ints or bytes that can be accessed concurrently * by multiple readers and a single writer without additional thread * synchronization. * * @created 2001-09-20 * * @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 <a href="http://www.pisoftware.com/">Plugged In * Software Pty Ltd</a> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public abstract class IntFile implements LongMapper { protected final static int SIZEOF_LONG = 8; protected final static int SIZEOF_INT = 4; protected final static long MASK32 = 0xffffffffL; protected final static ByteOrder byteOrder; // The byte order to use. private static final Logger logger = Logger.getLogger(IntFile.class); static { String useByteOrderProp = System.getProperty("mulgara.xa.useByteOrder"); ByteOrder bo = ByteOrder.nativeOrder(); // Default. if (useByteOrderProp != null) { if (useByteOrderProp.equalsIgnoreCase("native")) { bo = ByteOrder.nativeOrder(); } else if (useByteOrderProp.equalsIgnoreCase("big_endian")) { bo = ByteOrder.BIG_ENDIAN; } else if (useByteOrderProp.equalsIgnoreCase("little_endian")) { bo = ByteOrder.LITTLE_ENDIAN; } else { logger.warn("Invalid value for property mulgara.xa.useByteOrder: " + useByteOrderProp); } } byteOrder = bo; } /** * The name of the open file. */ protected File file; /** * A RandomAccessFile object for the file. */ protected RandomAccessFile raf; /** * A FileChannel object for the file. */ protected FileChannel fc; /** * Number of valid long entries in the file. */ protected long size; /** * The base class constructor. * * @param file The file to open. * @throws IOException if an I/O error occurs. */ protected IntFile(File file) throws IOException { this.file = file; raf = new RandomAccessFile(file, "rw"); fc = raf.getChannel(); size = fc.size() / SIZEOF_LONG; // Truncate the file to a multiple of SIZEOF_LONG if its size is not // already. long fileSize = size * SIZEOF_LONG; if (fileSize < fc.size()) { truncate(fileSize); logger.warn("File size was not a multiple of SIZEOF_LONG: \"" + file + "\"."); } } /** * Returns a newly constructed IntFile for the specified file. * The value of the system property mulgara.xa.forceIOType determines * if the file is accessed using mapped or explicit I/O. * * @param file The name of the file to open. * @return a newly created IntFile object. * @throws IOException if an I/O error occurs. */ public static IntFile open(File file) throws IOException { boolean explicitIO = false; String forceIOTypeProp = System.getProperty("mulgara.xa.forceIOType"); if (forceIOTypeProp != null) { if (forceIOTypeProp.equalsIgnoreCase("mapped")) { explicitIO = false; } else if (forceIOTypeProp.equalsIgnoreCase("explicit")) { explicitIO = true; } else { logger.warn("Invalid value for property mulgara.xa.forceIOType: " + forceIOTypeProp); } } return open(file, explicitIO); } /** * Returns a newly constructed IntFile for the specified file. * * @param file The name of the file to open. * @param explicitIO true if the file should be accessed using explicit I/O * and false if mapped I/O should be used. * @return a newly created IntFile object. * @throws IOException if an I/O error occurs. */ public static IntFile open(File file, boolean explicitIO) throws IOException { // TODO fix bug in ExplicitIntFile. Currently always use MappedIntFile. //if (explicitIO) { // return new ExplicitIntFile(file); //} else { return new MappedIntFile(file); //} } /** * Returns a newly constructed IntFile for the specified file. * The value of the system property mulgara.xa.forceIOType determines * if the file is accessed using mapped or explicit I/O. * * @param fileName The name of the file to open. * @return a newly created IntFile object. * @throws IOException if an I/O error occurs. */ public static IntFile open(String fileName) throws IOException { return open(new File(fileName)); } /** * Sets the size of the file in longs. * * @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."); } size = newSize; } /** * 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 abstract long getLong(long key); /** * 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 abstract int getInt(long key); /** * Reads the unsigned int at the specified offset into the file. * * @param key The offset into the file in ints. * @return the unsigned int at the specified offset. */ public long getUInt(long key) { return (long) getInt(key) & MASK32; } /** * 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 abstract byte getByte(long key); /** * Gets the number of valid long entries in the file. * * @return the number of valid long entries in the file. */ public long getSize() { return size; } /** * 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 abstract void putLong(long key, long l) throws IOException; /** * 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 abstract void putInt(long key, int i) throws IOException; /** * Writes an unsigned int to the file at the specified offset. * * @param key The offset into the file in ints. * @param ui The unsigned int to write. * @throws IOException if an I/O error occurs. */ public void putUInt(long key, long ui) throws IOException { assert ui >= 0 && ui < (1L << 32); putInt(key, (int) ui); } /** * 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 abstract void putByte(long key, byte b) throws IOException; /** * Ensures that all written data has been forced to physical storage * before returning. * * @throws IOException if an I/O error occurs. */ public void force() throws IOException { if (fc == null) { throw new IllegalStateException("FileChannel is null"); } fc.force(true); } /** * Truncates the file to zero length. * * @throws IOException if an I/O error occurs. */ public void clear() throws IOException { if (fc == null) { throw new IllegalStateException("FileChannel is null"); } size = 0; truncate(0L); fc.force(true); } /** * 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 void unmap() { // NO-OP } /** * Close the file after truncating it to the actual size. * * @throws IOException if an I/O error occurs. */ public synchronized void close() throws IOException { close(true); } /** * Close and delete the file. * * @throws IOException if an I/O error occurs. */ public synchronized void delete() throws IOException { try { // Close but don't bother truncating the file. close(false); } finally { if (file != null) { int retries = 10; while (!file.delete() && file.isFile() && retries-- > 0) { // Causing any MappedByteBuffers to be unmapped may allow the // file to be deleted. This may be needed for Windows. MappingUtil.systemCleanup(); } if (retries < 0) { logger.warn("NOTE: Failed to delete: " + file); } file = null; } } } /** * Create a temporary IntFile, using a name based on a given pattern. * @param namePattern The base for the name to use. * @return A new IntFile object. * @throws IOException Due to a file access error. */ public static IntFile newTempIntFile(String namePattern) throws IOException { File file = null; try { file = TempDir.createTempFile(namePattern, null); return IntFile.open(file); } catch (IOException e) { if (file != null) file.delete(); throw e; } } /** * Close and optionally truncate the file. * @throws IOException if an I/O error occurs. */ private void close(boolean truncateFile) throws IOException { // Truncate the file to the correct size. boolean success = false; try { unmap(); if (fc != null && fc.isOpen()) { try { if (truncateFile) { truncate(size * SIZEOF_LONG); } } finally { fc.close(); } } success = true; } finally { fc = null; if (raf != null) { try { raf.close(); } catch (IOException e) { if (success) throw e; // Everything else worked, rethrow this. else logger.info("Suppressing I/O exception closing failed IntFile", e); // Log and ignore, already got another exception. } finally { raf = null; } } } } /** * Truncates the file to the specified size. * * @param fileSize the size in bytes */ protected void truncate(long fileSize) { if (fc == null) { throw new IllegalStateException("FileChannel is null"); } try { MappingUtil.truncate(fc, fileSize); } catch (IOException ex) { logger.warn( "NOTE: Could not truncate file: \"" + file + "\" to size: " + fileSize + " - deferring until next time the file is opened." ); logger.info("Could not truncate file", ex); } } }