ConsoleView.java

package views;

import java.io.PrintStream;
import java.util.ArrayList;
// System imports
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;


// Local imports
import controller.CommandController;
import controller.UMLController;
import core.ErrorHandler;
import core.UMLFileIO;
//import model.UMLClass;
import model.UMLClassManager;
import observe.Observable;

public class ConsoleView extends View {
	private Scanner scanner;
	private UMLClassManager model;
	private UMLController controller;
	private UMLFileIO fileIO;
	
	// All valid commands
	private LinkedHashMap<String, String[]> validCommands;
	
	/**
	 * Initialize required variables for console
	 */
	public ConsoleView(UMLClassManager model, UMLController controller) {
		scanner = new Scanner(System.in);
		
		fileIO = new UMLFileIO();
		
		this.model = model;
		this.controller = controller;
		this.controller.addObserver(this);
		
		validCommands = new LinkedHashMap<String, String[]>();
		populateValidCommands();
	}
	
	/**
	 * Start input loop
	 */
	public void start() {
		while(true) {
			String input = getCommand();
			
			// Check if command is empty or only whitespace
			if(!input.isEmpty() && !input.replaceAll(" ", "").isEmpty()) {
				int result = execCommand(input, System.out);
				
				// If the result did not execute successfully, print error.
				if(result != 0) {
					System.err.println("Failed to execute command. Got error: ");
					System.err.println(ErrorHandler.toString(result));
				}
			}
		}
	}
	
	/**
	 * Execute the given command and report errors as necessary
	 * @param command - Command to be processed
	 * @param output - Where to print information to
	 * @return boolean indicating if command was successfully executed
	 */
	public int execCommand(String command, PrintStream output) {
		// Split command on white space
		// args[0] = name of the command
		// args[1...] = any arguments for the command
		String[] args = command.split(" ");
		
		// Result of given command
		// Should be updated for each command
		int result = 0;
		
		// Make sure list of args is not empty and a command exists
		if(args.length == 0 || args[0].trim().isEmpty()) {
			return 101;
		}
		
		// Check command, if it is a valid command then perform associated action
		//	Otherwise return false
		if(args[0].equals("exit") || args[0].equals("quit")) {
			output.println("Force quitting...");
			output.println("Goodbye :)");
			
			System.exit(0);
		}

		//split for all add methods
		else if(args[0].equals("add")){
			// after first argument is add check what is being added
			if (args.length < 3)
			//all adds require at least 3 arguments
			{
				return 102;
			}
			if(args[1].equals("class"))
			{
				//add class requires only one more argument, any more or less is rejected
				if (args.length == 3) {
				String className = args[2];
				result = controller.addClass(className);
				if (result == 0)
				{
					//successfully added class
					output.println("Added class \'" + className + "\'.");
				}
				}
				else {
					//if failed the arguments are invalid 
					return 102;
				}
				
			}
			// the add field method
			else if (args[1].equals("field"))
			{
				if(args.length == 5) {
					//takes exactly five arguments; add field classname, type, fieldname
					result = controller.addField(args[2], args[3], args[4]);
					//adds fieldname (if valid) to given exisiting class
					output.println("Added field \'" + args[4] + "\' to class \'" + args[2] + "\' of type \'" + args[3] + "\'.");
				}
				else {
					//invalid arguments
					return 102;
				}
			}
			//add method...method
			else if (args[1].equals("method"))
			{
				
				if(args.length >= 5) {
					//takes at least six arguments of add method class1 returnType methodName (x amount of params)
					//combine all params into a paramlist to pass to controller
					String paramlist = ""; 
					int i = 5;
					while ( i < args.length)
					{
						paramlist = paramlist.concat(args[i] + " ");
						++i;
					}
					// Remove trailing whitespace
					paramlist = paramlist.trim();
					
					result = controller.addMethod(args[2], args[3], args[4], paramlist);
					//adds valid methodName to given exisiting class
					output.println("Added method \'" + args[4] + "\' which accepts \'"+ paramlist + "\' with returnType: \'" + args[3] + "\' to class \'" + args[2] + "\'.");
				}
				else {
					//invalid amount of arguments
					return 102;
				}
			}
			//add relationship method
			else if (args[1].equals("relationship"))
			{
				if(args.length == 5) {
					//takes exactly 5 arguments in order of add relationship class1 type class2
					result = controller.addRelationship(args[2], args[3],  args[4]);
					output.println("Added " + args[3] +  " relationship from \'" + args[2] + "\' to \'" + args[4] + "\'.");
				}
				else {
					//invalid number arguments
					return 102;
				}
			}
		
			}

			//command split for all remove methods
		else if (args[0].equals("remove")){
			//remove requires at least 2 parameters
			if (args.length < 2)
			{
				return 102;
			}
			//remove class method
			else if (args[1].equals("class"))
			{
				if(args.length == 3) {
					// Pull exactly 3 args; remove, class, className
					String className = args[2];
					
					result = controller.removeClass(className);
					if(result == 0)
						output.println("Removed class \'" + className + "\'.");
				} 
				else {
					//invalid arg count
					return 102;
				}
			}
			//remove field method
			else if (args[1].equals("field"))
			{
				if(args.length == 4) {
					//takes exactly 4 arguments; remove, field, classname, fieldname
					result = controller.removeField(args[2], args[3]);
					output.println("Removed field \'" + args[3] + "\' from class \'" + args[2] + "\'.");
				}
				else {
					//invalid argument amount
					return 102;
				}
			}
			//remove method...method
			else if (args[1].equals("method"))
			{
				if(args.length >= 5) {
					//takes at least 5 arguments; remove, method, classname, methodname, paramName(s)
					//take all params after args[3] and concat into a param list
					String paramlist = ""; 
					int i = 4;
					while ( i < args.length)
					{
						paramlist = paramlist.concat(args[i] + " ");
						++i;
					}
					paramlist = paramlist.trim();
					result = controller.removeMethod(args[2], args[3], paramlist);
					output.println("Removed method \'" + args[3] + "\' ( " + paramlist + " )" + " from class \'" + args[2] + "\'.");
				}
				else {
					//invalid amount of parameters
					return 102;
				}
			}
			//remove relationship method
			else if (args[1].equals("relationship"))
			{
				if(args.length == 5) {
					//takes exaclty 5 arguments; remove, relationship, classname1, type, classname2
					result = controller.removeRelationship(args[2], args[3], args[4]);
					output.println("Removed " + args[3] + " relationship between \'" + args[4] + "\' and \'" + args[2] + "\'.");
				}
				else {
					//invalid amount of params
					return 102;
				}
			}

		}
		//split for edit command
		else if (args[0].equals("edit"))
		{
			if(args.length < 3)
			{
				//all edits take at least 3 params
				return 102;
			}
			//edit class method
			else if (args[1].equals("class"))
			{
				if(args.length == 4) {
					//takes 4 args; edit, class, className, newName
					String className = args[2];
					String newName = args[3];
					
					result = ((CommandController) controller).editClass(className, newName);
					if(result == 0)
						output.println("Changed class \'" + className + "\' to \'" + newName + "\'.");
				}
				else {
					// invalid param amount
					return 102;
				}
			}
			//edit field method
			else if (args[1].equals("field"))
			{
				if (args.length == 5) {
					//takes 5 arguments; edit field classname oldfield newfield
					String className = args[2];
					String oldName = args[3];
					String newName = args[4];
						
					result = ((CommandController) controller).editField(className, oldName, newName);
					if(result == 0)
						output.println("Changed field \'" + oldName + "\' to \'" + newName + "\' in class \'" + className + "\'.");
				}
				else {
					//invalid param count
					return 102;
				}
			}
			//edit method..method 
			else if (args[1].equals("method"))
			{
				if(args.length >= 5) {
					// takes at least 5 arguments; edit, method, classname, oldname, newName, params
					String className = args[2];
					String oldName = args[3];
					String newName = args[4];
					String paramlist = ""; 
					int i = 5;
					while ( i < args.length)
					{
						paramlist = paramlist.concat(args[i] + " ");
						++i;
					}
					paramlist = paramlist.trim();
					result = ((CommandController) controller).editMethod(className, oldName, newName, paramlist);
					if(result == 0)
						output.println("Changed method \'" + oldName + "\' from \'" + className + "\' to \'" + newName + "\'.");
				}
				else return 102;
			}
			else if (args[1].equals("relationship"))
			{
				//needs exactly 6 arguments
				if (args.length == 6) {
					String originClass = args[2];
					String oldType = args[3];
					String destClass = args[4];
					String newType = args[5];
					result = controller.editRelationships(originClass, oldType, destClass, newType);
					if (result == 0)
						output.println("Changed relationship from class \'" + originClass + "\' to class \'" + destClass + "\' of type \'" + oldType + "\' to type \'" + newType + "\'.");
					else 
						output.println("");
				
				}
			}
		}

		else if(args[0].equals("save")) {
			String filePath = "";
			
			if(args.length == 2) {
				// If user specified a file path, set the file to that path.
				filePath = args[1];
			}
			else if(args.length < 2) {
				// If the user did not specify a save file check to see if one is already saved.
				// If there is no file saved, prompt for one.
				if(!fileIO.fileSet()) {
					output.println("Save file not set.");
					output.print("Save file: ");
					filePath = scanner.nextLine();
					
				}
			}
			else {
				// If there are more than 2 arguments indicate an error for too many arguments.
				return 102;
			}
						
			if(!fileIO.fileSet()) {
				// Check to see if the file is set, if not make sure that filePath is not empty
				// Otherwise make sure the file ends with a .json and save
				if(filePath.replaceAll(" ", "").isEmpty()) {
					return 103;
				}
				
				// Ensure file extension
				if(!filePath.endsWith(".json")) {
					filePath += ".json";
				}
				
				// Set fileIO path
				result = fileIO.setFile(filePath);
				// Force return to prevent execution of next function
				if(result != 0)
					return result;
			}
			
			// Write JSON to file
			result = fileIO.writeToFile(controller.getModel().convertToJSON());
		}
		else if(args[0].equals("load")) {
			// Expects args[1] to be the path to the file to load.
			// Make sure there is enough arguments
			if(args.length > 1) {
				// Grab file path
				String filePath = args[1];
				
				// Set the file for FileIO
				result = fileIO.setFile(filePath);
				if(result != 0)
					return result;
				
				// Make sure the file exists
				if(!fileIO.fileExists()) {
					return 105;
				}
				
				// Read the file in and pass it to the classManager for parsing.
				result = controller.getModel().parseJSON((String)fileIO.readFile()[0]);
			}
			else {
				return 102;
			}
		}
		
		
		//Split for list
		else if(args[0].equals("list")) {
			// Expects minimum of 2 args and maximum of 3 args.
			if(args.length == 2) {
				if(args[1].equals("classes")) {
					ArrayList<String[]> listBoxes = ((CommandController)controller).printClasses();
					if(listBoxes == null) {
						output.println("There are no classes to display.");
					}
					else {
						for(String[] s : listBoxes) {
							for(String t : s) {
								output.println(t);
							}
							output.println();
						}
					}
				}
				else if(args[1].equals("relationships")) {
					ArrayList<ArrayList<String[]>> rBoxes = ((CommandController)controller).printRelationships();
					if(rBoxes == null) {
						output.print("There are no relationships to display.");
					}
					else {
						for(ArrayList<String[]> a : rBoxes) {
							for(String[] s : a) {
								for(String r : s) {
									output.println(r);
								}
								output.println();
							}
						}
					}
				}
			} else if(args.length == 3) {
				//Expect arg[2] to be class name
				if(args[1].equals("classes")) {
					String[] box = ((CommandController)controller).printClasses(args[2]);
					if(box == null) {
						result = 109;
					}
					else {
						for(String s : box)
							output.println(s);
					}
				}
				else if(args[1].equals("relationships")) {
					ArrayList<String[]> rList = ((CommandController)controller).printRelationships(args[2]);
					if(rList == null) {
						result = 109;
					}
					else if(rList.isEmpty()) {
						output.println("This class has no relationships.");
					}
					else {
						for(String[] a : rList) {
							for(String s : a) {
								output.println(s);
							}
							output.println();
						}
					}
				}
			} else{
				return 102;
			}
		}
		//split for help
		else if(args[0].equals("help")) {
			//Expect maximum of 2 args
			if(args.length == 1) {
				printHelp(output);
				return 0;
			}
			else if(args.length == 2) {
				if (validCommands.containsKey(args[1]))
				{
					commandHelp(args[1], output);
					return 0;
				}
				else {
					output.println("Command does not exist, try \"help\" for a list of avaliable commands");
				}
			}
			return 102;
		}
		else {
			return 104;
		}
		
		return result;
	}
	
	private String getCommand() {
		// Preface to indicate waiting for input
		System.out.print("editor> ");
		// Get input from user
		String str = scanner.nextLine();
		// Return user-entered string
		return str;
	}
	
	private void commandHelp(String command, PrintStream output) {
		output.println();
		String[] desc = validCommands.get(command);
		for(String s : desc) {
			output.println(s);
			output.println();
		}
	}
	/**
	 * Fills validCommands with the command name and a description
	 * Format is as follows
	 * 		validCommands.put(name, list_of_description_lines);
	 * 		name = the name of the command
	 * 		list_description_lines = an array of strings where each item is a new line to print out as a description
	 */
	private void populateValidCommands() {
		validCommands.put("help", new String [] {"help: Prints out a list of valid commands with descriptions for each command.", "Typing help <command> describes a specific command."});
		validCommands.put("add", new String[] {"add: Can add class with:", "add <class_name>.","Also adds fields and methods to specified class in the form:", "add <field/method> <class_name> <type/return_type> <field/method_name> <parameter_list>(for methods).",
		"Add relationship in the form:", "add <relationship> <class_name1> <relationship_type> <class_name2>."});
		validCommands.put("edit", new String[ ]{"edit: Edit classes with:", "edit class <old_class_name> <new_class_name>.","Edit fields and methods with:", "edit <field/method> <source_class> <old_name> <new_name> <parameter_list>(for method).",
		"Edit Relationships with:", "edit relationship <class_name1> <old_type> <class_name2> <new_type>."});
		validCommands.put("remove", new String[] {"remove: Can remove class with:", "remove <class_name>.", "Also removes fields and methods from specified class in the form:", "remove <field/method> <class_name> <field/method_name> <parameter_list>(for methods).",
		"Remove relationships in the form:", "remove <relationship> <class_name1> <relationship_type> <class_name2>."});
		validCommands.put("exit", new String[] {"exit: Quit the program."});
		validCommands.put("quit", new String[] {"quit: Quit the program."});
		validCommands.put("save", new String[] {"save: Save the current state of the UML diagram.  If a file has not been set it will prompt the user."});
		validCommands.put("load", new String[] {"load <file_path>: Load the given file into the UML editor."});
		validCommands.put("list", new String[] {"list: Can list all classes with:", "list classes", "or specific class with:", "list classes <class_name>.", "These lists take the form of boxes with the class name and its associated attributes inside.",
		"List all relationships with:", "list relationships", "or", "list relationships <class_name>.", "Takes the form boxes for each class containing their associated attributes with a delimiter for relationship type between classes."});
	}
	
	/**
	 * Get the list of valid commands
	 * @return validCommands
	 */
	public LinkedHashMap<String, String[]> getValidCommands() {
		return validCommands;
	}
	
	/**
	 * Print out the a list of commands with descriptions
	 */
	private void printHelp(PrintStream output) {
		output.println();
		output.println("VALID COMMANDS");
		output.println("---------------------------------");
		
		// For every key in dictionary print corresponding description
		for(Map.Entry<String, String[]> entry : validCommands.entrySet()) {
			for(String s : entry.getValue()) {
				output.println(s);
				output.println();
			}
		}
	}

	@Override
	public void updated(Observable src, String tag, Object data) {}
	
	/**
	 * Get scanner instance
	 * @return - scanner
	 */
	public Scanner getScanner() {
		return scanner;
	}
}