/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.lib;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Random;

import org.hsqldb.lib.java.JavaSystem;

A collection of file management methods.

Also provides the default FileAccess implementation

Author:Campbell Burnet (campbell-burnet@users dot sourceforge.net), Fred Toussi (fredt@users dot sourceforge.net), Ocke Janssen oj@openoffice.org
Version:2.4.1
Since:1.7.2
/** * A collection of file management methods.<p> * Also provides the default FileAccess implementation * * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * @author Ocke Janssen oj@openoffice.org * @version 2.4.1 * @since 1.7.2 */
public class FileUtil implements FileAccess { private static final FrameworkLogger LOG = FrameworkLogger.getLog(FileUtil.class); private static FileUtil fileUtil = new FileUtil(); private static FileAccessRes fileAccessRes = new FileAccessRes();
Creates a new instance of FileUtil
/** Creates a new instance of FileUtil */
FileUtil() {} public static FileUtil getFileUtil() { return fileUtil; } public static FileAccess getFileAccess(boolean isResource) { return isResource ? fileAccessRes : fileUtil; } public boolean isStreamElement(String elementName) { return new File(elementName).exists(); } public InputStream openInputStreamElement(String streamName) throws IOException { try { return new FileInputStream(new File(streamName)); } catch (Throwable e) { throw JavaSystem.toIOException(e); } } public void createParentDirs(String filename) { makeParentDirectories(new File(filename)); } public void removeElement(String filename) { if (isStreamElement(filename)) { delete(filename); } } public void renameElement(String oldName, String newName, boolean copyIfFailed) { if (renameWithOverwrite(oldName, newName)) { return; } if (!copyIfFailed) { return; } InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = openInputStreamElement(oldName); outputStream = openOutputStreamElement(newName, false); InOutUtil.copy(inputStream, outputStream); getFileSync(outputStream).sync(); } catch (IOException e) { String message = String.format( "Platform does not allow renaming files and failed to copy file contents from %s to %s", oldName, newName); LOG.error(message, e); return; } finally { try { if (inputStream != null) { inputStream.close(); } } catch (IOException e) { LOG.finest("Failed to dispose streams", e); } try { if (outputStream != null) { outputStream.close(); } } catch (IOException e) { LOG.finest("Failed to dispose streams", e); } } if (!delete(oldName)) { LOG.warning("Failed to delete renamed file " + oldName); } String message = String.format( "Platform does not allow renaming files. Copied file from %s to %s instead", oldName, newName); LOG.finer(message); } public OutputStream openOutputStreamElement(String streamName, boolean append) throws IOException { return new FileOutputStream(new File(streamName), append); } // end of FileAccess implementation // a new File("...")'s path is not canonicalized, only resolved // and normalized (e.g. redundant separator chars removed), // so as of JDK 1.4.2, this is a valid test for case insensitivity, // at least when it is assumed that we are dealing with a configuration // that only needs to consider the host platform's native file system, // even if, unlike for File.getCanonicalPath(), (new File("a")).exists() or // (new File("A")).exits(), regardless of the hosting system's // file path case sensitivity policy. public final boolean fsIsIgnoreCase = (new File("A")).equals(new File("a")); // posix separator normalized to File.separator? // CHECKME: is this true for every file system under Java? public final boolean fsNormalizesPosixSeparator = (new File("/")).getPath().endsWith(File.separator); // for JDK 1.1 createTempFile final Random random = new Random(System.currentTimeMillis());
Delete the named file
/** * Delete the named file */
public boolean delete(String filename) { return new File(filename).delete(); }
Requests, in a JDK 1.1 compliant way, that the file or directory denoted by the given abstract pathname be deleted when the virtual machine terminates.

Deletion will be attempted only for JDK 1.2 and greater runtime environments and only upon normal termination of the virtual machine, as defined by the Java Language Specification.

Once deletion has been sucessfully requested, it is not possible to cancel the request. This method should therefore be used with care.

Params:
  • f – the abstract pathname of the file be deleted when the virtual machine terminates
/** * Requests, in a JDK 1.1 compliant way, that the file or directory denoted * by the given abstract pathname be deleted when the virtual machine * terminates. <p> * * Deletion will be attempted only for JDK 1.2 and greater runtime * environments and only upon normal termination of the virtual * machine, as defined by the Java Language Specification. <p> * * Once deletion has been sucessfully requested, it is not possible to * cancel the request. This method should therefore be used with care. <p> * * @param f the abstract pathname of the file be deleted when the virtual * machine terminates */
public void deleteOnExit(File f) { f.deleteOnExit(); }
Return true or false based on whether the named file exists.
/** * Return true or false based on whether the named file exists. */
public boolean exists(String filename) { return new File(filename).exists(); } public boolean exists(String fileName, boolean resource, Class cla) { if (fileName == null || fileName.length() == 0) { return false; } return resource ? null != cla.getResource(fileName) : FileUtil.getFileUtil().exists(fileName); }
Rename the file with oldname to newname. If a file with newname already exists, it is deleted before the renaming operation proceeds. If a file with oldname does not exist, no file will exist after the operation.
/** * Rename the file with oldname to newname. If a file with newname already * exists, it is deleted before the renaming operation proceeds. * * If a file with oldname does not exist, no file will exist after the * operation. */
private boolean renameWithOverwrite(String oldname, String newname) { File file = new File(oldname); delete(newname); boolean renamed = file.renameTo(new File(newname)); if (renamed) { return true; } delete(newname); if (exists(newname)) { new File(newname).renameTo(new File(newDiscardFileName(newname))); } return file.renameTo(new File(newname)); }
Retrieves the absolute path, given some path specification.
Params:
  • path – the path for which to retrieve the absolute path
Returns:the absolute path
/** * Retrieves the absolute path, given some path specification. * * @param path the path for which to retrieve the absolute path * @return the absolute path */
public String absolutePath(String path) { return new File(path).getAbsolutePath(); }
Retrieves the canonical file for the given file, in a JDK 1.1 complaint way.
Params:
  • f – the File for which to retrieve the absolute File
Returns:the canonical File
/** * Retrieves the canonical file for the given file, in a * JDK 1.1 complaint way. * * @param f the File for which to retrieve the absolute File * @return the canonical File */
public File canonicalFile(File f) throws IOException { return new File(f.getCanonicalPath()); }
Retrieves the canonical file for the given path, in a JDK 1.1 complaint way.
Params:
  • path – the path for which to retrieve the canonical File
Returns:the canonical File
/** * Retrieves the canonical file for the given path, in a * JDK 1.1 complaint way. * * @param path the path for which to retrieve the canonical File * @return the canonical File */
public File canonicalFile(String path) throws IOException { return new File(new File(path).getCanonicalPath()); }
Retrieves the canonical path for the given File, in a JDK 1.1 complaint way.
Params:
  • f – the File for which to retrieve the canonical path
Returns:the canonical path
/** * Retrieves the canonical path for the given File, in a * JDK 1.1 complaint way. * * @param f the File for which to retrieve the canonical path * @return the canonical path */
public String canonicalPath(File f) throws IOException { return f.getCanonicalPath(); }
Retrieves the canonical path for the given path, in a JDK 1.1 complaint way.
Params:
  • path – the path for which to retrieve the canonical path
Returns:the canonical path
/** * Retrieves the canonical path for the given path, in a * JDK 1.1 complaint way. * * @param path the path for which to retrieve the canonical path * @return the canonical path */
public String canonicalPath(String path) throws IOException { return new File(path).getCanonicalPath(); }
Retrieves the canonical path for the given path, or the absolute path if attempting to retrieve the canonical path fails.
Params:
  • path – the path for which to retrieve the canonical or absolute path
Returns:the canonical or absolute path
/** * Retrieves the canonical path for the given path, or the absolute * path if attempting to retrieve the canonical path fails. * * @param path the path for which to retrieve the canonical or * absolute path * @return the canonical or absolute path */
public String canonicalOrAbsolutePath(String path) { try { return canonicalPath(path); } catch (Exception e) { return absolutePath(path); } } public void makeParentDirectories(File f) { String parent = f.getParent(); if (parent != null) { new File(parent).mkdirs(); } else { // workaround for jdk 1.1 bug (returns null when there is a parent) parent = f.getPath(); int index = parent.lastIndexOf('/'); if (index > 0) { parent = parent.substring(0, index); new File(parent).mkdirs(); } } } public static String makeDirectories(String path) { try { File file = new File(path); file.mkdirs(); return file.getCanonicalPath(); } catch (IOException e) { return null; } } public FileAccess.FileSync getFileSync(java.io.OutputStream os) throws IOException { return new FileSync((FileOutputStream) os); } public static class FileSync implements FileAccess.FileSync { FileDescriptor outDescriptor; FileSync(FileOutputStream os) throws IOException { outDescriptor = os.getFD(); } public void sync() throws IOException { outDescriptor.sync(); } } public static class FileAccessRes implements FileAccess { public boolean isStreamElement(String fileName) { URL url = null; try { url = getClass().getResource(fileName); if (url == null) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl != null) { url = cl.getResource(fileName); } } } catch (Throwable t) { // } return url != null; } public InputStream openInputStreamElement(final String fileName) throws IOException { InputStream fis = null; try { fis = getClass().getResourceAsStream(fileName); if (fis == null) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl != null) { fis = cl.getResourceAsStream(fileName); } } } catch (Throwable t) { // } finally { if (fis == null) { throw new FileNotFoundException(fileName); } } return fis; } public void createParentDirs(String filename) {} public void removeElement(String filename) {} public void renameElement(String oldName, String newName, boolean copyIfFailed) {} public OutputStream openOutputStreamElement(String streamName, boolean append) throws IOException { throw new IOException(); } public FileAccess.FileSync getFileSync(OutputStream os) throws IOException { throw new IOException(); } }
Utility method for user applications. Attempts to delete all the files for the database as listed by the getDatabaseFileList() method. If any of the current, main database files cannot be deleted, it is renamed by adding a suffix containing a hexadecimal timestamp portion and the ".old" extension. Also deletes the ".tmp" directory.
Params:
  • dbNamePath – full path or name of database (without a file extension)
Returns:currently always true
/** * Utility method for user applications. Attempts to delete all the files * for the database as listed by the getDatabaseFileList() method. If any * of the current, main database files cannot be deleted, it is renamed * by adding a suffix containing a hexadecimal timestamp portion and * the ".old" extension. Also deletes the ".tmp" directory. * * @param dbNamePath full path or name of database (without a file extension) * @return currently always true */
public static boolean deleteOrRenameDatabaseFiles(String dbNamePath) { DatabaseFilenameFilter filter = new DatabaseFilenameFilter(dbNamePath); File[] fileList = filter.getExistingFileListInDirectory(); for (int i = 0; i < fileList.length; i++) { fileList[i].delete(); } File tempDir = new File(filter.canonicalFile.getPath() + ".tmp"); if (tempDir.isDirectory()) { File[] tempList = tempDir.listFiles(); if (tempList != null) { for (int i = 0; i < tempList.length; i++) { tempList[i].delete(); } } tempDir.delete(); } fileList = filter.getExistingMainFileSetList(); if (fileList.length == 0) { return true; } for (int i = 0; i < fileList.length; i++) { fileList[i].delete(); } fileList = filter.getExistingMainFileSetList(); for (int i = 0; i < fileList.length; i++) { fileList[i].renameTo( new File(newDiscardFileName(fileList[i].getPath()))); } return true; }
Utility method for user applications. Returns a list of files that currently exist for a database. The list includes current database files as well as ".new", and ".old" versions of the files, plus any app logs.
Params:
  • dbNamePath – full path or name of database (without a file extension)
/** * Utility method for user applications. Returns a list of files that * currently exist for a database. The list includes current database files * as well as ".new", and ".old" versions of the files, plus any app logs. * * @param dbNamePath full path or name of database (without a file extension) */
public static File[] getDatabaseFileList(String dbNamePath) { DatabaseFilenameFilter filter = new DatabaseFilenameFilter(dbNamePath); return filter.getExistingFileListInDirectory(); }
Returns a list of existing main files for a database. The list excludes non-essential files.
Params:
  • dbNamePath – full path or name of database (without a file extension)
/** * Returns a list of existing main files for a database. The list excludes * non-essential files. * * @param dbNamePath full path or name of database (without a file extension) */
public static File[] getDatabaseMainFileList(String dbNamePath) { DatabaseFilenameFilter filter = new DatabaseFilenameFilter(dbNamePath, false); return filter.getExistingFileListInDirectory(); } static int discardSuffixLength = 9; public static String newDiscardFileName(String filename) { String timestamp = StringUtil.toPaddedString( Integer.toHexString((int) System.currentTimeMillis()), discardSuffixLength - 1, '0', true); String discardName = filename + "." + timestamp + ".old"; return discardName; } static class DatabaseFilenameFilter implements FilenameFilter { String[] suffixes = new String[] { ".backup", ".properties", ".script", ".data", ".log", ".lobs" }; String[] extraSuffixes = new String[] { ".lck", ".sql.log", ".app.log" }; private String dbName; private File parent; private File canonicalFile; private boolean extraFiles; DatabaseFilenameFilter(String dbNamePath) { this(dbNamePath, true); } DatabaseFilenameFilter(String dbNamePath, boolean extras) { canonicalFile = new File(dbNamePath); try { canonicalFile = canonicalFile.getCanonicalFile(); } catch (Exception e) {} dbName = canonicalFile.getName(); parent = canonicalFile.getParentFile(); extraFiles = extras; } public File[] getCompleteMainFileSetList() { File[] fileList = new File[suffixes.length]; for (int i = 0; i < suffixes.length; i++) { fileList[i] = new File(canonicalFile.getPath() + suffixes[i]); } return fileList; } public File[] getExistingMainFileSetList() { File[] fileList = getCompleteMainFileSetList(); HsqlArrayList list = new HsqlArrayList(); for (int i = 0; i < fileList.length; i++) { if (fileList[i].exists()) { list.add(fileList[i]); } } fileList = new File[list.size()]; list.toArray(fileList); return fileList; } public File[] getExistingFileListInDirectory() { File[] list = parent.listFiles(this); return list == null ? new File[]{} : list; }
Accepts all main files as well as ".new" and ".old" versions.
/** * Accepts all main files as well as ".new" and ".old" versions. */
public boolean accept(File dir, String name) { if (parent.equals(dir) && name.indexOf(dbName) == 0) { String suffix = name.substring(dbName.length()); if (extraFiles) { for (int i = 0; i < extraSuffixes.length; i++) { if (suffix.equals(extraSuffixes[i])) { return true; } } } for (int i = 0; i < suffixes.length; i++) { if (suffix.equals(suffixes[i])) { return true; } if (!extraFiles) { continue; } if (suffix.startsWith(suffixes[i])) { if (name.endsWith(".new")) { if (suffix.length() == suffixes[i].length() + 4) { return true; } } else if (name.endsWith(".old")) { if (suffix.length() == suffixes[i].length() + discardSuffixLength + 4) { return true; } } } } } return false; } } }