/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* 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 Business Objects 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 THE COPYRIGHT OWNER 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.
*/
/*
* FileSystemResourceHelper.java
* Creation date: Jul 23, 2004.
* By: Edward Lam
*/
package org.openquark.cal.services;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import javax.swing.filechooser.FileFilter;
import org.openquark.util.ClassInfo;
import org.openquark.util.FileSystemHelper;
import org.openquark.util.Pair;
import org.openquark.util.ui.ExtensionFileFilter;
/**
* A static helper class which contains various utility methods used in the manipulation of module resources
* with respect to the file system.
* @author Edward Lam
*/
public class FileSystemResourceHelper {
/*
* Not intended to be instantiated.
*/
private FileSystemResourceHelper() {
}
/**
* Converts a desired filename into an a name for use in the file system by
* preceding all capital letters with hyphens. This is needed because not
* all filesystems are case-sensitive (ie: Windows), but CAL is case sensitive.
* @param name the name to convert
* @return the valid metadata file name
*/
public static String getFileSystemName(String name) {
StringBuilder fileName = new StringBuilder();
int length = name.length();
for (int i = 0; i < length; i++) {
char c = name.charAt(i);
if (Character.isUpperCase(c)) {
fileName.append("-" + c);
} else {
fileName.append(c);
}
}
return fileName.toString();
}
/**
* Converts the file system name to the actual name used.
* @param fileName the name used by the file system.
* @return the case-sensitive name.
*/
public static String fromFileSystemName(String fileName) {
StringBuilder name = new StringBuilder();
boolean nextCharUpper = false; // Keeps track of whether the next character should be upper case.
int length = fileName.length();
for (int i = 0; i < length; i++) {
char c = fileName.charAt(i);
if (c == '-') {
nextCharUpper = true;
} else {
if (nextCharUpper) {
name.append(Character.toUpperCase(c));
} else {
name.append(Character.toLowerCase(c));
}
nextCharUpper = false;
}
}
return name.toString();
}
/**
* Get the default filechooser file filter for workspace files.
* @return FileFilter a file filter for workspace files.
*/
public static FileFilter getDefaultFileFilter() {
return new ExtensionFileFilter("cws", "Workspace files");
}
/**
* Delete a file or directory tree starting at some root.
* @param rootFile the file or directory to delete. If this is a directory, subdirectories will also be deleted.
* @param status the tracking status object.
* @return whether the deletion was successful.
*/
public static boolean delTree(File rootFile, Status status) {
// If the root file is a directory, empty it..
delSubtrees(rootFile, status);
// Attempt to delete the file or directory.
boolean rootFileDeleted = rootFile.delete();
if (!rootFileDeleted) {
String fileTypeString = rootFile.isFile() ? "file" : "directory";
status.add(new Status(Status.Severity.ERROR, "Could not delete " + fileTypeString + ": " + rootFile, null));
}
return rootFileDeleted;
}
/**
* If a given file is a directory, empty it (delete its contents).
* @param rootFile the root file or directory.
* @param status the tracking status object.
*/
public static void delSubtrees(File rootFile, Status status) {
// If the root is a directory, get the files in the directory.
File[] rootDirFiles = rootFile.listFiles();
if (rootDirFiles != null) {
// Iterate over the files in the directory.
for (final File dirFile : rootDirFiles) {
// Delete its contents.
delTree(dirFile, status);
}
}
}
/**
* Get the file names of resources which reside in a folder.
* This will be sorted.
*
* @param folder the folder to search.
* @return the file names of the resources.
*/
public static List<String> getFileNamesInFolder(ResourcePath.Folder folder) {
List<URL> resourceURLList = getResourceURLList(folder);
// The list to be returned.
List<String> folderResourceNameList = new ArrayList<String>();
// Iterate over the resources with the given path.
for (final URL folderURL : resourceURLList) {
try {
// Get the file for the given resource. This should be a directory..
URI folderURI = WorkspaceLoader.urlToUri(folderURL);
// Check for a non-hierarchical (opaque) URI.
// This is the case if the URI exists within a .jar file, and will cause the File constructor to throw an exception.
// We don't handle this, as this is the *FileSystem* resource helper.
if (folderURI.isOpaque()) {
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING,
"Cannot get resources for a folder with a non-hierarchical URI. URI: " + folderURI);
continue;
}
File folderFile = new File(folderURI);
// Add the matching files from the directory.
File[] directoryFiles = folderFile.listFiles();
if (directoryFiles != null) {
for (final File element : directoryFiles) {
folderResourceNameList.add(element.getName());
}
}
} catch (RuntimeException e) {
// Thrown by File constructor if the uri is not acceptable.
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING, "Exception getting resources for " + folderURL, e);
}
}
Collections.sort(folderResourceNameList);
return folderResourceNameList;
}
/**
* Get the file names of resources which reside in the subtree rooted at the folder.
* The returned list is not sorted.
*
* @param folder the folder to search.
* @param addSubfolderNames whether the returned list should contain entries for subfolders as well as files.
* @return the pairs of (file folder, file name) of the resources.
*/
public static List<Pair<ResourcePath.Folder, String>> getFileFolderNamePairsInSubtreeRootedAtFolder(ResourcePath.Folder folder, boolean addSubfolderNames) {
List<URL> resourceURLList = getResourceURLList(folder);
// The list to be returned.
List<Pair<ResourcePath.Folder, String>> folderResourceNameList = new ArrayList<Pair<ResourcePath.Folder, String>>();
// Iterate over the resources with the given path.
for (final URL folderURL : resourceURLList) {
try {
// Get the file for the given resource. This should be a directory..
URI folderURI = WorkspaceLoader.urlToUri(folderURL);
// Check for a non-hierarchical (opaque) URI.
// This is the case if the URI exists within a .jar file, and will cause the File constructor to throw an exception.
// We don't handle this, as this is the *FileSystem* resource helper.
if (folderURI.isOpaque()) {
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING,
"Cannot get resources for a folder with a non-hierarchical URI. URI: " + folderURI);
continue;
}
File folderFile = new File(folderURI);
// Add the matching files from the directory.
getFileFolderNamePairsInDirectoryTree(folderFile, folder, folderResourceNameList, addSubfolderNames);
} catch (RuntimeException e) {
// Thrown by File constructor if the uri is not acceptable.
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING, "Exception getting resources for " + folderURL, e);
}
}
return folderResourceNameList;
}
/**
* Returns a list of URLs corresponding to the given folder resource path.
* @param folder a folder resource path.
* @return a list of URLs corresponding to the given folder resource path.
*/
private static List<URL> getResourceURLList(ResourcePath.Folder folder) {
// Get the path string.
// Annoyingly, the argument to ClassLoader.getResources() must not start with a slash, unless loading from a jar.
// However, ClassLoader.getResource() must start with a slash.
String pathStringMinusSlash = folder.getPathStringMinusSlash();
// Create a list of resources with and without leading slash.
List<URL> resourceURLList = new ArrayList<URL>();
try {
for (Enumeration<URL> en = ClassInfo.getResources(pathStringMinusSlash + "/"); en.hasMoreElements(); ) {
resourceURLList.add(en.nextElement());
}
} catch (IOException e) {
// Thrown by ClassLoader.getResources();
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING, "Exception getting resources for " + pathStringMinusSlash, e);
}
try {
for (Enumeration<URL> en = ClassInfo.getResources(folder.getPathString() + "/"); en.hasMoreElements(); ) {
resourceURLList.add(en.nextElement());
}
} catch (IOException e) {
// Thrown by ClassLoader.getResources();
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING, "Exception getting resources for " + folder.getPathString(), e);
}
return resourceURLList;
}
/**
* Get the names representing the resources in a directory. This will be sorted.
*
* @param directory the directory to search.
* @param fileExtension the file extension used to represent resources of the desired type.
* If null, any resource will match.
* @param folder the resource folder corresponding to the directory searched.
* @param pathMapper the path mapper to use to map paths back to resource names
* @return the names representing the resources in the given directory.
* Never null, but may be empty if the given file is not a directory.
*/
public static List<ResourceName> getDirectoryResourceNames(File directory, String fileExtension, ResourcePath.Folder folder, ResourcePathMapper pathMapper) {
return getFilteredDirectoryResourceNames(directory, fileExtension, folder, pathMapper, null);
}
/**
* Get a filtered list of the names representing the resources in a directory. This will be sorted.
*
* @param directory the directory to search.
* @param fileExtension the file extension used to represent resources of the desired type.
* If null, any resource will match.
* @param folder the resource folder corresponding to the directory searched.
* @param pathMapper the path mapper to use to map paths back to resource names
* @param filter the filter to use for determining which resource names to keep in the returned list.
* Null for no filter.
* @return the names representing the resources in the given directory.
* Never null, but may be empty if the given file is not a directory.
*/
public static List<ResourceName> getFilteredDirectoryResourceNames(File directory, final String fileExtension, ResourcePath.Folder folder, ResourcePathMapper pathMapper, ResourceName.Filter filter) {
java.io.FileFilter fileFilter = new java.io.FileFilter() {
public boolean accept(File pathname) {
if (fileExtension == null) {
return true;
}
return pathname.getName().endsWith("." + fileExtension);
}
};
File[] matchingFiles = fileExtension == null ? directory.listFiles() : directory.listFiles(fileFilter);
if (matchingFiles == null) {
return Collections.emptyList();
}
List<ResourceName> directoryResourceNames = new ArrayList<ResourceName>();
for (final File matchingFile : matchingFiles) {
String fileName = matchingFile.getName();
// If the filter is not null, check the path to make sure it's acceptable first
ResourceName directoryResourceName = pathMapper.getResourceNameFromFolderAndFileName(folder, fileName);
// It passes if the path mapper returns a non-null resource name (e.g. it has the right file extension).
if (directoryResourceName != null) {
// If the filter is not null, check the path to make sure it's acceptable first
if (filter == null || filter.accept(directoryResourceName)) {
directoryResourceNames.add(directoryResourceName);
}
}
}
Collections.sort(directoryResourceNames);
return directoryResourceNames;
}
/**
* Get the names representing the resources in the subtree rooted at the directory. This will be sorted.
*
* @param directory the directory to search.
* @param fileExtension the file extension used to represent resources of the desired type.
* If null, any resource will match.
* @param folder the resource folder corresponding to the directory searched.
* @param pathMapper the path mapper to use to map paths back to resource names
* @return the names representing the resources in the given directory.
* Never null, but may be empty if the given file is not a directory.
*/
public static List<ResourceName> getResourceNamesInSubtreeRootedAtDirectory(File directory, final String fileExtension, ResourcePath.Folder folder, ResourcePathMapper pathMapper) {
List<Pair<ResourcePath.Folder, String>> matchingFiles = new ArrayList<Pair<ResourcePath.Folder, String>>();
getFileFolderNamePairsInDirectoryTree(directory, folder, matchingFiles, false);
List<ResourceName> directoryResourceNames = new ArrayList<ResourceName>();
for (int i = 0, n = matchingFiles.size(); i < n; i++) {
Pair<ResourcePath.Folder, String> pair = matchingFiles.get(i);
ResourcePath.Folder fileFolder = pair.fst();
String fileName = pair.snd();
// If the file extension is not null, check the path to make sure it's acceptable first
if (fileExtension != null) {
if (!fileName.endsWith("." + fileExtension)) {
continue;
}
}
ResourceName directoryResourceName = pathMapper.getResourceNameFromFolderAndFileName(fileFolder, fileName);
// It passes if the path mapper returns a non-null resource name (e.g. it has the right file extension).
if (directoryResourceName != null) {
directoryResourceNames.add(directoryResourceName);
}
}
Collections.sort(directoryResourceNames);
return directoryResourceNames;
}
/**
* Get the names representing the resources in this folder.
*
* @param folder the folder to search.
* @return the names representing the resources in the given directory.
* Never null, but may be empty if the given file is not a directory.
*/
public static List<ResourceName> getFolderResourceNames(ResourcePath.Folder folder, ResourcePathMapper pathMapper) {
return getFilteredFolderResourceNames(folder, pathMapper, null);
}
/**
* Get a filtered list of the names representing the resources in this folder.
*
* @param folder the folder to search.
* @param filter the filter to use for determining which resource names to keep in the returned list.
* @return the names representing the resources in the given directory.
* Never null, but may be empty if the given file is not a directory.
*/
public static List<ResourceName> getFilteredFolderResourceNames(ResourcePath.Folder folder, ResourcePathMapper pathMapper, ResourceName.Filter filter) {
Set<ResourceName> filteredSet = new TreeSet<ResourceName>(); // sort alphabetically..
for (final String fileName : getFileNamesInFolder(folder)) {
ResourceName resourceName = pathMapper.getResourceNameFromFolderAndFileName(folder, fileName);
// It passes if the path mapper returns a non-null resource name (e.g. it has the right file extension).
if (resourceName != null) {
// If the filter is not null, check the path to make sure it's acceptable first
if (filter == null || filter.accept(resourceName)) {
filteredSet.add(resourceName);
}
}
}
return new ArrayList<ResourceName>(filteredSet);
}
/**
* Get the names representing the resources in the subtree rooted at this folder.
*
* @param folder the folder to search.
* @return the names representing the resources in the given directory.
* Never null, but may be empty if the given file is not a directory.
*/
public static List<ResourceName> getResourceNamesInSubtreeRootedAtFolder(ResourcePath.Folder folder, ResourcePathMapper pathMapper) {
Set<ResourceName> filteredSet = new TreeSet<ResourceName>(); // sort alphabetically..
for (final Pair<ResourcePath.Folder, String> pair : getFileFolderNamePairsInSubtreeRootedAtFolder(folder, false)) {
ResourcePath.Folder fileFolder = pair.fst();
String fileName = pair.snd();
ResourceName resourceName = pathMapper.getResourceNameFromFolderAndFileName(fileFolder, fileName);
// It passes if the path mapper returns a non-null resource name (e.g. it has the right file extension).
if (resourceName != null) {
filteredSet.add(resourceName);
}
}
return new ArrayList<ResourceName>(filteredSet);
}
/**
* Get the subdirectories of a given directory which contain any files with a given extension.
* @param parentDirectory the folder whose subfolders will be searched. The names of these subfolders
* are eligible to be returned.
* @param fileExtension the file extension to search for. If null, any file will match.
* @param convertFromFileSystemName whether the folder names are encoded with getFileSystemName(), and should be decoded.
* @return the names of folders for which files with the given extension exist.
*/
public static Set<String> getDirectoryNamesWithFileExtension(File parentDirectory, String fileExtension, boolean convertFromFileSystemName) {
Set<String> subdirectoryNameSet = new HashSet<String>();
List<File> subDirectories = Arrays.asList(parentDirectory.listFiles());
subFolderName:
for (final File subDirectory : subDirectories) {
String subFolderName = subDirectory.getName();
String convertedName = convertFromFileSystemName ? fromFileSystemName(subFolderName) : subFolderName;
File subdirectory = new File(parentDirectory, subFolderName);
Set<String> filesInSubdirectoryTree = new HashSet<String>();
getFilesInDirectoryTree(subdirectory, filesInSubdirectoryTree);
for (String fileName : filesInSubdirectoryTree) {
if (convertFromFileSystemName) {
fileName = fromFileSystemName(fileName);
}
if (fileExtension == null || fileName.endsWith(fileExtension)) {
subdirectoryNameSet.add(convertedName);
continue subFolderName;
}
}
}
return subdirectoryNameSet;
}
/**
* Returns a set of folder resource paths of all non-empty subdirectories in the given folder.
* @param parentFolder the folder resource path of the root of the subtree to process.
* @param parentDirectory the file-system directory corresponding to the folder.
* @param pathMapper the path mapper to use for mapping paths.
* @return a set of folder resource paths of all non-empty subdirectories in the given folder.
*/
public static Set<ResourcePath.Folder> getResourcePathsOfAllNonEmptySubdirectories(ResourcePath.Folder parentFolder, File parentDirectory, ResourcePathMapper pathMapper) {
Set<ResourcePath.Folder> result = new HashSet<ResourcePath.Folder>();
List<Pair<ResourcePath.Folder, String>> subfolderParentNamePairs = new ArrayList<Pair<ResourcePath.Folder, String>>();
getFileFolderNamePairsInDirectoryTree(parentDirectory, parentFolder, subfolderParentNamePairs, true);
for (final Pair<ResourcePath.Folder, String> pair : subfolderParentNamePairs) {
ResourcePath.Folder subfolderParent = pair.fst();
String subfolderName = pair.snd();
ResourcePath.Folder subfolder = subfolderParent.extendFolder(subfolderName);
if (!getFolderResourceNames(subfolder, pathMapper).isEmpty()) {
result.add(subfolder);
}
}
return result;
}
/**
* Adds to the given Set a string containing the full path name for each
* descendent of the given directory
*
* @param directory The root directory which contains the files to be added
* @param fileSet The set to which all files will be added
*/
public static void getFilesInDirectoryTree(File directory, Set<String> fileSet) {
File[] fileList = directory.listFiles();
for (final File element : fileList) {
if (element.isDirectory()) {
getFilesInDirectoryTree(element, fileSet);
} else if (element.isFile()) {
fileSet.add(element.getAbsolutePath());
}
}
}
/**
* Adds to the given Set a string containing a pair (file folder, file name) for each
* descendent of the given directory
*
* @param directory The root directory which contains the files to be added
* @param fileCollection The collection to which all files will be added
* @param addSubfolderNames whether the returned list should contain entries for subfolders as well as files.
*/
public static void getFileFolderNamePairsInDirectoryTree(File directory, ResourcePath.Folder folder, Collection<Pair<ResourcePath.Folder, String>> fileCollection, boolean addSubfolderNames) {
File[] fileList = directory.listFiles();
for (final File element : fileList) {
if (element.isDirectory()) {
getFileFolderNamePairsInDirectoryTree(element, folder.extendFolder(element.getName()), fileCollection, addSubfolderNames);
if (addSubfolderNames) {
fileCollection.add(new Pair<ResourcePath.Folder, String>(folder, element.getName()));
}
} else if (element.isFile()) {
fileCollection.add(new Pair<ResourcePath.Folder, String>(folder, element.getName()));
}
}
}
/**
* Strip the file extension from a file name.
* @param fileName the name of the file in question.
* @param fileExtension the file extension to strip from the name. If null, the filename will be returned.
* @param fromFileSystemName whether to additionally convert the name from an encoded file system name.
* @return fileName minus the file extension, or null if fileName does not have the given file extension.
*/
public static String stripFileExtension(String fileName, String fileExtension, boolean fromFileSystemName) {
if (fileExtension == null) {
return fromFileSystemName ? FileSystemResourceHelper.fromFileSystemName(fileName) : fileName;
}
// Prefix the file extension with the period.
fileExtension = "." + fileExtension;
// Remove the extension, if any.
if (fileName.endsWith(fileExtension)) {
String truncatedFileName = fileName.substring(0, fileName.length() - fileExtension.length());
return fromFileSystemName ? FileSystemResourceHelper.fromFileSystemName(truncatedFileName) : truncatedFileName;
}
return null;
}
/**
* Get the file which corresponds to the given path
* @param resourcePath the resource path.
* @param rootDirectory the root directory for resources using resource paths.
* @param createDirectoryIfAbsent whether to create the directory to the file if it does not exist.
* @return the file which corresponds to that path.
*/
public static File getResourceFile(ResourcePath resourcePath, File rootDirectory, boolean createDirectoryIfAbsent) {
File file = new File(rootDirectory, resourcePath.getPathString());
// Create the file if that's what we need to do.
if (createDirectoryIfAbsent && !file.exists()) {
File fileFolder;
if (resourcePath instanceof ResourcePath.Folder) {
fileFolder = file;
} else if (resourcePath instanceof ResourcePath.FilePath) {
fileFolder = file.getParentFile();
} else {
throw new IllegalArgumentException("Unknown ResourcePath type: " + resourcePath.getClass().getName());
}
if (!FileSystemHelper.ensureDirectoryExists(fileFolder)) {
CALWorkspace.SERVICES_LOGGER.log(Level.WARNING, "Could not create directory: " + fileFolder);
}
}
return file;
}
/**
* Copies all the data from the input stream to the output stream.
*/
public static void transferData(InputStream inputStream, OutputStream outputStream) throws IOException {
// Note: FileInputStream returns 0 bytes on read() if the path name is too long.
// Here we avoid this by converting to a channel.
byte [] bufferArray = new byte [4096];
ByteBuffer buffer = ByteBuffer.wrap(bufferArray);
ReadableByteChannel channel = Channels.newChannel(inputStream);
int nRead = 0;
while ((nRead = channel.read(buffer)) != -1) {
outputStream.write(bufferArray, 0, nRead);
buffer.rewind(); // rewind the buffer so that the next read starts at position 0 again
}
}
}