1. Project: Duke Academy

Duke Academy is a Java based programming practice application equipped with a wide range of questions related to the field of Data Structures and Algorithms.

2. Overview

Duke Academy was built to emulate a Command Line Application where users interact with the GUI by typing commands into a command box. This is to allow new learners to also familiarize themselves with using a Command Line Interface, an invaluable skill for all programmers.

DukeAcademyGUI
Figure 1. Main GUI of Duke Academy

Duke Academy is an integrated platform with a built-in text editor, an auto-grader and a note-taking feature.

DukeAcademy can also be used as a teaching tool by educators as they can set their own questions to be loaded by their students using our simplified text format.

3. My Roles and Contributions

The following segment illustrates the different roles and tasks that I have performed for our team in detail. It also provides a bit of justification for the different choices that I have made pertaining to these roles and tasks.

3.1. Change underlying architecture to suit our needs [refactor]

Due to the increased complexity of our product, a large part of the existing architecture of AddressBook3 had to be changed. Most of the changes can be summarized into the 3 following sections :

3.1.1. Separate Logic component

  • Split the old Logic interface in AddressBook3 into separate interfaces, each with their own specialized functions.

  • The new interfaces are CommandLogic, QuestionsLogic, ProgramSubmissionLogic and NotesLogic.

  • Each interface facilitates operations pertaining to a different part of our application.

Justification :

  • Functionality of our application can be split into more cohesive components

  • Reduce coupling to make the codebase more testable and maintainable

3.1.2. Implemented Observer pattern

  • Created a new simple and lightweight observable class that can be customized using Java generics.

Justification :

  • Enable UI components to observe and reflect changes in the application state without introducing them as unnecessary dependencies.

  • JavaFX’s native Observable class was over-engineered for our use case.

3.1.3. Refactor Command pattern

  • Change the implementation of the Command pattern to be more flexible.

  • execute() no longer depends on Model.

  • Parsing of command arguments is now handled by individual commands.

  • Open-closed principle is maintained by registering commands into CommandLogic with a Supplier.

  • Supplier is in charge of injecting dependencies into the various commands.

Justification :

  • New commands no longer depend on Model and have their own dependencies.

  • New commands require their own unique argument parsing logic.

3.2. Program evaluation feature [feature]

  • What it does :
    Allows users' solutions to be automatically evaluated by our application. The solution is evaluated against test cases specified in the Question class.

  • Justification :
    It is difficult to come up with your own test cases to evaluate your solutions whenever you attempt programming questions. It is also very cumbersome to run each test case manually. Allowing users to automatically have their solutions evaluated will allow them to focus more on their learning.

  • Highlights :

    • Compile and runtime errors are also displayed to the user in the event that they occur so that users are able to learn from their mistakes.

    • A Time Limit Exceeded error is displayed if the user’s program takes too long to complete. This will allow users to know if their solutions are properly optimized.

ProgramEvaluation
Figure 2. Program evaluation example
CompileError
Figure 3. Compile error example

3.3. Note-taking feature [feature]

  • What it does :
    Allows users to record notes in the form of text or a simple sketch.

  • Justification :
    Being able to pen down ideas is extremely useful when coming up with solutions to programming questions. Furthermore, being able to take notes for future reference will help to reinforce our users' learning.

  • Highlights :
    In addition to taking notes in the form of text, user’s are also able to sketch their ideas within a a sketch pad and have them saved for future reference. This is particularly useful when dealing with data structures.

NotePage
Figure 4. Notes page preview

3.4. Tabs [feature]

  • What it does :
    Splits the various functions of the application — browsing and viewing of questions, attempting of questions and evaluation of results, note-taking, into individual tabs.

  • Justification :
    Separating the various functions of our application into their own separate tabs will reduce clutter and enable users to be more organized in their learning.

  • Highlights :
    In line with the Command Line theme, the user is able to switch tabs by typing the command "tab" instead of using a mouse-click.

4. Other Contributions

  • Project management

    • Managed and enforced branching workflow on GitHub

    • Set up auto-publishing of docs (commit)

    • Managed releases v1.3 - v1.4 on GitHub (2 releases)

  • Product design:

    • Updated the GUI layout and color scheme (Pull requests #112)

  • Documentation:

    • Cosmetic tweaks to contents of the User Guide (Pull requests #13 #34)

    • Actively contributed to the writing of the User Guide and Developer Guide (Pull requests #77 #83 #185 #190)

  • Testing:

    • Wrote tests to validate the code base (Pull requests #87 #88)

    • Actively wrote tests for most major pull requests

  • Community:

    • Reported bugs and suggestions for other teams in the class (Liberry)

5. Code Contributions

Click the links below to view my code contributions on RepoSense. On RepoSense, you can select options to view my functional code and test code.

6. Contributions to the User Guide

Given below are some of the sections that I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

6.1. Workspace Tab

The workspace is where you will work on the various questions.

workspace
  1. Problem Statement Display

    Displays the question that you are currently attempting.

  2. Program Evaluation Panel

    This panel displays the correctness of your program when evaluated against the pre-defined list of test cases tied to the question you are solving.

  3. Editor

    A built-in editor for you to write your code.

  • To submit your attempt, enter submit into the CommandBox.

  • To begin your attempt on a question, enter attempt <Qn Index> into the CommandBox.

  • To submit your attempt, enter submit into the CommandBox.

6.2. Notes Tab

The notes page is where you can record notes or even sketch out some of your ideas to aid your learning process.

notespage
  1. NotesListDisplay

    Displays all of the notes that you have previously created.

  2. CurrentNoteTitle

    Shows the title of the note that you are currently viewing/editing.

  3. NoteTextInput

    Input space for any text-based notes that you wish to record.

  4. Sketchpad

    A canvas for you to draw any ideas you may have pertaining to data structures!

  • To create a new note, enter newnote <Title> into the CommandBox

  • To open an exiting note, enter `opennote <Id> into the CommandBox.

  • The id of the note is the number that appears next to the title in the NotesListDisplay.

  • To save any changes to your notes, enter savenote into the CommandBox.

6.3. Submitting your answer: submit

Submits your solution.​ Your code will be compiled and run against test cases. The results will be displayed in the ProgramEvaluationDisplay on the bottom left of the GUI.

Format: submit

  • Make sure to check that your code compiles or you will receive an error message.

  • Remember to import the relevant packages that you have used in your code!

submit

7. Contributions to the Developer Guide

Given below are some of the sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

7.1. Logic components

7.1.1. Overview

There are 4 main logic components in the architecture of Duke Academy. They are CommandLogic, QuestionsLogic, ProgramSubmissionLogic and NotesLogic. Each logic component serves as a facade for basic operations with regards to Commands, Questions, ProgramSubmission and Notes.

They orchestrate the execution of these operations internally and expose a simple interface for other components, such as the UI to utilize.

The logic components are interfaces so their implementation can be changed easily.

Class diagrams for various logic classes

CommandLogicClassDiagram ProgramSubmissionLogicClassDiagram NotesLogicClassDiagram QuestionsLogicClassDiagram

CommandLogic:

  • Only has one method which is used by the application to execute commands.

QuestionsLogic:

  • Deals with all CRUD operations pertaining to the questions found in the application.

  • Keeps track a selected question which represents the Question that is currently being viewed by the user.

ProgramSubmissionLogic:

  • Performs the evaluation of the user’s program submissions.

  • Keeps track of a currently attempting question which is used to test user program submissions.

  • Uses the UserProgramChannel interface to enable other components such as the UI to serve as a source for UserProgram without introducing them as dependencies.

NotesLogic:

  • Deals with all CRUD operations pertaining to the notes found in the application.

  • Uses the NoteSubmissionChannel interface to enable other components such as the UI to serve as a source for notes without introducing them as dependencies.

7.1.2. CommandLogic implementation

The standard implementation of the CommandLogic is the CommandLogicManager class.

Overview:

CommandLogicManager
Figure 5. Associations of the CommandLogicManager
  • For commands to be parsed by CommandLogic, they have to be registered with registerCommand().

  • A CommandSupplier and a command word is required to register a Command

  • A Command can also be registered with a CommandFactory.

  • When executeCommand(commandText) is invoke, CommandLogicManager searches all previously registered commands for the right one to execute.

  • Storage of these keys and the parsing of the commandText argument is performed by CommandParser.

  • CommandSupplier is a functional interface that returns a Command.

  • Each CommandSupplier is mapped to a command word

  • This command word is used to search for the appropriate Command

  • CommandParser splits the text input of the user into the command word and arguments.

  • Each Command is responsible for parsing its own arguments.

  • InvalidCommandArgumentException is thrown if the arguments do not match the specification of the command.

CommandLogicSequence
Figure 6. Sequence diagram for command execution

The basic sequence of command execution is as follows:

  1. Command is registered upon startup by the application driver

  2. Command and matching command word is stored in CommandParser by CommandLogicManager

  3. User enters command text

  4. Application driver passes the command text as String to CommandLogicManager

  5. CommandLogicManager passes command text to CommandParser for parsing and Command retrieval

  6. CommandLogicManager receives and executes Command

7.1.3. NotesLogic implementation

The standard implementation of the NotesLogic interface is the NotesLogicManager. It relies on SketchManager to handle loadiand saving of the Sketches. It also stores the notes in the application in the form of a NoteBank. (View [Design-Storage] for more details.)

Overview:

NotesLogicManager
Figure 7. Associations of the NotesLogicManager
  • Allows other components of the application to make changes to the current state of notes using its interface.

  • Other components of the application can also observe the current state of notes by getting an ObservableList through getAllNotesList()

  • NotesLogicManager constructor takes in a NoteBankStorage instance which is used to load the initial QuestionBank and to save subsequent revisions to it.

  • The main bulk of the CRUD operations are handled by the NoteBank class.

  • NotesLogicManager saves the NoteBank methods along after each change

  • NoteSubmissionChannel is required to provide a pair of values — a Note and a WritableImage instance to represent the user’s sketch.

SketchManager:

SketchManagerClassDiagram
Figure 8. Class diagram of the SketchManager
  • Handles the saving, loading and deleting of sketches.

  • Deals with sketches in two formats, WritableImage class used by the JavaFX UI components for rendering the image, and png form in storage.

  • Converts sketches between the two formats upon loading/before saving.

NoteSavingSequence
Figure 9. Sequence diagram of saving a note

The basic sequence of saving a user’s note from a UI component is as follows:

  1. Register the UI component as the NoteSubmissionChannel

  2. Invoke the saveNoteFromSubmissionChannel() method

  3. Note and sketch is retrieved from the NoteSubmissionChannel

  4. Note is saved by the NoteBankStorage while the sketch is saved by the SketchManager

7.2. Evaluation of user program submissions

The evaluation of the user’s programs is facilitated by the testexecutor package.

7.2.1. Entry point

  • TestExecutor contains a single method runTestCases(testCases, program) which evaluates a UserProgram against a list of TestCase.

TestCase - stores an input and an expected value.

UserProgram - stores the name of the class which contains the main method along with the source code (note that the class name must match the source code for it to be evaluated successfully).

Class Diagrams for UserProgram and TestCase

UserProgramClassDiagram TestCaseClassDiagram

  • The result of the program evaluation is returned as a TestResult object.

TestResult - encapsulates all possible outcomes of evaluating the user’s program. It is contains TestCaseResult and CompileError.

TestResultDiagram
Figure 10. Class diagram for TestResult

7.2.2. Implementation overview

The evaluation of a user’s program is done in 5 main steps, each handled by a specialized interface. The 5 steps include:

  1. Create a Java file inside a temporary directory and write the source code into the file.

  2. Compile the Java file. Catch and store any compile errors.

  3. Execute the generated Class file and provide the inputs of the test cases.

  4. Collect and store any output from the program.

  5. Package the output, errors and results as a TestResult instance.

The 3 specialized interfaces used are CompilerEnvironment, Compiler and ProgramExecutor. They provided through dependency injection in the TestExecutor constructor.

TestExecutor acts as an orchestrator for the 3 interfaces.

CompilerEnvironment - in charge of creating a temporary folder in the user’s file system to create Java files. This temporary folder is deleted in closed(). Uses JavaFile.

CompilerEnvironment
Figure 11. Associations of CompilerEnvironment

Compiler - in charge of compiling the Java files into Class files at a given file path. Uses ClassFile.

Compiler
Figure 12. Associations of Compiler

ProgramExecutor - in charge of executing the compiled Class files. Uses ProgramOutput.

ProgramExecutor
Figure 13. Associations of ProgramExecutor

Models classes:

  • JavaFile - contains the canonical name and class path of a Java file with various convenience methods. Note that the file must actually exist or an FileNotFoundException is thrown during instantiation.

  • ClassFile - contains the canonical name and class path of a Java file with various convenience methods. Note that the file must actually exist or an FileNotFoundException is thrown during instantiation.

  • ProgramInput - contains the String input to be fed into the user’s program.

  • ProgramOutput - contains the String output produced by the user’s program. It also contains convenience methods for producing different outputs.

The basic flow of a program evaluation is as follows:

TestExecutorSequenceDiagram
Figure 14. Sequence diagram for the evaluation of a user’s program
  1. TestExecutor calls clearEnvironment() of CompilerEnvironment to remove any leftover files from previous program evaluations.

  2. TestExecutor calls createJavaFile() of CompilerEnvironment to create the Java file with the correct class name and source code.

  3. TestExecutor calls compileJavaFile() of Compiler to compile the newly created Java file.

  4. For each test case, TestExecutor calls executeProgram of StandardProgramExecutor with the corresponding input to retrieve a CompletableFuture of the results.

  5. TestExecutor sets a timeout on the CompletableFuture and maps the result into a TestCaseResult.

  6. TestExecutor packages all the errors and results into a single TestResult instance.

  • Note that if the evaluation CompletableFuture async task timesout before it is completed, an errored TestCaseResult with a "Time limit exceeded!" error message is returned instead.

  • Compile errors and runtime errors will also be reflected in the TestResult and TestCaseResult models respectively.

7.2.3. User interaction

In the application, the user’s interactions when submitting a program is as follows:

  1. User submits program after typing it into the Editor UI component.

  2. ProgramSubmissionLogic retrieves the program from Editor and evaluates it against the currently attempting Question in QuestionLogic.

  3. ProgramEvaluationPanel observes the latest TestResult and reflects new result in the UI.

  • Editor must have been set as the UserProgramChannel in ProgramSubmissionLogic.

  • ProgramEvaluationPanel must be observing the test result Observable provided by ProgramSubmissionLogic.

  • View [Logic-ProgramSubmissionLogic] for details about the sequence of events

7.2.4. Design considerations

Aspect : How the programs are run
Alternative 1 : Use native Java packages and run the program locally (current choice) Alternative 2 : Host an online server which receives user programs via HTTP requests

Pro : No additional installation requirements is needed from the user

Pro : Can support multiple languages

Pro : No internet connection is required

Pro : Scale of tests can be increased

Pro : Easy to implement

Pro : Reduce strain on user’s machine

Con : Creates files in the user’s machine (dependent on memory/permissions)

Con : Difficult to implement

Con : Can only support the execution of Java programs

Con : Dependent on internet