import java.util.HashMap;
import java.util.LinkedList;
import java.util.Arrays;
/**
* Implements a simple filesystem. From coursework description: A filesystem is
* a record, held in memory, of the relationships between directories and files
* and which disk blocks are allocated to which files. A filesystem consists of
* directories and files allocated to disk blocks. Directories contain both
* files and directories, starting at a "root". Only files occupy disk space, in
* non-contiguous 1KB increments.
*
* Filenames can contain the characters [a-z1-9]+
*
* @author Iain Lane, ial05u@cs.nott.ac.uk
* @version 0.6
*/
public class FileSystem {
private Tree fileSystemTree;
/** Stores file->block allocations */
public static Space fileStorage;
/**
* Ctor - Initialise filesystem with empty root directory and \c m KB of space
*
* @param m Amount, in KB, of disk space to allocate
*
*/
public FileSystem(int m) {
fileSystemTree = new Tree("root");
fileStorage = new Space(m);
}
/**
* Create an empty directory, with path provided in \c name.
*
* @param name String array containing path to directory to be created, as in \c file().
*/
public void dir(String[] name) throws BadFileNameException {
Tree workingTree = fileSystemTree;
if (name[0] != "root" || (FileExists(name) != null)) {
throw new BadFileNameException();
}
if (DirExists(name) != null) {
return;
}
//loop all the way, creating as we go down if necessary
for (int i = 0; i < name.length; i++) {
workingTree = workingTree.GetChildByName(name[i]);
}
}
/**
* List allocation of blocks on disk.
*
* @return A 2D String array of block/file allocations. Each index corresponds to one disk block and the entry is either null
* if the blocks is free or an array of strings which is the path to the file occupying that block.
*/
public String[][] disk() {
Leaf[] alloc = FileSystem.fileStorage.getAlloc();
String[][] disk = new String[alloc.length][];
int i = 0;
for (Leaf elem : alloc) {
if (elem == null) {
i++;
continue;
} else {
disk[i++] = elem.getPath();
}
}
return disk;
}
/**
* Create a \c k KB file, path provided in name.
*
* @param name String array, each element of which is an element in the path to file.
* Must start with root. Any nonexistent directories along the path will be created.
* @param k Size of file to create, in KB.
* @throws BadFileNameException First directory is not root.
* @throws OutOfSpaceException Adding child failed; not enough free space.
*/
public void file(String[] name, int k) throws BadFileNameException, OutOfSpaceException {
Tree workingTree = fileSystemTree;
String fileName = name[name.length - 1];
if (name[0] != "root") {
throw new BadFileNameException();
}
if (k > FileSystem.fileStorage.countFreeSpace()) { //not enough space free
Leaf file = FileExists(name);
if (file == null) {
throw new OutOfSpaceException();
} else if (k <= (FileSystem.fileStorage.countFreeSpace() - file.allocations.length)) { //if there will be enough space free after deleting the old file, do it
rmfile(name);
}
}
//loop until level containing file
for (int i = 0; i < name.length - 1; i++) {
workingTree = workingTree.GetChildByName(name[i]);
}
//will now be at same level as file, contained in workingTree
if (workingTree.children.containsKey(fileName)) { //file exists, remove (reached this point, so file can fit)
if (workingTree.children.get(fileName).getClass().getName() == "Tree") { //name of existing directory
throw new BadFileNameException();
}
//enough space free, remove old file
rmfile(name);
}
Leaf newLeaf = new Leaf(fileName, k);
newLeaf.parent = workingTree;
newLeaf.depth = newLeaf.parent.depth + 1;
workingTree.children.put(fileName, newLeaf);
}
/**
* List files and subdirectories contained in name.
*
* @param name String array containing path to directory to list, as in \c file().
* @return A String array containing the filename (only) of all files in the directory.
*/
public String[] lsdir(String[] name) {
Tree file = DirExists(name);
String[] fileList;
if (file == null) {
return null;
}
fileList = new String[file.children.size()];
fileList = file.children.keySet().toArray(fileList);
//sort array - not essential, but nice!
Arrays.sort(fileList);
return fileList;
}
/**
* Remove a file.
*
* @param name String array containing path to file to be removed, as in \c file().
* @see file
*/
public void rmfile(String[] name) {
Leaf file = FileExists(name);
if (file == null) { //file doesn't exist
return;
}
FileSystem.fileStorage.Dealloc(file);
}
/**
* Remove an empty directory.
*
* @param name String array containing path to directory to be removed, as in \c file().
* @throws DirectoryNotEmptyException The directory is not empty.
* @see file
*/
public void rmdir(String[] name) throws DirectoryNotEmptyException {
Tree dir = DirExists(name);
if (dir == null) {
return;
}
if (dir.children.size() > 0) {
throw new DirectoryNotEmptyException();
}
dir.parent.children.remove(dir.name);
}
private Node PathExists(String[] name) {
Tree workingTree = fileSystemTree;
for (int i = 0; i < name.length - 1; i++) {
if (!workingTree.children.containsKey(name[i])) {
return null;
}
workingTree = (Tree) workingTree.children.get(name[i]);
}
return workingTree.children.get(name[name.length - 1]);
}
/**
* Checks whether the specified file exists
* @param name Path to the file
* @return File if exists, \c null otherwise
*/
public Leaf FileExists(String[] name) {
Node found = PathExists(name);
if (found == null || found.getClass().getName() == "Node") {
return null;
}
return (Leaf) found;
}
/**
* Checks whether the specified directory exists
* @param name Path to directory
* @return Directory if exists, null otherwise
*/
public Tree DirExists(String[] name) {
Node found = PathExists(name);
if (found == null || found.getClass().getName() == "Leaf") {
return null;
}
return (Tree) found;
}
}
/**
* Leaf - cannot have children.
*
* @author iain
*
*/
class Leaf extends Node {
/** Size (in KB) of Leaf */
public int size;
/** Array of blocks containing Leaf data */
public int[] allocations;
/**
* Ctor - create leaf.
*
* @param name Name of the leaf.
* @param size Size, in KB, of the leaf.
* @throws OutOfSpaceException Allocating space failed.
*/
public Leaf(String name, int size) throws OutOfSpaceException {
this.name = name;
allocateSpace(size);
}
private void allocateSpace(int size) throws OutOfSpaceException {
FileSystem.fileStorage.Alloc(size, this);
}
}
/**
* Class from which Trees and Leaves are derived.
* Every element is actually a node.
*
* @author iain
*
*/
abstract class Node {
/** Every node must have a name */
String name;
/** Every node must have a parent. For \c root this will be null */
public Tree parent;
/** Store the depth */
int depth = 0;
/**
* Get the path to the current node
* @return Path to the current node
*/
public String[] getPath() {
String[] path = new String[this.depth];
Node workingNode = this;
for (int i = this.depth - 1; i >= 0; i--) {
path[i] = workingNode.name;
workingNode = workingNode.parent;
}
return path;
}
}
/**
* Implement file storage. Provide methods for allocating and deallocating files on disk.
*
* @author iain
*
*/
class Space {
private Leaf[] blocks;
private LinkedList freeBlocks;
/**
* Ctor - create \c size blank filesystem blocks.
* @param size
*/
public Space(int size) {
blocks = new Leaf[size];
//sacrifice space for speed
freeBlocks = new LinkedList();
//O(size) to initialize queue
for (int i = 0; i < size; i++) {
freeBlocks.add(i);
}
}
/**
* Allocate \c size blocks to store \c file.
*
* @param size Number of blocks, in KB, required by \c file.
* @param file File to store.
* @throws OutOfSpaceException
*/
public void Alloc(int size, Leaf file) throws OutOfSpaceException {
file.allocations = new int[size];
//we reached this point, therefore there is enough free space
for (int i = 0; i < size; i++) {
file.allocations[i] = freeBlocks.poll();
blocks[file.allocations[i]] = file;
}
}
/**
* Free any space occupied by \c file.
*
* @param file File to deallocate.
*/
public void Dealloc(Leaf file) {
for (int i = 0; i < file.allocations.length; i++) {
blocks[file.allocations[i]] = null;
//record new free block
freeBlocks.add(file.allocations[i]);
}
file.parent.children.remove(file.name);
}
/**
* Count the amount of free space.
* @return the amount of kb blocks free
*/
public int countFreeSpace() {
return freeBlocks.size();
}
/**
* Get the file allocations
* @return an array (of leaves) of allocations
*/
public Leaf[] getAlloc() {
return blocks;
}
}
/**
* Tree data structure - nodes can have arbitrarily many children.
*
* @author iain
*
*/
class Tree extends Node {
/** The children of the current node */
public HashMap children = new HashMap();
/**
* Ctor - create tree.
*
* @param name The name of the root element of this tree.
*/
public Tree(String name) {
this.name = name;
}
/**
* Get a child from a \c Node, or create it if nonexistant.
*
* @param name Name of child to search for.
* @return Tree found (or created).
*/
public Tree GetChildByName(String name) {
if (this.children.containsKey(name)) {
return (Tree) this.children.get(name);
}
//not found - create
Tree newTree = new Tree(name);
newTree.parent = this;
newTree.depth = newTree.parent.depth + 1;
this.children.put(name, newTree);
return newTree;
}
}