All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atomikos.util.VersionedFile Maven / Gradle / Ivy

/**
 * Copyright (C) 2000-2020 Atomikos 
 *
 * LICENSE CONDITIONS
 *
 * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
 */

package com.atomikos.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;


 /**
  * A file with underlying version capabilities to ensure safe overwriting.
  *
  * Unlike regular files, this type of file is safe w.r.t. (over)writing
  * a previous version: a backup version of the original content is kept
  * until the client application explicitly states that a consistent
  * new version has been written.
  *
  */

public class VersionedFile
{

	private static final String FILE_SEPARATOR = String.valueOf(File.separatorChar);
	private String baseDir;
	private String suffix;
	private String baseName;

	//state attributes below

	private long version;
	private FileInputStream inputStream;

	private RandomAccessFile randomAccessFile;


	/**
	 * Creates a new instance based on the given name parameters.
	 * The actual complete name(s) of the physical file(s) will be based on a version number
	 * inserted in between, to identify versions.
	 *
	 * @param baseDir The base folder.
	 * @param baseName The base name for of the file path/name.
	 * @param suffix The suffix to append to the complete file name.
	 */
	public VersionedFile ( String baseDir , String baseName , String suffix )
	{
		
		if(!baseDir.endsWith(FILE_SEPARATOR)) {
			baseDir += FILE_SEPARATOR;
		}
		this.baseDir = baseDir;
		this.suffix = suffix;
		this.baseName = baseName;
		resetVersion();
	}

	private void resetVersion()
	{
		this.version = extractLastValidVersionNumberFromFileNames();
	}

	private long extractLastValidVersionNumberFromFileNames() {
		long version = -1;
        File cd = new File ( getBaseDir() );
        String[] names = cd.list ( new FilenameFilter () {
            public boolean accept ( File dir , String name )
            {
                return (name.startsWith ( getBaseName() ) && name
                        .endsWith ( getSuffix() ));
            }
        } );
        if ( names!= null ) {
        	for ( int i = 0; i < names.length; i++ ) {
        		long sfx = extractVersion ( names[i] );
        		if ( version < 0 || sfx < version )
        			version = sfx;
        	}
        }

        return version;
	}

	private long extractVersion ( String name )
    {
		long ret  = 0;
        int lastpos = name.lastIndexOf ( '.' );
        int startpos = getBaseName().length ();
        String suffix = name.substring ( startpos, lastpos );
        try {

			ret = Long.valueOf( suffix );
		} catch ( NumberFormatException e ) {
			IllegalArgumentException err = new IllegalArgumentException ( "Error extracting version from file: " + name+" in " + getBaseDir() );
			err.initCause ( e );
			throw err;
		}
        return ret;
    }

	private String getBackupVersionFileName()
	{
		return getBaseUrl() + (version - 1) + getSuffix();
	}

	public String getCurrentVersionFileName()
	{
		return getBaseUrl() + version + getSuffix();
	}

	public String getBaseUrl()
	{
		return baseDir + baseName;
	}

	public String getBaseDir()
	{
		return this.baseDir;
	}

	public String getBaseName()
	{
		return this.baseName;
	}

	public String getSuffix()
	{
		return this.suffix;
	}

	/**
	 * Opens the last valid version for reading.
	 *
	 * @return A stream to read the last valid contents
	 * of the file: either the backup version (if present)
	 * or the current (and only) version if no backup is found.
	 *
	 * @throws IllegalStateException If a newer version was opened for writing.
	 * @throws FileNotFoundException If no last version was found.
	 */
	public FileInputStream openLastValidVersionForReading()
	throws IllegalStateException, FileNotFoundException
	{
		if ( randomAccessFile != null ) throw new IllegalStateException ( "Already started writing." );
		inputStream = new FileInputStream ( getCurrentVersionFileName() );
		return inputStream;
	}

	/**
	 * Opens a new version for writing to. Note that
	 * this new version is tentative and cannot be read
	 * by {@link #openLastValidVersionForReading()} until
	 * {@link #discardBackupVersion()} is called.
	 *
	 * @return A stream for writing to.
	 * @throws IllegalStateException If called more than once
	 * without a close in between.
	 * @throws IOException If the file cannot be opened for writing.
	 */
	public FileOutputStream openNewVersionForWriting() throws IOException
	{
		openNewVersionForNioWriting();
		return new FileOutputStream(randomAccessFile.getFD());
	}

	/**
	 * Opens a new version for writing to. Note that
	 * this new version is tentative and cannot be read
	 * by {@link #openLastValidVersionForReading()} until
	 * {@link #discardBackupVersion()} is called.
	 *
	 * @return A file for writing to.
	 * @throws IOException
	 *
	 * @throws IllegalStateException If called more than once
	 * without a close in between.
	 * @throws FileNotFoundException If the file cannot be opened for writing.
	 * @throws IOException 
	 */
	public FileChannel openNewVersionForNioWriting() throws FileNotFoundException
	{
		if ( randomAccessFile != null ) throw new IllegalStateException ( "Already writing a new version." );
		version++;
		randomAccessFile = new RandomAccessFile(getCurrentVersionFileName(), "rw");
		return randomAccessFile.getChannel();
	}
	/**
	 * Discards the backup version (if any).
	 * After calling this method, the newer version
	 * produced after calling {@link #openNewVersionForWriting()}
	 * becomes valid for reading next time when
	 * {@link #openLastValidVersionForReading()} is called.
	 *
	 * Note: it is the caller's responsibility to make sure that
	 * all new data has been flushed to disk before calling this method!
	 *
	 * @throws IllegalStateException If {@link #openNewVersionForWriting()} has not been called yet.
	 * @throws IOException If the previous version exists but could no be deleted.
	 */
	public void discardBackupVersion() throws IllegalStateException, IOException
	{
		if ( randomAccessFile == null ) throw new IllegalStateException ( "No new version yet!" );
		String fileName = getBackupVersionFileName();
		
		File temp = new File ( fileName );
        if ( temp.exists() && !temp.delete() ) throw new IOException ( "Failed to delete backup version: " + fileName );

	}

	/**
	 * Closes any open resources and resets the file for reading again.
	 * @throws IOException If the output stream could not be closed.
	 */

	public void close() throws IOException
	{
		resetVersion();
		if ( inputStream != null ) {
			try {
				inputStream.close();
			} catch (IOException e) {
				//don't care and won't happen: closing an input stream
				//does nothing says the JDK javadoc!
			} finally {
				inputStream = null;
			}
		}
		if ( randomAccessFile != null ) {
			try {
				if ( randomAccessFile.getFD().valid() ) randomAccessFile.close();
			} finally {
				randomAccessFile = null;
			}
		}
	}

	public long getSize()
	{
		long res = -1;
		File f = new File ( getCurrentVersionFileName() );
		res = f.length();
		return res;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy