/*
   The TextInput class 
   Written by Jeffrey Carroll (Aug 2004)

   The TextInput class is a (pedagogical) class created to simplify input for
   novice Java programmers. It expands on an earlier class (called Keyboard) 
   designed to simplify reading input from the keyboard by avoiding concerns with
   exceptions.  
   
   
   This newer class generalizes the previous class (described below) by allowing
   the student to read text from EITHER a keyboard device or from a text file.
   
   The text source is determined via the constructor used. The default constructor
   accesses keyboard input. If a string is provided to the constructor, the class creates
   an object that ATTEMPTS to read from a file associated with the given parameter.
   
   The boolean method, isValidSource, determines whether a file-based TextInput object
   can be used successfully (it always returns true when a TextInput object is 
   keyboard-based).  By the point that students have learned the basic loop patterns, they
   should ready to use this isValidSource (in conjuntion with the constructor) to ensure
   that they have a valid file-based object.
   

   In the upcoming Java version 1.5, there are supposed to be additional predefined
   classes to address these same kind of aims. However, at this writing, version 1.5 is
   not available on all platforms (in particular, the class platform).


	---------------------------------------------------------------------
	Version 1 (of Jeff Carroll's Keyboard class: Aug. 2002)

   		This class is my variation of the Keyboard class used in the 
		Lewis & Loftus text. My version simplified their formulation for 
		addressing input. In the L&L version, all keystrokes are buffered --
		this is reasonable for more experienced programmers but it tends to 
		confuse novice programmers. 

		Basically, this version of Keyboard assumes that there can only be ONE
		data item per line of input. When attempting to read a particular numeric 
		value of a primitive type, the corresponding method will only successfully read
		that line of input when the line exactly matches a permissable form of exactly ONE
		such value of the type. Otherwise, an input error is displayed on the screen
		and an extremal value (e.g., MIN_VALUE) is returned. 
		
		The class methods may be made less restrictive by pre-trimming the strings
		that are read. Although reasonable to do, having students realize the issue on
		their own is probably more valuable.
		
			This technique disallows a program tester from typing a real when an integer
			type is expected (this was not the case with L&L's buffereing). Novice programmers
			can more easily discover errors when they have greater assurance that all of 
			the data the program has successfully obtained matches with what was typed.	
		
		As the exception to the rule, when trying to read a character, the first 
		character is accepted but all remaining chars are ignored (this allows
		students to avoid having to distinquish between a 'Y' response and a "Yes" 
		response in the early stages of program writing).
			


		The similarity with L&L, is that the methods return extremal values instead of 
		throwing exceptions. Novice programmers are discouraged by having to address 
		exceptions from the outset. It presents an obstacle to their understanding basic
		concepts without providing any significant payoff when they have matured enough
		to properly understand exceptions.
		
		By the second half of the semester, students should have gained enough experience
		to understand exceptions. Further, they should find that continued use of this class
		is also limiting to how text input can be obtained (e.g., wanting to read two ints 
		on a single line of input).  However, there will be a "last assignment" when
		this class will be allowed to be used.

 	---------------------------------------------------------------------

	This class is not provided as part of a package. You will need to copy this java file to
	every project folder you use to write a program and you will have to update the .mcp file
	so that CodeWarrior is aware that this class is part of your source. On one hand, creating
	a package is a more global way to address this "defect" of Java but there are two 
	disadvantages to doing this. First, it is harder to correctly use a non-standard package.
	Secondly, once students have learned enough about Java, this particular class becomes
	a "crutch"; were this class part of a local package, programmers would develop a greater
	dependence upon this introductory tool.

     
*/

import java.io.*; // use the standard Java I/O package

public class TextInput {

	// instance variables
	private BufferedReader textReader; // the text input source
	private String fileName;  // the name of the source
	private boolean isValid;  // true iff the source is valid
	// by default, isValid is true whenever the source is the keyboard

	// Constructors

	public TextInput() {
	// creates a TextInput object that reads from the keyboard
		textReader = new BufferedReader(
				new InputStreamReader(System.in));
		fileName = "KeyBoard";
		isValid = true;		
	}

	public TextInput(String fileName) {
	// ATTEMPTS to create a TextInput object that reads from
    // a file with the specified name.
    // It is left to the user (via the method isValidSource) to
    // determine if the file was successfully created
		this.fileName = fileName;
		File f = new File (fileName);
		try {
			FileInputStream fis = new FileInputStream(f);
			textReader = new BufferedReader(new InputStreamReader(fis));
			isValid = true;
		} catch (IOException e) {
			isValid = false;
			System.out.println("TextInput object creation error:\n   the file \""
						+ fileName
						+ "\" does not exist or cannot be read from."
						+ "\n   Attempts to use this TextInput object" 
						+ " will not produce valid results.");
		}	
	}


	public boolean isValidSource () {
	// returns true iff 
	// the created object represents a valid text input source
		return isValid;
	}
	
	public boolean atEndOfFile () {
	// returns true iff
	// the created object is at the end of file (or the source is invalid
	
		if (!isValid)
			return true;
		try {
			return !textReader.ready();
		} catch (Exception e) {// io error
			return true;
		}
	}
	
	
	public String readLine() {
    // returns the string corresponding to the current line of input from 
    // the text input source.
	// If the input source is invalid, an error message is displayed
	// and a null string is returned.
	
		if (!isValid) {
			System.out.println("The file '"+fileName+"' cannot be read.");
			return null;
		}
		try {
			return textReader.readLine();
		} catch (Exception e) {
			if (!isValid) //owise, just at end of file
				System.out.println("ERROR discovered by TextInput>>" +
									 " The source text " + fileName
									 + "is invalid");

				return null;
			}
    	}

	public char readChar(){ 
	// Returns the first character from the line being input.
	// All characters after the first character are ignored.
	// If the input line is empty or if the source is invalid,
	// an error message is displayed and MIN_VALUE is returned
		try {
			return readLine().charAt(0);	
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A char was expected.");
			System.out.println("                                      the char'" + Character.MIN_VALUE 
								+ "' will be used");			
			return Character.MIN_VALUE;
		}
	}

	public double readDouble() {
	// returns the double corresponding to the line of text input
	// if the line doesn't correspond to a double, NaN is returned
	// if the input source is invalid, NaN is returned
		try {
			return Double.parseDouble(readLine());
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A double was expected.");
			System.out.println("                                      the double " + Double.NaN 
								+ " will be used");				
			return Double.NaN;
		}
	}	

	public int readInt() {
	// returns the int corresponding to the line of text input
	// if the line doesn't correspond to a int, MIN_VALUE is returned
	// if the input source is invalid, MIN_VALUE is returned
		try {
			return Integer.parseInt(readLine());
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> An int was expected.");
			System.out.println("                                      the int " + Integer.MIN_VALUE
								+ " will be used");				
			return Integer.MIN_VALUE;
		}
	}

	/* ********************************************************************* *
	
		Text reading methods for remaining (less-used) primitive types
		
			Comments are suppressed when they match with the previous more common types
 
    * ********************************************************************* */

	public long readLong () {
		try {
			return Long.parseLong(readLine());
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A long was expected.");
			System.out.println("                                      the long " + Long.MIN_VALUE
								+ " will be used");	
			return Long.MIN_VALUE;
		}
	}

	public short readShort () {
		try {
			return Short.parseShort(readLine());
		}  catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A short was expected.");
			System.out.println("                                      the short " + Short.MIN_VALUE
								+ " will be used");	
			return Short.MIN_VALUE;
		}
	}
	public byte readByte() { 
		try {
			return Byte.parseByte(readLine());
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A byte was expected.");
			System.out.println("                                      the byte " + Byte.MIN_VALUE
								+ " will be used");		
			return Byte.MIN_VALUE;
		}
	}
	
	public float readFloat() {
		try {
			return Float.parseFloat(readLine());
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A float was expected.");
			System.out.println("                                      the float " + Float.NaN 
								+ " will be used");		
			return Float.NaN;
		}
	}

	public boolean readBoolean() {
		try {
			return readLine().equalsIgnoreCase("true");		
		} catch (Exception e) {
			System.out.println("INPUT ERROR discovered by TextInput>> A boolean was expected.");
			System.out.println("                                      the boolean " + false 
								+ " will be used");			
					return false;
		}
	}

	/* ********************************************************************* *
	
		A main program to demonstrate the functionality of the classTextInput
		
			(this program also demonstrates that there can be more than
			one "main" class in a Java program)
    
     * ********************************************************************* */	

	public static void main (String[] args) {
	// a demonstration program of the basics
	// of the TextInput class
	
		System.out.println("This program demonstrates how the class TextInput works.");
		System.out.println();
		
		System.out.println("First demonstrating basic keyboard input:");
		System.out.println();
		
		//creating a keyboard input stream
		TextInput kb = new TextInput();
	
		
		/*******************************************************
			 STAGE 1: tests of reading basic int (& long) & double
		 *******************************************************/
		
		// reading an int
		System.out.print("Please enter an int ");
		int theIntYouTyped = kb.readInt();
		System.out.println("The int that you typed: " + theIntYouTyped);
		System.out.println();
		
		// reading an int as a double
		System.out.print("Please enter another int ");
		double theDoubleYouTyped = kb.readInt();
		System.out.println("The int that you typed: " + theDoubleYouTyped);
		System.out.println("Any integer is also a double");
		System.out.println();
		
		// reading a double
		System.out.print("Please enter a double this time (with a decimal point ");
		theDoubleYouTyped = kb.readDouble();
		System.out.println("The double that you typed: " + theDoubleYouTyped);
		System.out.println();
		
		// TRYING to read an int but when a double is typed
		System.out.print("Please enter a double again ");
		theIntYouTyped = kb.readInt();
		System.out.println("The program tried to read this as an int");
		System.out.println("The int received: " + theIntYouTyped);
		System.out.println("Whenever the typist uses a decimal point, you can't use an int");
		System.out.println();
			
		// TRYING to read an int but when a long is typed
		System.out.print("Please enter an int with exactly 12 digits ");
		theIntYouTyped = kb.readInt();
		System.out.println("The program tried to read this as an int");
		System.out.println("The int received: " + theIntYouTyped);
		System.out.println("There is a maximum int: " + Integer.MAX_VALUE);
		System.out.println("The number you typed couldn't be stored as an int");
		System.out.println("The 'long' type is used for bigger integers");
		System.out.println();
		
		// Reading a long
		System.out.print("Please enter an long with at least 18 digits ");
		long theLongYouTyped = kb.readLong();
		System.out.println("The long that you typed: " + theLongYouTyped);
		System.out.println("There is a maximum long though: " + Long.MAX_VALUE);
		System.out.println();


		// Reading a long as a double 
		System.out.print("Please the same long with at least 18 digits ");
		theDoubleYouTyped = kb.readLong();
		System.out.println("The long that you typed as a double: " + theDoubleYouTyped);
		System.out.println("Notice that, although doubles can hold big numbers, they also can lose accuracy");
		System.out.println();

		// TRYING to read two ints from one line
		System.out.print("Please enter two ints on this line: ");
		theIntYouTyped = kb.readInt();
		System.out.println("The program tried to read this as a single int");
		System.out.println("The int received: " + theIntYouTyped);
		System.out.println("You can only read one numeric item per line");
		System.out.println("Next, just hit Return");
		int theOtherInt = kb.readInt();
		System.out.println ("The program tried to read the second int you typed");
		System.out.println ("However, the second value is always ignored.");
		System.out.println("This was the second value: "+ theOtherInt);
		System.out.println();

		System.out.println("The same thing happens when you try to read more than one double on a line");
		System.out.println();
		
		// TRYING to read an int with white space preceding
		System.out.print("Please enter an int on this line (but precede it with a space): ");
		theIntYouTyped = kb.readInt();
		System.out.println("The program tried to read this ");
		System.out.println("The int received: " + theIntYouTyped);
		System.out.println();
		
		// TRYING to read an int with white space after		
		System.out.print("Please enter an int on this line (but follow it with a space): ");
		theIntYouTyped = kb.readInt();
		System.out.println("The program tried to read this ");
		System.out.println("The int received: " + theIntYouTyped);	
		System.out.println();
		
		/*******************************************************
			 STAGE 2: Char & String reading
		 *******************************************************/

		System.out.print("Please enter any string on this line: ");
		String theStringYouTyped = kb.readLine();
		System.out.println("The String received: \"" + theStringYouTyped+"\"");	
		System.out.println();
		System.out.print("Enter another string with interesting white space: ");
		theStringYouTyped = kb.readLine();
		System.out.println("The String received: \"" + theStringYouTyped+"\"");	
		System.out.println();
		System.out.print("Enter two ints on this line: ");
		theStringYouTyped = kb.readLine();
		System.out.println("The String received: \"" + theStringYouTyped+"\"");	
		System.out.println();

		System.out.print("Enter a single char: ");
		char theCharYouTyped = kb.readChar();
		System.out.println("The char received: '" + theCharYouTyped+"'");	
		System.out.println();

		// Reading a string as a char 
		System.out.print("Enter a string of at least two characters: ");
		theCharYouTyped = kb.readChar();
		System.out.println("The char received: '" + theCharYouTyped+"'");	
		System.out.println();
		System.out.print("Enter a string beginning with whitespace: ");
		theCharYouTyped = kb.readChar();
		System.out.println("The char received: '" + theCharYouTyped+"'");	
		System.out.println();
		
		// ATTEMPTING to read a char when none is entered
		System.out.print("Just hit <Enter> without typing any characters: ");
		theCharYouTyped = kb.readChar();
		System.out.println("The char received: '" + theCharYouTyped+"'");	
		System.out.println();

		/*******************************************************
			 STAGE 3: Reading from files
		 *******************************************************/

		// ATTEMPTING to create a source from a non-existing file
		System.out.print("Type the following filename exactly \"test1.txt\"" +
					" (without the quotes): " );
		String fileNameType = kb.readLine();
		System.out.println("Attempt to create the TextInput object for the file \"" + fileNameType +"\"");
		TextInput fileSource = new TextInput(fileNameType);
		System.out.println();
		System.out.println("You should have seen an error message printed.");
		// Demo of the method isValidSource
		System.out.println("The value returned by applying the isValidSource method " +
				"to this object is " + fileSource.isValidSource());
		System.out.println();

		// ATTEMPTING to read from a non-existing file
		System.out.println("Next, the program will try to read a string from the file.");
		String theStringFromFile=fileSource.readLine();
		System.out.println("You should have seen an error message printed.");
		System.out.println("The string received: \"" + theStringFromFile + "\"");
		System.out.println();
		System.out.println("Next, the program will try to read an int from the file.");
		int theIntFromFile=fileSource.readInt();
		System.out.println("You should have seen an error message printed.");
		System.out.println("The int received: \"" + theIntFromFile + "\"");
		System.out.println();
		System.out.println("The remaining cases for the other primitive types are similar");
		System.out.println();

		// Creating a source from an existing file
		System.out.println("The file \"test2.txt\" actually exists.");
		System.out.print("Type the abovefilename exactly (without the quotes): " );
		fileNameType = kb.readLine();
		System.out.println("Attempt to create the TextInput object for the file \""
					 + fileNameType +"\"");
		fileSource = new TextInput(fileNameType);
		System.out.println();
		// Demo of the method isValidSource
		System.out.println("The value returned by applying the isValidSource method " +
				"to this new object is " + fileSource.isValidSource());
		System.out.println();		

		System.out.println("The remaining lines show how the class treats the various lines:");
		String firstLine = fileSource.readLine();
		System.out.println("The first line was read as the string: \"" +
					firstLine + "\"");
		int secondLine = fileSource.readInt();
		System.out.println("The second line was read as the int: " + secondLine);
		double thirdLine = fileSource.readDouble();
		System.out.println("The third line was read as the double: " + thirdLine);
		char fourthLine = fileSource.readChar();
		System.out.println("The fourth line was read as the char: " + fourthLine);
		System.out.println("I'm attempting to read the last line as an int");
		int lastLine = fileSource.readInt();
		System.out.println("The value returned is " + lastLine);
	}	
}






