com.gmarciani.gmparser.controllers.App.java Source code

Java tutorial

Introduction

Here is the source code for com.gmarciani.gmparser.controllers.App.java

Source

/*   The MIT License (MIT)
 *
 *   Copyright (c) 2014 Giacomo Marciani
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *   
 *   The above copyright notice and this permission notice shall be included in all
 *   copies or substantial portions of the Software.
 *   
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *   SOFTWARE.
*/

package com.gmarciani.gmparser.controllers;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import com.gmarciani.gmparser.models.commons.menu.Menus;
import com.gmarciani.gmparser.models.grammar.Grammar;
import com.gmarciani.gmparser.models.grammar.analysis.GrammarAnalysis;
import com.gmarciani.gmparser.models.grammar.transformation.GrammarTransformation;
import com.gmarciani.gmparser.models.parser.ParserType;
import com.gmarciani.gmparser.models.parser.cyk.CYKParser;
import com.gmarciani.gmparser.models.parser.lr.LROneParser;
import com.gmarciani.gmparser.views.AppMenus.MainMenu;
import com.gmarciani.gmparser.views.AppMenus.ParserMenu;
import com.gmarciani.gmparser.views.AppMenus.TransformationMenu;

import static org.fusesource.jansi.Ansi.*;

/**
 * <p>Main user-app interaction controller.<p>
 * <p>It manages the whole app workflow.<p>
 *  
 * @see com.gmarciani.gmparser.controllers.UiManager
 * @see com.gmarciani.gmparser.controllers.Output
 * 
 * @author Giacomo Marciani
 * @version 1.0
 */
public final class App {

    private static App instance;

    private Options options;
    private Menus menus;

    private Output output;

    private App() {
        this.setupCli();
        this.setupControllers();
    }

    /**
     * Returns the app controller singleton instance.
     * 
     * @return the controller singleton instance.
     */
    public synchronized static App getInstance() {
        if (instance == null)
            instance = new App();
        return instance;
    }

    /**
     * Sets up the GMParser command-line interface environment.
     */
    private void setupCli() {
        UiManager.installAnsiConsole();
        this.options = UiManager.buildCommandLineOptions();
        this.menus = UiManager.buildMenus();
    }

    /**
     * Sets up the GMParser controllers: Output controller, for single entry-point to output display.
     */
    private void setupControllers() {
        this.output = Output.getInstance();
    }

    /**
     * Returns the app output interface.
     * 
     * @return the output interface.
     */
    public Output getOutput() {
        return this.output;
    }

    /**
     * Shows the GMParser welcome splash screen, based on ASCII art style.
     */
    public void printWelcome() {
        String welcome = UiManager.getLogo();
        System.out.println(ansi().fg(UiManager.AppUI.LOGO_COLOR).bold().a(welcome).reset());
    }

    /**
     * <p>The app entry-point method.<p>
     * <p>This method is activated when no arguments neither options are specified.<p>
     * 
     * @param args command-line arguments.
     * @throws ParseException
     */
    public void play(String[] args) throws ParseException {
        CommandLineParser cmdParser = new GnuParser();
        CommandLine cmd = cmdParser.parse(this.options, args);
        String unrecognizedArguments[] = cmd.getArgs();

        if (unrecognizedArguments.length != 0) {
            this.getOutput().onUnrecognizedArguments(unrecognizedArguments);
            this.quit();
        }

        if (cmd.getOptions().length == 0) {
            boolean loop = true;
            while (loop) {
                this.playMenu();
                loop = this.getContinued();
            }
        } else if (cmd.hasOption("analyze")) {
            final String vals[] = cmd.getOptionValues("analyze");
            final String grammar = vals[0];
            if (!this.validateGrammar(grammar))
                return;
            this.analyze(grammar);
        } else if (cmd.hasOption("transform")) {
            final String vals[] = cmd.getOptionValues("transform");
            final GrammarTransformation transformation = GrammarTransformation.valueOf(vals[0]);
            final String grammar = vals[1];
            if (!this.validateGrammar(grammar))
                return;
            this.transform(grammar, transformation);
        } else if (cmd.hasOption("parse")) {
            final String vals[] = cmd.getOptionValues("parse");
            final ParserType parserType = ParserType.valueOf(vals[0]);
            final String word = (vals[1] == null || vals[1].equals(Grammar.EPSILON.toString())) ? "" : vals[1];
            final String grammar = vals[2];
            if (!this.validateGrammar(grammar))
                return;
            this.parse(grammar, word, parserType);
        } else if (cmd.hasOption("help")) {
            this.help();
        } else if (cmd.hasOption("version")) {
            this.version();
        }
        this.quit();
    }

    /**
     * Plays the main menu.
     */
    private void playMenu() {
        int choice = this.menus.run(MainMenu.IDENTIFIER);

        if (choice == MainMenu.ANALYZE) {
            String grammar = this.getGrammar();
            if (!this.validateGrammar(grammar))
                return;
            this.analyze(grammar);
        } else if (choice == MainMenu.TRANSFORM) {
            String grammar = this.getGrammar();
            GrammarTransformation transformation = this.getGrammarTransformation();
            if (!this.validateGrammar(grammar))
                return;
            this.transform(grammar, transformation);
        } else if (choice == MainMenu.PARSE) {
            String grammar = this.getGrammar();
            String word = this.getWord();
            ParserType parser = this.getParser();
            if (!this.validateGrammar(grammar))
                return;
            this.parse(grammar, word, parser);
        } else if (choice == MainMenu.HELP) {
            this.help();
        } else if (choice == MainMenu.QUIT) {
            this.quit();
        } else {
            this.getOutput().onWarning("Unrecognized choice\n");
        }
    }

    /**
     * <p>Analyzes the specified grammar, represented as string.<p> 
     * <p>This method is activated by the command-line option {@code -analyze}.<p>
     * 
     * @param strGrammar grammar to analyze, represented as string.
     */
    private void analyze(String strGrammar) {
        Grammar grammar = Grammar.generateGrammar(strGrammar);
        this.getOutput().onResult("This is your grammar: " + grammar.toString());
        GrammarAnalysis analysis = grammar.generateGrammarAnalysis();
        analysis.setTitle("GRAMMAR ANALYSIS");
        this.getOutput().onResult("Here we are! This is your grammar analysis");
        this.getOutput().onDefault(analysis.toString());
    }

    /**
     * <p>Executes the specified transformation to the specified grammar, represented as string.<p> 
     * <p>It also generates a grammar analysis both for input grammar and output grammar.<p>    * 
     * <p>Available transformations: 
     * remove ungenerative symbols (RGS), 
     * remove unreacheables symbols (RRS), 
     * remove useless symbols (RUS), 
     * remove epsilon productions (REP), 
     * remove unit productions (RUP) 
     * and generate CHosmky-Normal-Form (CNF).<p>
     * <p>This method is activated by the command-line option {@code -transform}.<p>
     * 
     * @param strGrammar grammar to transform, represented as string.
     * @param transformation target transformation to run on grammar.
     */
    private void transform(String strGrammar, GrammarTransformation transformation) {
        Grammar grammar = Grammar.generateGrammar(strGrammar);

        this.getOutput().onResult("This is your grammar: " + grammar);
        this.getOutput().onResult("This is your transformation: " + transformation);

        GrammarAnalysis analysisIn = grammar.generateGrammarAnalysis();
        analysisIn.setTitle("GRAMMAR ANALYSIS: input grammar");

        if (transformation == GrammarTransformation.RGS) {
            grammar.removeUngenerativeSymbols();
        } else if (transformation == GrammarTransformation.RRS) {
            grammar.removeUnreacheableSymbols();
        } else if (transformation == GrammarTransformation.RUS) {
            grammar.removeUselessSymbols();
        } else if (transformation == GrammarTransformation.REP) {
            grammar.removeEpsilonProductions();
        } else if (transformation == GrammarTransformation.RUP) {
            grammar.removeUnitProductions();
        } else if (transformation == GrammarTransformation.CNF) {
            grammar.toChomskyNormalForm();
        }

        GrammarAnalysis analysisOut = grammar.generateGrammarAnalysis();
        analysisOut.setTitle("GRAMMAR ANALYSIS: output grammar");

        this.getOutput().onResult("Here we are! This is your transformation result");
        this.getOutput().onDefault(analysisIn.toString());
        this.getOutput().onDefault(analysisOut.toString());
    }

    /**
     * <p>Parses the specified word by the specified parser, according to the specified grammar, represented as a string.<p> 
     * <p>It shows a grammar analysis for the specified grammar, and a complete parsing session report.<p>
     * <p>This method is activated by the command-line option {@code -parse}.<p>
     * 
     * @param strGrammar grammar to parse with, represented as a string.
     * @param word word to parse.
     * @param parserType parser to parse with.
     */
    private void parse(String strGrammar, String word, ParserType parser) {
        Grammar grammar = Grammar.generateGrammar(strGrammar);

        this.getOutput().onResult("This is your grammar: " + grammar);
        this.getOutput().onResult("This is your word: " + word);
        this.getOutput().onResult("This is your parser: " + parser);

        if (!grammar.isContextFree() && !grammar.isRegular()) {
            this.getOutput().onWarning(
                    "Your grammar is not Context-Free, but " + grammar.getType().getName() + ". Aborting parsing.");
            return;
        }

        this.getOutput().onResult("Here we are! This is your parsing session results!");

        if (parser.equals(ParserType.CYK))
            this.getOutput().onDefault(CYKParser.parseWithSession(grammar, word).toFormattedParsingSession());
        if (parser.equals(ParserType.LR1))
            this.getOutput().onDefault(LROneParser.parseWithSession(grammar, word).toFormattedParsingSession());
    }

    /**
     * <p>Shows the GMParser helper.<p> 
     * <p>This method is activated by the command-line option {@code -help}.<p>
     */
    private void help() {
        HelpFormatter helper = new HelpFormatter();
        helper.printHelp("gmparser", this.options);
        System.out.print("\n");
        System.out.flush();
    }

    /**
     * <p>Shows the GMParser version.<p> 
     * <p>This method is activated by the command-line option {@code -version}.<p>
     */
    private void version() {
        System.out.println("GMParser version: 1.0");
        System.out.flush();
    }

    /**
     * Quits the app.
     */
    public void quit() {
        this.getOutput().onResult("Good bye!");
        UiManager.uninstallAnsiConsole();
        System.exit(0);
    }

    /**
     * <p>Lets the user continue or quit the app workflow after a correct execution or warning/exception.<p>
     *
     * @return true if the user wants to continue the workflow; false, otherwise.
     */
    private boolean getContinued() {
        System.out.print("[continue? (y/n)]> ");
        System.out.flush();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = null;
        try {
            input = br.readLine();
        } catch (NumberFormatException | IOException e) {

        }
        System.out.print("\n");
        return (input.equals("y") ? true : false);
    }

    /**
     * <p>Lets the user specify the grammar to analyze, transform, or parse with.<p>
     * 
     * @return the input grammar, represented as a string.
     */
    private String getGrammar() {
        System.out.print("[grammar]> ");
        System.out.flush();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = null;
        try {
            input = br.readLine();
        } catch (NumberFormatException | IOException exc) {
            this.getOutput().onException(exc.getMessage());
        }
        System.out.print("\n");
        return input;
    }

    /**
     * <p>Lets the user specify the word to parse.<p>
     * <p>The empty word is normally specified by an empty input. The user doesn't need to care about any end marker (like $ for LR(1)).<p>
     * 
     * @return the word to parse.
     */
    private String getWord() {
        System.out.print("[word]> ");
        System.out.flush();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = null;
        try {
            input = br.readLine();
        } catch (NumberFormatException | IOException exc) {
            this.getOutput().onException(exc.getMessage());
        }
        System.out.print("\n");
        return input;
    }

    /**
     * <p>Lets the user selected the desidered grammar transformation.<p>
     * <p>Available transformations: 
     * remove ungenerative symbols (RGS), 
     * remove unreacheables symbols (RRS), 
     * remove useless symbols (RUS), 
     * remove epsilon productions (REP), 
     * remove unit productions (RUP) 
     * and generate CHosmky-Normal-Form (CNF).<p>
     * 
     * @return the selected grammar transformation.
     */
    private GrammarTransformation getGrammarTransformation() {
        int choice = this.menus.run(TransformationMenu.IDENTIFIER);

        if (choice == TransformationMenu.REMOVE_UNGENERATIVE_SYMBOLS)
            return GrammarTransformation.RGS;
        if (choice == TransformationMenu.REMOVE_UNREACHEABLES_SYMBOLS)
            return GrammarTransformation.RRS;
        if (choice == TransformationMenu.REMOVE_USELESS_SYMBOLS)
            return GrammarTransformation.RUS;
        if (choice == TransformationMenu.REMOVE_EPSILON_PRODUCTIONS)
            return GrammarTransformation.REP;
        if (choice == TransformationMenu.REMOVE_UNIT_PRODUCTIONS)
            return GrammarTransformation.RUP;
        if (choice == TransformationMenu.GENERATE_CHOMSKY_NORMAL_FORM)
            return GrammarTransformation.CNF;
        return null;
    }

    /**
     * <p>Let the user select the desidered parse type.<p>
     * <p>Available parsers:
     * Cocke-Younger-Kasami Parser (CYK)
     * and LR(1) Parser (LR).<p>
     * 
     * @return parserType selected parser type.
     */
    private ParserType getParser() {
        int choice = this.menus.run(ParserMenu.IDENTIFIER);

        if (choice == ParserMenu.CYK) {
            return ParserType.CYK;
        } else if (choice == ParserMenu.LR1) {
            return ParserType.LR1;
        } else {
            return null;
        }
    }

    /**
     * <p>Validates the input grammar, represented as a string.<p>
     * 
     * @param strGrammar input grammar, represented as a string.
     * 
     * @return true if the string correctly represents a grammar; false, otherwise.
     */
    private boolean validateGrammar(String strGrammar) {
        if (!Grammar.validate(strGrammar)) {
            this.getOutput().onWarning("Grammar syntax error\n");
            return false;
        }
        return true;
    }

}