The goal of this project is to develop an in-memory Virtual File System (VFS), named the COMP VFS (CVFS), in Java.
A VFS is usually built on top of a host file system to enable uniform access to files located in different host file systems, while the CVFS simulates a file system in memory.
COMP Virtual System (CVFS) is a virtual file system where you can create virtual disks and manage files within them. CVFS supports you to create criteria to filter specific files. You can also load/save the virtual disks and criteria file from/to your local file system.
This project not only meets the requirements, but also explores the design principles and programming philosophies, and brings them to reality. I'm very happy that this project was designed and completed perfectly.
YANG Xikun, Nov 2024
- YANG Xikun (25%)
- Overall Design, Coding and Report
- YANG Jinkun (25%)
- Model Validating and Improving
- REN Yixiao (25%)
- Test Engineer
- Arda EREN (25%)
- User Manual
- Modularity: The project application is divided into four parts: Model, Controller, Service, and View. Each part is highly cohesive and lowly coupled, interacting through well-defined interfaces. We strictly adhere to the MVC design pattern.
- Separation of Responsibilities: The four parts have distinct and clear responsibilities. Each part focuses on its own duties, without concern about issues not in its scope.
- Isolation: Each part [strictly] limits the interfaces provided to other parts, so to prevent excessive interference and coupling.
- High-Fidelity Simulation: Since the CVFS is a virtual file system, we aim to make it [as closely as possible] to real file systems or existing virtual file systems. We try our best in our simulations, including interface and responsibility simulations. Some details are too complicated to simulate, so we compromise by making them [appear] realistic to other parts, rather than focusing on every internal detail.
- Safest structure: We require all structures to be safe, especially ensuring that the safety of lower-level structures does not depend on upper-level structures. Reasonable but redundant safety checks are allowed.
Since the core of CVFS is a file system, the very basic part is to simulate a file system.
A file system abstracts the concepts of files for the users. More specifically, in a disk, there is not a clear concept of files: a file might be split into multiple parts and stored separately.
The users, as well as other parts above the file system, such as the View, Controller, and Service in the MVC model mentioned later, all rely on the file system to interact with the virtual disk.
Similar to the fetch-decode-execute cycle of a CPU, our system uses a cycle to keep handling user commands:
- Receive user's input.
- Parse the input.
- Check if the input is valid.
- Convert the input into an operation.
- Execute the operation.
- Present the result back to user.
- Go back to 1.
This cycle led us to develop an Enhanced MVC Model. See next section.
There are four parts in our application, the Model, Controller, Service and View.
Below is a simple diagram showing their relationships, where arrows indicate dependencies (e.g., A → B
means A
depends on B
's interfaces).
+-------------------------+
| +------------+ | +------------+ +------------------+
| | Controller | -------------> | Service | -----> | |
| +------------+ | +------------+ | Model |
| | | | | (FileSystem) |
| | ---------------------------------------------> | |
| | | +------------------+
| | |
| +------------+ |
| | View | |
| +------------+ |
| |
| Command Line Interface |
| (Console) |
+-------------------------+
- Application (as
Application
class) is the highest layer.- It provides a main entry point.
- All other parts should be booted by it.
- The Model (as
model
package) is the core part of the system.- It implements several Entities (as
entities
package).- The entities include:
- Files (as
File
interface) - Directories (as
Directory
class) - Documents (as
Document
class) - Criteria (as
Criterion
class) - Virtual Disks (as
VDisk
class)
- Files (as
- In the view of other parts, entities are only piles of read-only information. For example, a
File
is just a file, and it cannot control is organization on the virtual disk. - Constructors like
new VDisk(...)
andnew Document(...)
are allowed, but nominally, they must call the File System to take effect.
- The entities include:
- It has another part to maintain the application's state and process all file system requests, defined as the File System (as
FileSystem
class).- Any attempts to modify the Entities or find their relational context [must] request the File System first. The attempts include:
- Create new files (requires allocating disk space)
- Remove files (requires deallocating disk space)
- Rename files (requires disk read & write)
- Modify documents (also requires disk read & write)
- Find the parent of files (requires disk read)
- Find if a file exists in the directory (also requires disk read)
- Any attempts to modify the Entities or find their relational context [must] request the File System first. The attempts include:
- It implements several Entities (as
- The Controller (as
Controller
class) is the application's controller.- It handles user input, hence it is partially in the Console.
- It requests operations (as
Operation
class) from the Service, and then executes them.
- The Service (as
service
andoperationservice
package) converts the commands into operations and provides simple and unified interfaces to the Controller, so the Controller can perform the operation in few steps.- It is designed to alleviate the Controller's load, because there are many kinds of operations.
- It needs to use the interfaces provided by the Model. However, it does not know the implementation details.
- It serves as a bridge between the Controller and the Model.
- The Console (as
Console
class) is the core of the command line interface.- It includes the entire View part, because it handles all information displayed to the user.
- It is also a part of the Controller because users need to provide commands through the Console.
After introducing the enhanced MVC model, we could show you a more detailed running cycle. This is the core of our program.
- [Controller, Console] Receive user's input.
- [Controller, Console] Parse the input.
- Split the input by spacebars.
- Process the quote marks and backticks.
- [Controller, Service] Check if the input is valid.
- Check if the command exists.
- Check if the parameters of the command are correct.
- If all correct, save the command's parameter information.
- [Service] Convert the input into an operation.
- Get the application's current state.
- Generate an
Operation
object using the state information and the parameter information.
- [Controller] Execute the operation.
- Simply call the
exec()
method of theOperation
object.
- Simply call the
- [View, Console] Present the result back to user.
- If the operation executes successfully, present the success information.
- Or, present the error information.
- Go back to 1.
We introduce the principle of high cohesion and low coupling. In our program, each part having clear responsibilities, and the interfaces to other parts are strictly limited.
- The Model is the core part, it does not depend on any other parts. The Model's interfaces to other parts include:
- [For Service and Controller] The reference to the File System.
// It is allowed to use such statements in the Service and Controller part. FileSystem fs = ...;
- [Only for Service] File system interfaces (such as retrieving files, creating files / allocating space, deleting files / deallocating space, and other file management operations).
// These interfaces are provided, but only for the Service part: /* Methods involving virtual disk mounting and ejecting */ // public void ejectVDisk(); // currently unavailable public void mountVDisk(VDisk vDisk); public void releaseResource(); /* Public getters */ public Directory getRootDirectory(); public Directory getWorkingDirectory(); public String getWorkingDirectoryPathSafely(); /* Public setter */ public void setNewWorkingDirectory(Directory newWorkingDirectory); /* File system responsibilities */ public TreeMap<String, File> getAllFiles(Directory directory); public File findFile(Directory directory, String name); public File getParent(File file); public void storeFile(File file); public void removeFile(File file); public void renameFile(File file, String newName); public void modifyDocument(File file, String newContent); /* Methods related to the criteria */ public void addCriterion(Criterion criterion); public TreeMap<String, Criterion> getAllCriteria(); public Criterion findCriterion(String name); public void removeCriterion(Criterion criterion);
- [Only for Service and Read-only] The Entities.
// Virtual disk interfaces: NONE // File interfaces: public String getName(); public String getFullname(); public long getSize(); public String getPath(); // Additional interfaces in Document: public Document(String name, String type, String content, Directory parent); public String getType(); public String getContent(); // Hence in the view of other parts, // Files and Directories are just piles read-only information, // looks like the abstract structures provided by the file system.
- [Exception] A general exception class (as
ModelException
class).
- [For Service and Controller] The reference to the File System.
- The Service encapsulates the second and third interfaces of the Model, greatly reducing the coupling between the Controller and the Model. The Service provides the following interfaces to the Controller:
- [For Controller] An
exec()
method to execute operations.// You may see the following statements in the Controller: String result = operation.exec(); console.printInformation(result);
- [Exception] Two exception classes (as
InvalidCommandException
exception andOperationCannotExecuteException
exception), indicating that user commands from the Controller are invalid or operations cannot be executed.
- [For Controller] An
- It is clear that the Controller hardly depends on the Model; it only needs a reference to the file system. The Controller slightly depends on the Service, but it is minimal.
- The View does not involve the Model or Service. It simply outputs the content as requested by the Controller.
Noted that to ensure functionalities, the minimal dependency and coupling must exist between parts.
As we mentioned before, we try our best to simulate as much as possible. In our discussion above, it is clear that the Controller, Service, and View fully aligns with the real file systems. However, the entities part of the Model needs clarification.
- Our program does not maintain a space to simulate the actual storage of a virtual disk. Instead, our virtual disks store files by storing references. We have not simulated complex mechanisms, such as virtual page tables and space maps.
- And in order to keep code simple, some operations involving the disk read & write are not fully implemented in the
FileSystem
class. Instead, they might be in theFile
interface andDirectory
class. - Luckily, we do not allow the files to operate tasks involving disk read & write in other parts, which greatly simulates the reality where real disk read & writes exist.
- We introduced a
@ModelInternalUse
annotation to indicate that, to those things whose details are not aligned to the real world, and/or could cause serious issues if exposed to other parts, they should remain internal in the Model. The method with this annotation will also have a__INTERNAL__
prefix. For example,public void storeFile(File file) throws ... { checkAddressSpace(file); if (file.getSize() > currentVDisk.getFreeSpace()) { throw new ...; } // The following should be the actual disk storage logic. Directory parent = (Directory)file.__INTERNAL__getParent(); if (parent.__INTERNAL__existsName(file.getName())) { throw new ...; } parent.__INTERNAL__add(file); }
- Overall, in the view of other parts,
- The
FileSystem
class manages all responsibilities of the File System, - The Entities just represents entities themselves, along with their information read from the virtual disk. More specifically,
- The Entities themselves do not participate in any disk reading & writing activities.
- The Entities look like the structures abstracted from the file system.
- The
This is the best we can do, and we believe that not to over-simulate is compatible with the project's expectations.
Below, we have listed all the classes of our application.
- The Application
Application
- The Application
- The Model
- The Entities
File
- FilesDirectories
- DirectoriesDocument
- Documents- Criteria
Criterion
- CriteriaCriterionFactory
- The Criterion Factory, used to generateCriterion
objectsIsDocument
- TheIsDocument
criterionNameCriterion
- The simple criterion withattrType == name
TypeCriterion
- The simple criterion withattrType == type
SizeCriterion
- The simple criterion withattrType == size
NegationCriterion
- The negation criterionLogicAndCriterion
- The binary criterion withlogicOp == &&
LogicOrCriterion
- The binary criterion withlogicOp == ||
VDisk
- Virtual Disks
- The File System
FileSystem
- The File System
- The Entities
- The Controller
Controller
- The Controller
- The Service
OperationFactory
- The Operation Factory, used to generateOperation
objectsOperationRecord
- The Operation Record System, used to record undoable operations- Operations:
Operation
- The general operationsUndoableOperation
- The undoable operationsFileUnrelatedUndoableOperation
- The undoable operations which are not related to files.NewDisk
- The operation ofnewDisk
commandNewDir
- The operation ofnewDir
commandNewDoc
- The operation ofnewDoc
commandViewContent
- The operation ofview
commandRemove
- The operation ofdelete
command, the inverse operation ofPutBack
,NewDir
andNewDoc
PutBack
- The inverse operation ofRemove
ModifyContent
- The operation ofmodify
commandRename
- The operation ofrename
commandChangeDir
- The operation ofchangeDir
commandList
- The operation oflist
commandRList
- The operation ofrList
commandNewSimpleCri
- The operation ofnewSimpleCri
commandNewNegation
- The operation ofnewNegation
commandNewBinaryCri
- The operation ofnewBinaryCri
commandRemoveCri
- The operation ofremoveCri
command, the inverse operation ofPutBackCri
,NewSimpleCri
,NewNegation
andNewBinaryCri
PutBackCri
- The inverse operation ofRemoveCri
PrintAllCriteria
- The operation ofprintAllCriteria
commandSearch
- The operation ofsearch
commandRSearch
- The operation ofrSearch
commandSave
- The operation ofsave
commandLoad
- The operation ofload
commandQuit
- The operation ofquit
command
- Console
Console
- The Console
We have completed outlining our overall structure, but it still may not be easy to understand the information flow.
We will further refine the cycle introduced in section 2.2.4. This time, we will specifically associate with the classes.
Console
receives user's input.Console
parses the input, then returns it back toController
.Controller
gives the parsed input toCriterionFactory
.CriterionFactory
converts the input into anOperation
object, and returns it back toController
.- The
Operation
object corresponds to the command. - The
Operation
object includes the application’s state information and the command parameter information.
- The
Controller
call theexec()
method of theOperation
object to execute the operation.- The
Operation
object would call the methods in the File System. Once its execution is finished, it returns the successful result back toController
. Controller
provides the result toConsole
.Console
presents the result to the user.- Go back to 1.
This annotation indicates that a method should only be used within the Model part (i.e., the Entities and the File System), and should not be exposed to other parts.
The main reasons for using the @ModelInternalUse
annotation include:
-
Some complex mechanisms, such as the actual disk storage logic, are not simulated. It is best not to expose such unsimulated things to other parts, and related methods should add this annotation and only be used internally. In addition, even with complete simulation, such methods should keep this annotation, refer to the second and third points.
-
Many methods are safely used within the Model part, but exposing it to other parts could lead to risks, such as unpredictable data changes. These methods should add this annotation.
-
Many methods are intended to be controlled and managed by the file system, and they are designed not to be exposed to other parts. These methods should add this annotation.
It is required to check the contexts and perform necessary error checking before using methods with this annotation.
We use a set of simple commands to start the application:
FileSystem fs = new FileSystem();
Console console = new Console();
Controller controller = new Controller(fs, console);
Application app = new Application(fs, console, controller);
We focus on how the Controller repeatedly executes the Cycle. It is clear that this is the function of the method work(). Here we provide the general logic of work(), and important codes are highlighted:
private void work() {
while (true) {
try {
String[] command = console.getNextCommand(...);
Operation operation = operationFactory.createOperation(...);
String result = operation.exec();
console.printInformation(result);
if (operation instanceof UndoableOperation) {
operationRecord.record((UndoableOperation) operation);
}
} catch (...) {
console.printErrorStream(e);
}
}
}
The Operation Factory is used to generate Operation objects. As mentioned, it needs to store the application’s state information and the command’s parameter information. The only method in this class, and the most important one, is createOperation(). Its general logic is:
public Operation createOperation(FileSystem fs, OperationRecord operationRecord, String[] command) {
switch (command[0]) {
case "newDisk":
return new NewDisk(fs, operationRecord, command);
case "newDoc":
return new NewDoc(fs, command);
...
}
}
To implement undo and redo features, we need some definitions. These definitions are all integrated into an enum type, called OperationType
.
- Each undoable operation has three types:
REGULAR
: Neither an undo nor a redo version of another operation.UNDO
: The undo version of another operation.REDO
: The redo version of another operation.
- There are state transitions between operations. If a type results from the state transition of another type, it is called the inverse type of that type.
- The inverse type of
REGULAR
operation is of typeUNDO
. - The inverse type of
UNDO
operation is of typeREDO
. - The inverse type of
REDO
operation is of typeUNDO
.
- The inverse type of
- The enum type
OperationType
provides agetInverseType()
method.
Then we need to find the inverse operation for each undoable operation. Since UndoableOperation
interface provides a getInverseOperation()
method, the following part is very simple.
Each time undo or redo command is called, the Operation Factory takes a similar approach, returns the inverse operation of the latest executed operation.
case "undo": return operationRecord.popForUndo() .getInverseOperation();
case "redo": return operationRecord.popForRedo().getInverseOperation();
When an undoable operation is executed, the Controller will attempt to record it:
if (operation instanceof UndoableOperation) {
operationRecord.record((UndoableOperation) operation);
}
So the operation is given to an Operation Record for further processing. The Operation Record is a structure used to record undoable operations. Basically, it maintains two stacks:
- An undo stack for recording
REGULAR
andREDO
operations. - A redo stack for recording
UNDO
operations.
Then it comes to the recording logic, which is handled by the record()
method:
public void record(UndoableOperation o) {
if (!o.isUndoOperation()) { // type is REGULAR or REDO
undo.push(o);
if (!o.isRedoOperation()) { // type is REGULAR
redo.clear();
}
} else { // type is UNDO
redo.push(o);
}
}
Additionally, let us briefly mention UndoableOperation
and FileUnrelatedUndoableOperation
. These interfaces are designed for certain special operations (like Load
and Save
, or LoadCri
and SaveCri
) where it is necessary to clear some operation records but not all. The separation between these two interfaces makes the application possible to clear part of the records.
In this section, we introduce the core of the Service part: the exec()
method of the Operation object, which executes the operation.
Overall, the implementation details of each operation will be provided in later. Now we only provide an example: the NewDir
operation. Recall that NewDir
corresponds to the newDir
command, which creates a new directory.
@Override
public String exec() throws OperationCannotExecuteException {
try {
directory = new Directory(name, fs.getWorkingDirectory());
fs.storeFile(directory);
return ...
} catch (ModelException e) {
throw new OperationCannotExecuteException(e.getMessage());
}
}
Note that storeFile(File)
is a method of the File System. As part of the Service part, NewDir should not focus on its implementation.
In previous sections, we listed all the File System interfaces, and,
In previous sections, we introduced the implementation details of those interfaces and explained how we simulate a real file system as closely as possible.
We will not elaborate more here.
In previous sections, we emphasized that for the Service part and other parts, entities including virtual disks, files, directories and documents are read-only and appear abstracted by the File System. These rules are set to better simulate a real file system.
These rules also indicates that, Entities cannot modify themselves or attempt to do tasks that are the responsibility of the File System. In previous sections, we listed the tasks that only the File System can do.
We will not elaborate more here.
In previous sections, we only introduced some of our core components. These components are innovative, but understanding them alone is not enough.
We list some other key components and provide brief introductions.
- Implementations of the Entities. In previous sections, we showed the interfaces they provide to the File Systm as well as the other parts, and we showed the restrictions and requirements for their interaction with other parts, ensuring they seem abstracted by the File System.
- Implementations of the Criteria. The File System maintains a criteria list to record the saved criteria. Each criterion has a
check(File)
method to check if a file meets the criterion. - Implementation of the Criterion Factory. In previous sections, we explained the use of the Criterion Factory. In fact, it is the same as the Operation Factory. Given some parameters, it generates the corresponding
Criterion
object. We also showed the interfaces it provides.
- Model
- Model external exception
ModelException
- The general exception class of the Model part, and other parts shall catch this exception. All exceptions to the Model part should extend this exception.
- Model internal exceptions (all extends
ModelException
)CannotDeleteCriterionException
- Indicate that the criterion cannot be deleted. Generally, this is because other criteria depend on this criterion.CannotEditRootDirectoryException
- Indicate that the root directory cannot be edited.CannotInitializeFileException
- Indicate that the file cannot be initialized. This is most likely because the provided parameters do not meet the system requirements.CannotInitializeVDiskException
- Indicate that the virtual disk cannot be initialized. This is most likely because the provided parameters do not meet the system requirements.CriterionNotExistsException
- Indicate that the criterion with the specific name does not exist in the criterion list.DuplicateCriterionNameException
- Indicate that the criterion with the specific name does not exist in the criterion list.DuplicateFilenameException
- Indicate that there is another file in the directory with the same name. The instruction document specifies that, even a directory and a file, or two files of different types, cannot share the same name.FileNotExistsException
- Indicate that the file does not exist in the directory.InvalidCriterionParametersException
- Indicate that the parameter is invalid for creating a criterion.LocalFileSystemException
- Indicate that the there are error(s) from the local file system, which prevents the virtual disk and criteria from being loaded or saved.NoMountedDiskOrWorkingDirectoryException
- Indicate that the command requires a mounted virtual disk and an available working directory, while there is not.VDiskOutOfSpaceException
- Indicate that the virtual disk is out of space to store the new file.WrongAddressSpaceException
- Indicate that the directory does not belong to the virtual disk. From a simulation perspective, this is an error related to address mapping.
- Model external exception
- Service
InvalidCommandException
- Indicate that the user provided command is invalid. This is also the general exception in the Service part.OperationCannotExecuteException
- Indicate that the operation cannot be executed due to various reasons.
- The global exception
CVFS_Exception
- The most general exception class of this CVFS application. All other exceptions in the application should extend this exception.