Cartesian Plane Lesson 12: Sample Applications

Following is a description of the sample applications for Cartesian Plane, Lesson 12.

Cartesian Plane Lesson 12: GUI Beginnings

GitHub repository: Cartesian Plane Part 12

User Input Module, Review

Recall that the User Input module contains two classes that are used to obtain and parse input. They are:

  • CommandReader
    An instance of this class contains a BufferedReader, which can be used to read input one line at a time. The source of the BufferedReader is not specified; it could be a file, the application console, a Web page, or other text source. The principal instance method in this class is nextCommand(String prompt), which optionally prints a prompt to stdout, obtains a line of input and transforms it into a ParsedCommand object. It also contains a class method, parseCommand(String line), which can parse any line of text, producing a ParsedCommand object.
  • InputParser
    An instance of this class takes a ParsedCommand object, and uses it to configure an Equation object. For example the command/argument set a=4,b=3 declares the variables a and b in an equation, and gives them the values 4 and 3, respectively. Some commands it ignores altogether because they can only be processed at the application level, for example YPLOT and XYPLOT.

Application Package Utility Class

The application package contains a high-level utility class, CommandExecutor, which accepts sequential commands from a Supplier<ParsedCommand>, and executes each command to completion with the assistance of an InputParser. First it feeds the command to InputParser for configuration processing, then performs any additional processing that may be required; for example, the YPLOT command may be executed, producing a plot via a CartesianPlane object.

■ Constructor

CommandExecutor has a constructor which establishes a connection with a CartesianPlane object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class CommandExecutor
{
    private static final String errorDialogTitle    = "Parsing Error";
    private static final String newl                = System.lineSeparator();
    private static final int    newlLen             = newl.length();
    private static final String nullPlaneError      = 
        "CartesianPlane may not be null";
    
    private final CartesianPlane    plane;
    private InputParser             inputParser;
    // ...
    public CommandExecutor( CartesianPlane plane )
    {
        if ( plane == null )
            throw new IllegalArgumentException( nullPlaneError );
        this.plane = plane;
    }
    // ...
}

■ Method exec( Supplier reader, Equation equation )

The principal public method in this class, exec, takes a Supplier<ParsedCommand> object and an optional Equation object. It accepts input from the supplier in a loop, which terminates when it receives an EXIT command. The pseudocode looks like this:

private InputParser inputParser
exec( Supplier<ParsedCommand> reader, Equation equation )
    inputParser = new InputParser( equation );
    do
        get next command from Supplier
        feed the command to inputParser
        if inputParser returns an error:
            display an error message
        else
            perform additional command processing as necessary:
                YPLOT:  plot the equation y = f(x)
                XYPLOT: plot the parametric equation (x,y) = f(t)
                RPLOT:  plot the polar equation r = f(t)
                TPLOT:  plot the polar equation t = f(r)
                OPEN:   read an equation from a file
                SAVE:   save the current equation to a file
    while command != EXIT

Here’s the source code for the exec method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void exec( Supplier<ParsedCommand> reader, Equation equation )
{
    inputParser = new InputParser( equation );
    ParsedCommand       parsedCommand   = null;
    Command             command         = Command.NONE;
    do
    {
        parsedCommand = reader.get();
        command = parsedCommand.getCommand();
        Result  result  = inputParser.parseInput( parsedCommand );
        if ( command == Command.INVALID )
            showUsage();
        else if ( !result.isSuccess() )
            showError( result );
        else if ( command == Command.YPLOT )
            plot( () -> inputParser.getEquation().yPlot() );
        else if ( command == Command.XYPLOT )
            plot( () -> inputParser.getEquation().xyPlot() );
        else if ( command == Command.RPLOT )
            plot( () -> inputParser.getEquation().rPlot() );
        else if ( command == Command.TPLOT )
            plot( () -> inputParser.getEquation().tPlot() );
        else if ( command == Command.OPEN )
            open( parsedCommand.getArgString() );
        else if ( command == Command.SAVE )
            save( parsedCommand.getArgString() );
        else
            ;
    } while ( command != Command.EXIT );
}

■ Method plot( Supplier<Stream<Point2D>> pointStreamSupplier )

This method produces a plot of a given type. The type of plot is determined by the parameter. The method takes the Point2D stream returned by the Supplier, maps it to a Stream<PlotCommand>, and feeds the resulting stream to the CartesianPlane object for display. The last two lines two lines of code tells the CartesianPlane object to redraw itself.

1
2
3
4
5
6
7
8
9
private void plot( Supplier<Stream<Point2D>> pointStreamSupplier )
{
    plane.setStreamSupplier( () ->
        pointStreamSupplier.get()
        .map( p -> PlotPointCommand.of( p, plane) )
    );
    NotificationManager.INSTANCE
        .propagateNotification( CPConstants.REDRAW_NP );
}

■ Methods open( String name ), save( String name )

These methods read an equation from a file, or save the current equation to a file. All error handling is provided by the FileManager class. If name is an empty string, the FileManager will prompt the operator for a file name. See the FileManager class documentation for complete details.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private void open( String name )
{
    Equation    equation    = name.isEmpty() ? 
        FileManager.open() : FileManager.open( name );
    if ( equation != null )
        inputParser = new InputParser( equation );
}
    
private void save( String name )
{
    Equation    equation    = inputParser.getEquation();
    FileManager.save( name, equation);
}

■ Methods showUsage(), showError( Result result )

These are helper methods that display error messages. I won’t try to explain them. If you want to see the source code you can find it in the GitHub repository.

The Sample Applications

Following is a description of the three sample applications that demonstrate use of the User Input module. The source code is in the app package of the CartesianPlane project. It can be found in the GitHub repository.

⬛ ConsoleInputApp

This application shows how to read and process commands from the console. It starts by creating a BufferedReader from stdin:

InputStreamReader inReader  = new InputStreamReader( System.in );
BufferedReader    bufReader = new BufferedReader( inReader );

The BufferedReader is used to create a CommandReader. The application invokes the exec method of the CommandExecutor class, feeding it a reference to the helper method nextCommand as a supplier. The supplier then acts as an intermediary between the exec method and the CommandReader; every time the exec method needs a new command it calls the nextCommand method; the nextCommand method requests a command from the CommandReader, and returns it to the exec method:

The code for the application follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ConsoleInputApp
{
    private static final CartesianPlane plane   = new CartesianPlane();
    private static final String         prompt  = "Enter a command> ";
    private static CommandReader        reader;
    
    public static void main(String[] args)
    {
        Root    root    = new Root( plane );
        root.start();
        try (
            InputStreamReader inReader  = new InputStreamReader( System.in );
            BufferedReader bufReader = new BufferedReader( inReader );
        )
        {
            reader  = new CommandReader( bufReader );
            CommandExecutor executor    = new CommandExecutor( plane );
            executor.exec( ConsoleInputApp::nextCommand, null );
        }
        catch ( IOException exc )
        {
            exc.printStackTrace();
            System.exit( 1 );
        }
        
        System.exit( 0 );
    }
    
    private static ParsedCommand nextCommand()
    {
        ParsedCommand   command = null;
        try
        {
            command = reader.nextCommand( prompt );
        }
        catch ( IOException exc )
        {
            exc.printStackTrace();
            System.exit( 1 );
        }
        return command;
    }
}

Note: you might think that we could pass a reference to CommandReader.nextCommand to the CommandExecutor,BUT… Supplier.get(), from the java.util.function package does not throw exceptions, and CommandReader.nextCommand does. So the reference to CommandReader.nextCommand has to be wrapped in another method which catches and discards the exception.

⬛ FileInputApp

This application shows how to read and process commands from a file. It is so similar to the console-input application that there’s really not much need to discuss it. There are only two differences:

  • Instead of creating a BufferedReader from stdin, it opens a text file in a FileReader, then creates the BufferedReader from the FileReader.
  • When it calls CommandReader.nextCommand it passes null for the prompt.

Following is the text from the input file, and a picture of the plot it produces. That is followed by the application source code.

# rose
start 0
end 7
step .001
set a=3,n=4,t
x= a sin(n t)cos(t)
y= a sin(n t)sin(t)
xyplot
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class FileInputApp
{
    private static final CartesianPlane plane       = new CartesianPlane();
    private static final String         filePath    = 
        "Equations" + File.separator + "FileInputAppData.txt";
    private static CommandReader        reader;
    
    /**
     * Application entry point.
     * 
     * @param args  command line arguments; not used
     */
    public static void main(String[] args)
    {
        Root    root    = new Root( plane );
        root.start();
        try (
            FileReader inReader  = new FileReader( filePath );
            BufferedReader bufReader = new BufferedReader( inReader );
        )
        {
            reader  = new CommandReader( bufReader );
            CommandExecutor executor    = new CommandExecutor( plane );
            executor.exec( FileInputApp::nextCommand, null );
        }
        catch ( IOException exc )
        {
            exc.printStackTrace();
            System.exit( 1 );
        }
        
        System.exit( 0 );
    }
    
    private static ParsedCommand nextCommand()
    {
        ParsedCommand   command = null;
        try
        {
            command = reader.nextCommand( null );
        }
        catch ( IOException exc )
        {
            exc.printStackTrace();
            System.exit( 1 );
        }
        return command;
    }
}

⬛ DialogInputApp

The main class for this application uses a simple dialog to request commands from the operator. It contains a CommandExecutor, passing to its exec method a reference to a supplier that display a JOptionPane input dialog to obtain a command. The text entered by the operator is converted to a ParsedCommand by the parseCommand( String line ) class method of the CommandReader class. When the operator cancels the dialog, the supplier generates an EXIT command.

Here’s the source code for the application .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class DialogInputApp
{
    private static final String prompt      = "Enter a command> ";
    private static final String dialogTitle = "Command Input";
    
    public static void main(String[] args)
    {
        CartesianPlane  plane   = new CartesianPlane();
        Root            root    = new Root( plane );
        root.start();
        
        CommandExecutor executor    = new CommandExecutor( plane );
        executor.exec( DialogInputApp::getInput, null );
        
        System.exit( 0 );
    }
    
    private static ParsedCommand getInput()
    {
        ParsedCommand   command = null;
        while ( command == null )
        {
            String  input   =
                JOptionPane.showInputDialog(
                    null, 
                    prompt, 
                    dialogTitle, 
                    JOptionPane.QUESTION_MESSAGE
                );
            if ( input == null )    // exit if operator cancels
                command = new ParsedCommand( Command.EXIT, "", "" );
            else if ( input.isEmpty() )  // keep looping on empty input
                ;
            else
                command = CommandReader.parseCommand( input );
        }
        return command;
    }
}