/* * 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 com.github.ggrandes.kvstore.io; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.apache.log4j.Logger; /** * File for store one Long value with history for crash recovery * This class is Thread-Safe * * @author Guillermo Grandes / guillermo.grandes[at]gmail.com */ public class FileLongStore { private static final Logger log = Logger.getLogger(FileLongStore.class); /** * File associated to this store */ private File file = null; /** * RamdomAccessFile for this store */ private RandomAccessFile raf = null; /** * FileChannel for this store */ private FileChannel fc = null; /** * ByteBuffer (internal used) */ private final ByteBuffer buf = ByteBuffer.allocate(8); /** * In Valid State? */ private boolean validState = false; /** * Current Long */ private long value = 0; /** * Instantiate FileLongPointerStore * @param file name of file to open */ public FileLongStore(final String file) { this(new File(file)); } /** * Instantiate FileLongPointerStore * @param file file to open */ public FileLongStore(final File file) { this.file = file; } // ========= Open / Close ========= /** * Open file for read/write * @return true if valid state */ public boolean open() { return open(false); } /** * Open file * @param readOnly open in readOnly mode? * @return true if valid state */ public synchronized boolean open(final boolean readOnly) { if (isOpen()) { close(); } try { raf = new RandomAccessFile(file, readOnly ? "r" : "rw"); fc = raf.getChannel(); } catch(Exception e) { log.error("Exception in open()", e); try { close(); } catch(Exception ign) {} } validState = isOpen(); return validState; } /** * Close file */ public synchronized void close() { if (validState) sync(); try { fc.close(); } catch(Exception ign) {} try { raf.close(); } catch(Exception ign) {} raf = null; fc = null; // validState = false; } // ========= Info ========= /** * @return true if file is open */ public synchronized boolean isOpen() { try { if (fc != null) { return fc.isOpen(); } } catch(Exception ign) {} return false; } /** * Read value from file * @throws IOException */ public synchronized boolean canRead() throws IOException { if (!validState) throw new InvalidStateException(); final long offset = ((fc.size() & ~7) -8); return (offset >= 0); } /** * @return size of file in bytes * @see #getBlockSize() */ public synchronized long size() { try { return file.length(); } catch(Exception e) { log.error("Exception in size()", e); } return -1; } // ========= Destroy ========= /** * Truncate file */ public synchronized void clear() { if (!validState) throw new InvalidStateException(); try { buf.clear(); fc.position(0).truncate(0).force(true); close(); open(); } catch(Exception e) { log.error("Exception in clear()", e); } } /** * Delete file */ public synchronized void delete() { buf.clear(); close(); try { file.delete(); } catch(Exception ign) {} } // ========= Operations ========= /** * Set value */ public synchronized void set(final long value) { this.value = value; } /** * Get value */ public synchronized long get() { return value; } /** * Read value from file * @throws IOException */ public synchronized void read() throws IOException { if (!validState) throw new InvalidStateException(); final long offset = ((fc.size() & ~7) -8); if (offset < 0) throw new IOException("Empty file"); buf.clear(); int readed = fc.position(offset).read(buf); if (readed < 8) { // long 8 bytes throw new IOException("cant read long from file"); } buf.flip(); value = buf.getLong(); } /** * Write value to file * @throws IOException */ public void write() throws IOException { write(false); } /** * Write value to file * @param forceSync if true data must be synced to disk * @throws IOException */ public synchronized void write(final boolean forceSync) throws IOException { if (!validState) throw new InvalidStateException(); buf.clear(); buf.putLong(value); buf.flip(); fc.position(fc.size()).write(buf); // go end and write if (forceSync) fc.force(false); } /** * Write value to file and reduce size to minimal * @throws IOException */ public synchronized void pack() throws IOException { if (!validState) throw new InvalidStateException(); buf.clear(); buf.putLong(value); buf.flip(); fc.position(0).write(buf); // go begin and write fc.truncate(8).force(true); } /** * Forces any updates to this file to be written to the storage device that contains it. * @return false if exception occur */ public synchronized boolean sync() { if (!validState) throw new InvalidStateException(); try { fc.force(false); return true; } catch(Exception e) { log.error("Exception in sync()", e); } return false; } // ========= Exceptions ========= /** * Exception throwed when store is in invalid state (closed) */ public static class InvalidStateException extends RuntimeException { private static final long serialVersionUID = 42L; } // ========= END ========= }