Skip to content
Valentin Baranov edited this page Jun 22, 2020 · 16 revisions

Welcome to the scenario wiki!

Why Scenario?

Each time when we write the CLI application on Java, we have to answer the following questions and then implement the corresponding logic:

  • design the menu system:
  • which menu should be displayed first?
  • which items (choices) should each menu contain?
  • which menu should be displayed when user chooses the action or types some text?
  • how to prevent the user from typing incorrect value?
  • how to call the needed business logic when some action is chosen?
  • how to be sure that menu won't fall into never-ending cycle (user won't be able to exit the app)?
  • how to separate the menu system from the other application parts, so it can be easily replaced with another UI if needed?
    ... and many others.

It leads to nested if or switch statements, input control classes and other boilerplate code.

The main purpose of the scenario library is to delegate all the CLI menu handling to it, so it will act as a service.


Quick Start Guide

Add the Dependency

Add the following dependency to your project's pom.xml:

⚠️ WARNING! The release version is not published yet and will be ready when this wiki page is complete.

<dependency>
    <groupId>com.github.prifiz</groupId>
    <artifactId>scenario</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

Menu Configuration

Okay, you've just added the needed dependency to your project. What's next?
It's time to forget that you're developer and start designing the menu.
It is really easy. You can even ask someone without any programming skills to configure it. The only required thing to know here is YAML syntax.
Now let's create a simple menu configuration:

  1. Create an empty yaml-file with any name you want, e.g. myfirstmenu.yml
  2. The root element should be "menuSystem:".
  3. Create a menu by specifying its name and text.
    The name should be unique for referencing this menu by the others, the text value will be displayed in your command line app.
    The simplest menu configuration will look like this:
menuSystem:
 # Home Menu
  - name: "home"
    text: |
          Welcome to my awesome app.
          It was quite easy to configure it!

Then we should tell our menu system that the defined menu is the starting point to iterate. To do this, the properties attribute should be added:

menuSystem:
 # Home Menu
  - name: "home"
    properties:
      - home
    text: |
          Welcome to my awesome app.
          It was quite easy to configure it!

Currently, only three properties are supported:

  • home - menu with this property value is the starting point to iterate over the menu system
  • exit - menu with this property value is the end of the menu system. It is used in menu validations.
  • noInput - menu with this property value won't require any user input.
    E.g. if the menu has this properties attribute values:
properties:
  - exit
  - noInput

the menu won't wait for any user input, and the whole menu system will stop the execution.

This is still not actually a menu system because it doesn't provide any options to choose. Let's add a couple.

menuSystem:
 # Home Menu
  - name: "home"
    properties:
      - home
    text: |
          Welcome to my awesome app.
          It was quite easy to configure it!
    items:
      - name: "option1"
        text: "Option One"

      - name: "option2"
        text: "Option Two"

What happened here:

  • our menu will display the welcome text specified in "text" attribute,
  • next, the two options will be displayed:
    • Option One
    • Option Two

But... it is still not a menu system because it can't go to the chosen menu.
To do this, let's add two other menus and link the items with them by "gotoMenu" attribute:

menuSystem:
  # Home Menu
  - name: "home"
    properties:
      - home
    text: |
          Welcome to my awesome app.
          It was quite easy to configure it!
    items:
      - name: "option1"
        text: "Option One"
        gotoMenu: "menuOne"

      - name: "option2"
        text: "Option Two"
        gotoMenu: "menuTwo"
  
  # Menu One
  - name: "menuOne"
    text: "Welcome to Menu One!"

  # Menu Two
  - name: "menuTwo"
    text: "Welcome to Menu Two!"

Well done!
Save the myfirstmenu.yml file somewhere to the project resources.

Running the Menu System

Now we are finally ready for returning to Java code to enable the menu.
Open the class of your app where you want the menu to be called.

import org.prifizapps.walkers.MenuWalkerInitiator;
import java.io.IOException;
import java.io.InputStream;

public class MyFirstMenuClass {

    public void runMenuSystem() throws IOException {
        InputStream inputStream = this.getClass().getResourceAsStream("myfirstmenu.yml");
        MenuWalkerInitiator.initMenu(inputStream).run();
    }

}

That's it! You've just added a CLI menu system to your application by typing only 2 lines of java code!
Now you can run your app and type "Option One" for going to Menu One or "Option Two" for going to Menu Two.

Interaction With the Client Code

Well, now you're able to configure the menu system and run it from your Java app.
But the menu system should interact with application's business logic.
To do this, the adapter classes should be used.

How to create an adapter

Create a class implementing org.prifizapps.adapters.CommandLineAdapter interface. E.g. if your application is a calculator, the AdditionAdapter class can be created like the following:

// Adapter example
public class AdditionAdapter implements CommandLineAdapter {

    private String addFirst;
    private String addSecond;

    Calculator calculator = new CalculatorImpl();

    public String execute() {
        int first = Integer.parseInt(addFirst);
        int second = Integer.parseInt(addSecond);
        return String.valueOf(calculator.add(first, second));
    }
}

Here, the Calculator instance is an example of your application class which implements some business logic.
The adapter interacts with the menu system by one of the following ways:

  • Menu system can set some adapter's field value
  • Menu system can call the adapter's execute() method
  • Menu system can first set the field value and then call execute() method

When the adapter is created, the field and methods bindings should be added to menu configuration via "bindings" attribute.
This code binds the specified field:

  - name: "addFirst"
    text: "Enter first value:"
    gotoMenu: "addSecond"
    bindings: { field: "addFirst" }

It means that when user inputs the requested first value, the entered value will be set to "addFirst" field of any found adapter class.
If you want to bind a field of the only specific class, you should just add the class name reference before the field name:

bindings: { field: "AdditionAdapter.addFirst" }

⚠️ In the current library version, the multiple adapters can't be specified. For example, you can't configure the following:

bindings: { field: "Adapter1.addFirst, Adapter2.addFirst" }

To bind the execute method, the bindings should be configured like this:

bindings: { runAdapter: "AdditionAdapter" }

This will call the AdditionAdapter.execute() method.
And for both field and method:

bindings: { field: "AdditionAdapter.addFirst", runAdapter: "AdditionAdapter" }

Adapters Registration

Well, we have an adapter class and the binding configuration. To make this work together, we should register our adapter in the MenuWalker:

MenuWalkerInitiator.initMenu(inputStream)
    .registerAdapter(new AdditionAdapter())
    .run();

Now the menu system will find our adapter and link it.
That's all! Our menu system is ready to use!

Menu System Validations

The library provides in-build menu validations.
When the run() method is called, the menu system performs self-diagnostic in order to find some issues with the menu system architecture, such as:

  • Dead Ends - menus which lead to nowhere. The

Input Check

Clone this wiki locally