/******************************************************************************* * Copyright (c) 2012, 2014 Ericsson * Copyright (c) 2010, 2011 École Polytechnique de Montréal * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com> * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package fr.inria.linuxtools.internal.statesystem.core.backend.historytree; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; /** * This class abstracts inputs/outputs of the HistoryTree nodes. * * It contains all the methods and descriptors to handle reading/writing nodes * to the tree-file on disk and all the caching mechanisms. * * This abstraction is mainly for code isolation/clarification purposes. * Every HistoryTree must contain 1 and only 1 HT_IO element. * * @author Alexandre Montplaisir * */ class HT_IO { /* Configuration of the History Tree */ private final HTConfig fConfig; /* Fields related to the file I/O */ private final FileInputStream fis; private final FileOutputStream fos; private final FileChannel fcIn; private final FileChannel fcOut; // TODO test/benchmark optimal cache size private final int CACHE_SIZE = 256; private final HTNode fNodeCache[] = new HTNode[CACHE_SIZE]; /** * Standard constructor * * @param config * The configuration object for the StateHistoryTree * @param newFile * Flag indicating that the file must be created from scratch * @throws IOException * An exception can be thrown when file cannot be accessed */ public HT_IO(HTConfig config, boolean newFile) throws IOException { fConfig = config; File historyTreeFile = config.getStateFile(); if (newFile) { boolean success1 = true; /* Create a new empty History Tree file */ if (historyTreeFile.exists()) { success1 = historyTreeFile.delete(); } boolean success2 = historyTreeFile.createNewFile(); if (!(success1 && success2)) { /* It seems we do not have permission to create the new file */ throw new IOException("Cannot create new file at " + //$NON-NLS-1$ historyTreeFile.getName()); } fis = new FileInputStream(historyTreeFile); fos = new FileOutputStream(historyTreeFile, false); } else { /* * We want to open an existing file, make sure we don't squash the * existing content when opening the fos! */ this.fis = new FileInputStream(historyTreeFile); this.fos = new FileOutputStream(historyTreeFile, true); } this.fcIn = fis.getChannel(); this.fcOut = fos.getChannel(); } /** * Read a node from the file on disk. * * @param seqNumber * The sequence number of the node to read. * @return The object representing the node * @throws ClosedChannelException * Usually happens because the file was closed while we were * reading. Instead of using a big reader-writer lock, we'll * just catch this exception. */ public synchronized HTNode readNode(int seqNumber) throws ClosedChannelException { /* Do a cache lookup */ int offset = seqNumber & (CACHE_SIZE - 1); HTNode readNode = fNodeCache[offset]; if (readNode != null && readNode.getSequenceNumber() == seqNumber) { return readNode; } /* Lookup on disk */ try { seekFCToNodePos(fcIn, seqNumber); readNode = HTNode.readNode(fConfig, fcIn); /* Put the node in the cache. */ fNodeCache[offset] = readNode; return readNode; } catch (ClosedChannelException e) { throw e; } catch (IOException e) { /* Other types of IOExceptions shouldn't happen at this point though */ e.printStackTrace(); return null; } } public synchronized void writeNode(HTNode node) { try { /* Insert the node into the cache. */ int seqNumber = node.getSequenceNumber(); int offset = seqNumber & (CACHE_SIZE - 1); fNodeCache[offset] = node; /* Position ourselves at the start of the node and write it */ seekFCToNodePos(fcOut, seqNumber); node.writeSelf(fcOut); } catch (IOException e) { /* If we were able to open the file, we should be fine now... */ e.printStackTrace(); } } public FileChannel getFcOut() { return this.fcOut; } public FileInputStream supplyATReader(int nodeOffset) { try { /* * Position ourselves at the start of the Mapping section in the * file (which is right after the Blocks) */ seekFCToNodePos(fcIn, nodeOffset); } catch (IOException e) { e.printStackTrace(); } return fis; } public synchronized void closeFile() { try { fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } public synchronized void deleteFile() { closeFile(); File historyTreeFile = fConfig.getStateFile(); if (!historyTreeFile.delete()) { /* We didn't succeed in deleting the file */ //TODO log it? } } /** * Seek the given FileChannel to the position corresponding to the node that * has seqNumber * * @param fc the channel to seek * @param seqNumber the node sequence number to seek the channel to * @throws IOException */ private void seekFCToNodePos(FileChannel fc, int seqNumber) throws IOException { /* * Cast to (long) is needed to make sure the result is a long too and * doesn't get truncated */ fc.position(HistoryTree.TREE_HEADER_SIZE + ((long) seqNumber) * fConfig.getBlockSize()); } }