diff options
Diffstat (limited to 'bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/framework/internal/reliablefile/ReliableFile.java')
-rw-r--r-- | bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/framework/internal/reliablefile/ReliableFile.java | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/framework/internal/reliablefile/ReliableFile.java b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/framework/internal/reliablefile/ReliableFile.java new file mode 100644 index 000000000..f6c4635b8 --- /dev/null +++ b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/framework/internal/reliablefile/ReliableFile.java @@ -0,0 +1,536 @@ +/******************************************************************************* + * Copyright (c) 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.reliablefile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import org.eclipse.osgi.framework.internal.defaultadaptor.AdaptorMsg; + +/** + * ReliableFile class used by ReliableFileInputStream and ReliableOutputStream. + * This class encapsulates all the logic for reliable file support. + */ + +public class ReliableFile +{ + /** + * Extension of tmp file used during writing. + * A reliable file with this extension should + * never be directly used. + */ + public static final String tmpExt = ".tmp"; + + /** + * Extension of previous generation of the reliable file. + * A reliable file with this extension should + * never be directly used. + */ + public static final String oldExt = ".bak"; + + /** + * Extension of next generation of the reliable file. + * A reliable file with this extension should + * never be directly used. + */ + public static final String newExt = ".new"; + + /** List of active ReliableFile objects: File => ReliableFile */ + private static Hashtable files; + + static + { + files = new Hashtable(30); /* initialize files */ + } + + /** File object for original file */ + private File orgFile; + + /** File object for the temporary output file */ + private File tmpFile; + + /** File object for old data file */ + private File oldFile; + + /** File object for file containing new data */ + private File newFile; + + + /** True if this object is open for read or write */ + private boolean locked; + + /** Use code of this object. When zero this object must be removed from files */ + private int use; + + /** + * ReliableFile object factory. This method is called by ReliableFileInputStream + * and ReliableFileOutputStream to get a ReliableFile object for a target file. + * If the object is in the cache, the cached copy is returned. + * Otherwise a new ReliableFile object is created and returned. + * The use count of the returned ReliableFile object is incremented. + * + * @param name Name of the target file. + * @return A ReliableFile object for the target file. + * @throws IOException If the target file is a directory. + */ + static ReliableFile getReliableFile(String name) throws IOException + { + return getReliableFile(new File(name)); + } + + /** + * ReliableFile object factory. This method is called by ReliableFileInputStream + * and ReliableFileOutputStream to get a ReliableFile object for a target file. + * If the object is in the cache, the cached copy is returned. + * Otherwise a new ReliableFile object is created and returned. + * The use count of the returned ReliableFile object is incremented. + * + * @param file File object for the target file. + * @return A ReliableFile object for the target file. + * @throws IOException If the target file is a directory. + */ + static ReliableFile getReliableFile(File file) throws IOException + { + if (file.isDirectory()) + { + throw new FileNotFoundException(AdaptorMsg.formatter.getString("RELIABLEFILE_FILE_IS_DIRECTORY")); + } + + synchronized (files) + { + ReliableFile reliable = (ReliableFile)files.get(file); + + if (reliable == null) + { + reliable = new ReliableFile(file); + + files.put(file, reliable); + } + + reliable.use++; + + return reliable; + } + } + + /** + * Decrement this object's use count. If the use count + * drops to zero, remove this object from the cache. + * + */ + private void release() + { + synchronized (files) + { + use--; + + if (use <= 0) + { + files.remove(orgFile); + } + } + } + + /** + * Private constructor used by the static getReliableFile factory methods. + * + * @param file File object for the target file. + */ + private ReliableFile(File file) + { + String name = file.getPath(); + + orgFile = file; + tmpFile = new File(name + tmpExt); + oldFile = new File(name + oldExt); + newFile = new File(name + newExt); + use = 0; + locked = false; + } + + /** + * Recovers the target file, if necessary, and returns an InputStream + * object for reading the target file. + * + * @return An InputStream object which can be used to read the target file. + * @throws IOException If an error occurs preparing the file. + */ + synchronized InputStream getInputStream() throws IOException + { + try + { + lock(); + } + catch (IOException e) + { + /* the lock request failed; decrement the use count */ + release(); + + throw e; + } + + try + { + recoverFile(); + + return new FileInputStream(orgFile.getPath()); + } + catch (IOException e) + { + unlock(); + + release(); + + throw e; + } + } + + /** + * Close the target file for reading. + * + * @throws IOException If an error occurs closing the file. + */ + /* This method does not need to be synchronized if it only calls release. */ + void closeInputFile() throws IOException + { + unlock(); + + release(); + } + + /** + * Recovers the target file, if necessary, and returns an OutputStream + * object for writing the target file. + * + * @return An OutputStream object which can be used to write the target file. + * @throws IOException If an error occurs preparing the file. + */ + synchronized OutputStream getOutputStream(boolean append) throws IOException + { + try + { + lock(); + } + catch (IOException e) + { + /* the lock request failed; decrement the use count */ + release(); + + throw e; + } + + try + { + if (append) + { + recoverFile(); + + if (orgFile.exists()) + { + cp(orgFile, tmpFile); + } + } + + return new FileOutputStream(tmpFile.getPath(), append); + } + catch (IOException e) + { + unlock(); + + release(); + + throw e; + } + } + + /** + * Close the target file for reading. + * + * @throws IOException If an error occurs closing the file. + */ + synchronized void closeOutputFile() throws IOException + { + try + { + boolean orgExists = orgFile.exists(); + boolean newExists = newFile.exists(); + + if (newExists) + { + rm(oldFile); + mv(newFile, oldFile); + } + + mv(tmpFile, newFile); + + if (orgExists) + { + if (newExists) + { + rm(orgFile); + } + else + { + rm(oldFile); + mv(orgFile, oldFile); + } + } + + mv(newFile, orgFile); + } + finally + { + unlock(); + + release(); + } + } + + /** + * This method recovers the reliable file if necessary. + * + * @throws IOException If an error occurs recovering the file. + */ + private void recoverFile() throws IOException + { + boolean orgExists = orgFile.exists(); + boolean newExists = newFile.exists(); + boolean oldExists = oldFile.exists(); + + if (newExists) + { + if (orgExists && !oldExists) + { + mv(orgFile, oldFile); + } + + cp(newFile, orgFile); + + if (orgExists || oldExists) + { + rm(newFile); + } + else + { + mv(newFile, oldFile); + } + } + else + { + if (oldExists && !orgExists) + { + cp(oldFile, orgFile); + } + } + } + + /** + * Lock the target file. + * + * @throws IOException If the file is already locked. + */ + private void lock() throws IOException + { + if (locked) + { + throw new FileNotFoundException(AdaptorMsg.formatter.getString("RELIABLEFILE_FILE_LOCKED")); + } + + locked = true; + } + + /** + * Unlock the target file. + */ + private void unlock() + { + locked = false; + } + + /** + * Rename a file. + * + * @param from The original file. + * @param to The new file name. + * @throws IOException If the rename failed. + */ + private static void mv(File from, File to) throws IOException + { + if (!from.renameTo(to)) + { + throw new IOException(AdaptorMsg.formatter.getString("RELIABLEFILE_RENAME_FAILED")); + } + } + + /** + * Copy a file. + * + * @param from The original file. + * @param to The target file. + * @throws IOException If the copy failed. + */ + private static final int CP_BUF_SIZE = 4096; + private static void cp(File from, File to) throws IOException + { + FileInputStream in = null; + FileOutputStream out = null; + + try + { + out = new FileOutputStream(to); + + int length = (int)from.length(); + if (length > 0) + { + if (length > CP_BUF_SIZE) + { + length = CP_BUF_SIZE; + } + + in = new FileInputStream(from); + + byte buffer[] = new byte[length]; + int count; + while ((count = in.read(buffer, 0, length)) > 0) + { + out.write(buffer, 0, count); + } + + in.close(); + in = null; + } + + out.close(); + out = null; + } + catch (IOException e) + { + // close open streams + if (out != null) + { + try + { + out.close(); + } + catch (IOException ee) + { + } + } + + if (in != null) + { + try + { + in.close(); + } + catch (IOException ee) + { + } + } + + throw e; + } + } + + /** + * Delete a file. + * + * @param file The file to delete. + * @throws IOException If the delete failed. + */ + private static void rm(File file) throws IOException + { + if (file.exists() && !file.delete()) + { + throw new IOException(AdaptorMsg.formatter.getString("RELIABLEFILE_DELETE_FAILED")); + } + } + + /** + * Answers a boolean indicating whether or not the specified reliable file + * exists on the underlying file system. + * + * @return <code>true</code> if the specified reliable file exists, + * <code>false</code> otherwise. + */ + public static boolean exists(File file) + { + if (file.exists()) /* quick test */ + { + return true; + } + + String name = file.getPath(); + + return new File(name + oldExt).exists() || new File(name + newExt).exists(); + } + + /** + * Delete this reliable file on the underlying file system. + * + * @throws IOException If the delete failed. + */ + private synchronized void delete() throws IOException + { + try + { + lock(); + } + catch (IOException e) + { + /* the lock request failed; decrement the use count */ + release(); + + throw e; + } + + try + { + rm(oldFile); + rm(orgFile); + rm(newFile); + rm(tmpFile); + } + finally + { + unlock(); + + release(); + } + } + + /** + * Delete the specified reliable file + * on the underlying file system. + * + * @return <code>true</code> if the specified reliable file was deleted, + * <code>false</code> otherwise. + */ + public static boolean delete(File file) + { + try + { + getReliableFile(file).delete(); + + return true; + } + catch (IOException e) + { + return false; + } + } +} |