
/*
 * The FileIO class is an extension of TextIO that can read from Files as well
 * as from standard input and that can write to files as well as to standard
 * output.  Several routines are provided for selecting the input source and
 * the output destination.  By default, standard input and output are used; in 
 * this case, FileIO acts exactly like TextIO; in particular, input functions
 * do not produce an error because the user is prompted for new input when
 * an error occurs.  However, if an error occurs while reading from or
 * writing to a file, FileIO throws exception of type IllegalArgumentException.
 * This will abort the program (unless the exception is caught and handled).
 * 
 * This file is not meant as an example of good Java programming style -- it is
 * meant to provide some basic I/O facilities in a single class.
 * 
 * This file defines a class FileIO, which provides a simple interface to Java's
 * standard console input and output and well as to file input and output. Class 
 * FileIO defines several static methods for reading and writing values of various 
 * types.
 * 
 * To use this class in your program, simply include the compiled class file
 * FileIO.class in the same directory with the class file for your main program.
 * 
 * Written by: David Eck, Department of Mathematics and Computer Science, Hobart
 * and William Smith Colleges Geneva, NY 14456; Email: eck@hws.edu;
 * WWW: http://math.hws.edu/eck/
 *  
 */

import java.io.*;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;


public class FileIO {

	public static char EOF = (char)0xFFFF;  // The value returned by peek() when at end-of-file.
	public static char EOLN = '\n';          // The value returned by peek() when at end-of-line.
	
	// ********* Methods for changing input source and output destination **********
	// WARNING:  If you use readUserSelectedFile() or writeUserSelectedFile(), you will
	// have to call System.exit to terminate the program, since otherwise the GUI thread of
	// the program continues even after the main routine terminates.
	

	/**
	 * After this method is called, input will be read from standard input (as it 
	 * is in the default state).  If a file was previously open for input, that file
	 * is closed;
	 */
	public static void readStandardInput() {
		readFile(null);
	}
	

	/**
	 * Opens a file with a specified name for input.  If the file name is null, this has
	 * the same effect as calling readStandardInput(); that is, input will be read from standard
	 * input.  If another file was opened previously, that file is first closed.  If an
	 * error occurs while trying to open the file, an exception of type IllegalArgumentException
	 * is thrown.  If the file is opened successfully, then after this method is called,
	 * all of the input routines will read from the file, instead of from standard input.
	 */
	public static void readFile(String fileName) {
		if (inputFileName != null) { // close current file
			try {
				in.close();
			}
			catch (Exception e) {
			}
			inputFileName = null;
		}
		if (fileName == null) { // Go back to reading standard input
			in = standardInput;
		}
		else {
			try {
				in = new BufferedReader( new FileReader(fileName) );
				inputFileName = fileName;
			}
			catch (Exception e) {
				throw new IllegalArgumentException("Can't open file \"" + fileName + "\" for input.\n"
						         + "(Error :" + e + ")");
			}
		}
	}
	

	/**
	 * Puts a GUI file-selection dialog box on the screen in which the user can select
	 * an input file.  If the user cancels the dialog instead of selecting a file, it is
	 * not considered an error, but the return value of the subroutine is false.
	 * If the user does select a file, but there is an error while trying to open the
	 * file, then an exception of type IllegalArgumentException is thrown.  Finally, if
	 * the user selects a file and it is successfully opened, then the return value of the
	 * subroutine is true, and  the input routines will read from the file, instead of 
	 * from standard input.
	 */
	public static boolean readUserSelectedFile() {
		if (inputFileName != null) { // close current file
			try {
				in.close();
			}
			catch (Exception e) {
			}
			inputFileName = null;
		}
		if (fileDialog == null)
			fileDialog = new JFileChooser();
		fileDialog.setDialogTitle("Select File for Input");
		int option = fileDialog.showOpenDialog(null);
		if (option != JFileChooser.APPROVE_OPTION)
			return false;
		File selectedFile = fileDialog.getSelectedFile();
		try {
			in = new BufferedReader( new FileReader(selectedFile) );
			inputFileName = selectedFile.getName();
			return true;
		}
		catch (Exception e) {
			throw new IllegalArgumentException("Can't open file \"" + selectedFile.getName() + "\" for input.\n"
					         + "(Error :" + e + ")");
		}
	}
	
	/**
	 * After this method is called, output will be written to standard output (as it 
	 * is in the default state).  If a file was previously open for output, that file
	 * is closed;
	 */
	public static void writeStandardOutput() {
		writeFile(null);
	}
	

	/**
	 * Opens a file with a specified name for output.  If the file name is null, this has
	 * the same effect as calling writeStandardOutput(); that is, output will be sent to standard
	 * output.  If another file was opened previously, that file is first closed.  If an
	 * error occurs while trying to open the file, an exception of type IllegalArgumentException
	 * is thrown.  If the file is opened successfully, then after this method is called,
	 * all of the output routines will write to the file, instead of to  standard output.
	 */
	public static void writeFile(String fileName) {
		if (outputFileName != null) { // close old file
			try {
				out.close();
			}
			catch (Exception e) {
			}
			outputFileName = null;
		}
		if (fileName == null) { // Go back to reading standard output
			out = standardOutput;
		}
		else {
			try {
				out = new PrintWriter(new FileWriter(fileName));
				outputFileName = fileName;
			}
			catch (Exception e) {
				throw new IllegalArgumentException("Can't open file \"" + fileName + "\" for output.\n"
						         + "(Error :" + e + ")");
			}
		}
	}
	
	/**
	 * Puts a GUI file-selection dialog box on the screen in which the user can select
	 * an output file.  If the user cancels the dialog instead of selecting a file, it is
	 * not considered an error, but the return value of the subroutine is false.
	 * If the user does select a file, but there is an error while trying to open the
	 * file, then an exception of type IllegalArgumentException is thrown.  Finally, if
	 * the user selects a file and it is successfully opened, then the return value of the
	 * subroutine is true, and  the output routines will write to the file, instead of 
	 * to standard output.
	 */
	public static boolean writeUserSelectedFile() {
		if (outputFileName != null) { // close old file
			try {
				out.close();
			}
			catch (Exception e) {
			}
			outputFileName = null;
		}
		if (fileDialog == null)
			fileDialog = new JFileChooser();
		fileDialog.setDialogTitle("Select File for Output");
		File selectedFile;;
		while (true) {
			int option = fileDialog.showSaveDialog(null);
			if (option != JFileChooser.APPROVE_OPTION)
				return false;  // user canceled
			selectedFile = fileDialog.getSelectedFile();
			if (selectedFile.exists()) {
				int response = JOptionPane.showConfirmDialog(null,
						"The file \"" + selectedFile.getName() + "\" already exists.  Do you want to replace it?",
						"Replace existing file?",
						JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
				if (response == JOptionPane.YES_OPTION)
					break;
			}
			else {
				break;
			}
		}
		try {
			out = new PrintWriter(new FileWriter(selectedFile));
			outputFileName = selectedFile.getName();
			return true;
		}
		catch (Exception e) {
			throw new IllegalArgumentException("Can't open file \"" + selectedFile.getName() + "\" for output.\n"
					         + "(Error :" + e + ")");
		}
	}
	

	/**
	 * If this class is currently reading from a file rather than from standard
	 * input, then the return value is the name of the file.  If the class is reading
	 * from standard input, then the return value is null.
	 * @return
	 */
	public static String getInputFileName() {
		return inputFileName;
	}
	

	/**
	 * If this class is currently writing to a file rather than to standard
	 * ouput, then the return value is the name of the file.  If the class is writing
	 * to standard output, then the return value is null.
	 */
	public static String getOutputFileName() {
		return outputFileName;
	}
	

	// *************************** Output Methods *********************************
	
	// Methods for writing the primitive types, plus type String and a general Object type,
	// to the current output destination, with no extra spaces.  Output goes to the
	// currently selected output destination, which is standard output
	// by default.
	//
	// Note that the real-number data types, float
	// and double, a rounded version is output that will
	// use at most 10 or 11 characters.  If you want to
	// output a real number with full accuracy, use
	// "FileIO.put(String.valueOf(x))", for example.
	
	public static void put(int x)     { put(x,0); }   // Note: also handles byte and short!
	public static void put(long x)    { put(x,0); }
	public static void put(double x)  { put(x,0); }   // Also handles float.
	public static void put(char x)    { put(x,0); }
	public static void put(boolean x) { put(x,0); }
	public static void put(String x)   { put(x,0); }
	public static void put(Object x)   { put(x,0); }
	
	
	// Methods for writing the primitive types, plus type String and a general Object type,
	// to the current output destination,followed by a carriage return, with
	// no extra spaces.
	
	public static void putln(int x)      { put(x,0); newLine(); }  // Note: also handles byte and short!
	public static void putln(long x)     { put(x,0); newLine(); }
	public static void putln(double x)   { put(x,0); newLine(); }  // Also handles float.
	public static void putln(char x)     { put(x,0); newLine(); }
	public static void putln(boolean x)  { put(x,0); newLine(); }
	public static void putln(String x)   { put(x,0); newLine(); }
	public static void putln(Object x)   { put(x,0); newLine(); }
	
	
	// Methods for writing the primitive types, plus type String and a general Object type,
	// to the current output destination, with a minimum field width of w,
	// and followed by a carriage  return.
	// If output value is less than w characters, it is padded
	// with extra spaces in front of the value.
	
	public static void putln(int x, int w)     { put(x,w); newLine(); }   // Note: also handles byte and short!
	public static void putln(long x, int w)    { put(x,w); newLine(); }
	public static void putln(double x, int w)  { put(x,w); newLine(); }   // Also handles float.
	public static void putln(char x, int w)    { put(x,w); newLine(); }
	public static void putln(boolean x, int w) { put(x,w); newLine(); }
	public static void putln(String x, int w)  { put(x,w); newLine(); }
	public static void putln(Object x, int w)  { put(x,w); newLine(); }
	
	
	// Method for outputting a carriage return to the current output destination.
	
	public static void putln() { newLine(); }
	
	
	// Methods for writing the primitive types, plus type String and a general Object type,
	// to the current output destination, with minimum field width w.
	
	public static void put(int x, int w)     { dumpString(String.valueOf(x), w); }   // Note: also handles byte and short!
	public static void put(long x, int w)    { dumpString(String.valueOf(x), w); }
	public static void put(double x, int w)  { dumpString(realToString(x), w); }     // Also handles float.
	public static void put(char x, int w)    { dumpString(String.valueOf(x), w); }
	public static void put(boolean x, int w) { dumpString(String.valueOf(x), w); }
	public static void put(String x, int w)  { dumpString(x, w); }
	public static void put(Object x, int w)  { dumpString( x == null? "null" : x.toString(), w); }
	
	
	// *************************** Input Methods *********************************

	// Methods for checking for end-of-line and for end-of-file.  Calling FileIO.eoln() is
	// the same as checking whether FileIO.peek() is '\n' (or FileIO.EOLN).  Calling FileIO.eof(
	// is the same as checking whether FileIO.peek() is FileIO.EOF.
	
	public static boolean eoln() { return peek() == '\n'; }
	public static boolean eof()  { return peek() == EOF; }
	

	// Methods for skipping over white space in the input.  skipBlanks() will skip any
	// spaces and tabs in the input, stopping at end-of-line or a character that is not a
	// space or a tab.  skipWhiteSpace() will skip all white space characters, including end-of-line,
	// stopping at end-of-file or at a non-whitespace character.1
	
	public static void skipBlanks() { while (peek() == ' ' || peek() == '\t') getAnyChar(); }
	public static void skipWhitespace() { while (Character.isWhitespace(peek())) getAnyChar(); }


	// Methods for reading in the primitive types, plus "words" and "lines".
	//    Data is read from the currently selected input source.
	// The "getln..." methods discard any extra input, up to and including
	//    the next carriage return.
	// A "word" read by getlnWord() is any sequence of non-blank characters.
	// A "line" read by getlnString() or getln() is everything up to next CR;
	//    the carriage return is not part of the returned value, but it is
	//    read and discarded.
	// Note that all input methods except getAnyChar(), peek(), the ones for lines
	//    skip past any blanks and carriage returns to find a non-blank value.
	// getln() can return an empty string; getChar() and getlnChar() can 
	//    return a space or a linefeed ('\n') character.
	// peek() allows you to look at the next character in input, without
	//    removing it from the input stream.  (Note that using this
	//    routine might force the user to enter a line, in order to
	//    check what the next character is.)
	// Acceptable boolean values are the "words": true, false, t, f, yes,
	//    no, y, n, 0, or 1;  uppercase letters are OK.
	// None of these can produce an error when the input source is standard input; 
	//    if an error is found in standard input the user is forced to re-enter.
	//    When reading from another input source and an error is found in the
	//    input, an exception of type IllegalArgumentException is thrown.
	// Available input routines are:
	//
	//            getByte()      getlnByte()    getShort()     getlnShort()
	//            getInt()       getlnInt()     getLong()      getlnLong()
	//            getFloat()     getlnFloat()   getDouble()    getlnDouble()
	//            getChar()      getlnChar()    peek()         getAnyChar()
	//            getWord()      getlnWord()    getln()        getString()    getlnString()
	//
	// (getlnString is the same as getln and is only provided for consistency.)
	
	public static byte getlnByte()       { byte x=getByte();       emptyBuffer();  return x; }
	public static short getlnShort()     { short x=getShort();     emptyBuffer();  return x; }
	public static int getlnInt()         { int x=getInt();         emptyBuffer();  return x; }
	public static long getlnLong()       { long x=getLong();       emptyBuffer();  return x; }
	public static float getlnFloat()     { float x=getFloat();     emptyBuffer();  return x; }
	public static double getlnDouble()   { double x=getDouble();   emptyBuffer();  return x; }
	public static char getlnChar()       { char x=getChar();       emptyBuffer();  return x; }
	public static boolean getlnBoolean() { boolean x=getBoolean(); emptyBuffer();  return x; }
	public static String getlnWord()     { String x=getWord();     emptyBuffer();  return x; }
	public static String getlnString()   { return getln(); }  // same as getln()
	public static String getln() {
		StringBuffer s = new StringBuffer(100);
		char ch = readChar();
		while (ch != '\n') {
			s.append(ch);
			ch = readChar();
		}
		return s.toString();
	}
	
	
	public static byte getByte()   { return (byte)readInteger(-128L,127L); }
	public static short getShort() { return (short)readInteger(-32768L,32767L); }   
	public static int getInt()     { return (int)readInteger((long)Integer.MIN_VALUE, (long)Integer.MAX_VALUE); }
	public static long getLong()   { return readInteger(Long.MIN_VALUE, Long.MAX_VALUE); }
	
	public static char getAnyChar(){ return readChar(); }
	public static char peek()      { return lookChar(); }
	
	public static char getChar() {  // skip spaces & cr's, then return next char
		char ch = lookChar();
		while (ch == ' ' || ch == '\n') {
			readChar();
			if (ch == '\n' && inputFileName == null)
				dumpString("? ",0);
			ch = lookChar();
		}
		return readChar();
	}
	
	public static float getFloat() {
		float x = 0.0F;
		while (true) {
			String str = readRealString();
			if (str.equals("")) {
				errorMessage("Illegal floating point input.",
						"Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
			}
			else {
				Float f = null;
				try { f = Float.valueOf(str); }
				catch (NumberFormatException e) {
					errorMessage("Illegal floating point input.",
							"Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
					continue;
				}
				if (f.isInfinite()) {
					errorMessage("Floating point input outside of legal range.",
							"Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
					continue;
				}
				x = f.floatValue();
				break;
			}
		}
		return x;
	}
	
	public static double getDouble() {
		double x = 0.0;
		while (true) {
			String str = readRealString();
			if (str.equals("")) {
				errorMessage("Illegal floating point input",
						"Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
			}
			else {
				Double f = null;
				try { f = Double.valueOf(str); }
				catch (NumberFormatException e) {
					errorMessage("Illegal floating point input",
							"Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
					continue;
				}
				if (f.isInfinite()) {
					errorMessage("Floating point input outside of legal range.",
							"Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
					continue;
				}
				x = f.doubleValue();
				break;
			}
		}
		return x;
	}
	
	public static String getWord() {
		char ch = lookChar();
		while (ch == ' ' || ch == '\n') {
			readChar();
			if (ch == '\n' && inputFileName == null)
				dumpString("? ",0);
			ch = lookChar();
		}
		StringBuffer str = new StringBuffer(50);
		while (ch != ' ' && ch != '\n') {
			str.append(readChar());
			ch = lookChar();
		}
		return str.toString();
	}
	
	public static boolean getBoolean() {
		boolean ans = false;
		while (true) {
			String s = getWord();
			if ( s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t") ||
					s.equalsIgnoreCase("yes")  || s.equalsIgnoreCase("y") ||
					s.equals("1") ) {
				ans = true;
				break;
			}
			else if ( s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f") ||
					s.equalsIgnoreCase("no")  || s.equalsIgnoreCase("n") ||
					s.equals("0") ) {
				ans = false;
				break;
			}
			else
				errorMessage("Illegal boolean input value.",
				"one of:  true, false, t, f, yes, no, y, n, 0, or 1");
		}
		return ans;
	}
	
	// ***************** Everything beyond this point is private *******************
	
	// ********************** Utility routines for input/output ********************
	
	private static String inputFileName;
	private static String outputFileName;
	
	private static JFileChooser fileDialog;
	
	private static BufferedReader standardInput = new BufferedReader(new InputStreamReader(System.in));    // rename standard input stream
	private static PrintWriter standardOutput = new PrintWriter(System.out);  // rename standard output stream

	private static BufferedReader in = standardInput;  // Stream that data is read from
	private static PrintWriter out = standardOutput;   // Stream that data is written to
	
	private static String buffer = null;  // one line read from input
	private static int pos = 0;           // position of next char in input line that has
	//      not yet been processed
	
	
	private static String readRealString() {   // read chars from input following syntax of real numbers
		StringBuffer s=new StringBuffer(50);
		char ch=lookChar();
		while (ch == ' ' || ch == '\n') {
			readChar();
			if (ch == '\n' && inputFileName == null)
				dumpString("? ",0);
			ch = lookChar();
		}
		if (ch == '-' || ch == '+') {
			s.append(readChar());
			ch = lookChar();
			while (ch == ' ') {
				readChar();
				ch = lookChar();
			}
		}
		while (ch >= '0' && ch <= '9') {
			s.append(readChar());
			ch = lookChar();
		}
		if (ch == '.') {
			s.append(readChar());
			ch = lookChar();
			while (ch >= '0' && ch <= '9') {
				s.append(readChar());
				ch = lookChar();
			}
		}
		if (ch == 'E' || ch == 'e') {
			s.append(readChar());
			ch = lookChar();
			if (ch == '-' || ch == '+') {
				s.append(readChar());
				ch = lookChar();
			}
			while (ch >= '0' && ch <= '9') {
				s.append(readChar());
				ch = lookChar();
			}
		}
		return s.toString();
	}
	
	private static long readInteger(long min, long max) {  // read long integer, limited to specified range
		long x=0;
		while (true) {
			StringBuffer s=new StringBuffer(34);
			char ch=lookChar();
			while (ch == ' ' || ch == '\n') {
				readChar();
				if (ch == '\n' && inputFileName == null)
					dumpString("? ",0);
				ch = lookChar();
			}
			if (ch == '-' || ch == '+') {
				s.append(readChar());
				ch = lookChar();
				while (ch == ' ') {
					readChar();
					ch = lookChar();
				}
			}
			while (ch >= '0' && ch <= '9') {
				s.append(readChar());
				ch = lookChar();
			}
			if (s.equals("")){
				errorMessage("Illegal integer input.",
						"Integer in the range " + min + " to " + max);
			}
			else {
				String str = s.toString();
				try { 
					x = Long.parseLong(str);
				}
				catch (NumberFormatException e) {
					errorMessage("Illegal integer input.",
							"Integer in the range " + min + " to " + max);
					continue;
				}
				if (x < min || x > max) {
					errorMessage("Integer input outside of legal range.",
							"Integer in the range " + min + " to " + max);
					continue;
				}
				break;
			}
		}
		return x;
	}
	
	private static String realToString(double x) {
		// Goal is to get a reasonable representation of x in at most
		// 10 characters, or 11 characters if x is negative.
		if (Double.isNaN(x))
			return "undefined";
		if (Double.isInfinite(x))
			if (x < 0)
				return "-INF";
			else
				return "INF";
		if (Math.abs(x) <= 5000000000.0 && Math.rint(x) == x)
			return String.valueOf( (long)x );
		String s = String.valueOf(x);
		if (s.length() <= 10)
			return s;
		boolean neg = false;
		if (x < 0) {
			neg = true;
			x = -x;
			s = String.valueOf(x);
		}
		if (x >= 0.00005 && x <= 50000000 && (s.indexOf('E') == -1 && s.indexOf('e') == -1)) {  // trim x to 10 chars max
			s = round(s,10);
			s = trimZeros(s);
		}
		else if (x > 1) { // construct exponential form with positive exponent
			long power = (long)Math.floor(Math.log(x)/Math.log(10));
			String exp = "E" + power;
			int numlength = 10 - exp.length();
			x = x / Math.pow(10,power);
			s = String.valueOf(x);
			s = round(s,numlength);
			s = trimZeros(s);
			s += exp;
		}
		else { // constuct exponential form
			long power = (long)Math.ceil(-Math.log(x)/Math.log(10));
			String exp = "E-" + power;
			int numlength = 10 - exp.length();
			x = x * Math.pow(10,power);
			s = String.valueOf(x);
			s = round(s,numlength);
			s = trimZeros(s);
			s += exp;
		}
		if (neg)
			return "-" + s;
		else
			return s;
	}
	
	private static String trimZeros(String num) {  // used by realToString
		if (num.indexOf('.') >= 0 && num.charAt(num.length() - 1) == '0') {
			int i = num.length() - 1;
			while (num.charAt(i) == '0')
				i--;
			if (num.charAt(i) == '.')
				num = num.substring(0,i);
			else
				num = num.substring(0,i+1);
		}
		return num;
	}
	
	private static String round(String num, int length) {  // used by realToString
		if (num.indexOf('.') < 0)
			return num;
		if (num.length() <= length)
			return num;
		if (num.charAt(length) >= '5' && num.charAt(length) != '.') {
			char[] temp = new char[length+1];
			int ct = length;
			boolean rounding = true;
			for (int i = length-1; i >= 0; i--) {
				temp[ct] = num.charAt(i); 
				if (rounding && temp[ct] != '.') {
					if (temp[ct] < '9') {
						temp[ct]++;
						rounding = false;
					}
					else
						temp[ct] = '0';
				}
				ct--;
			}
			if (rounding) {
				temp[ct] = '1';
				ct--;
			}
			// ct is -1 or 0
			return new String(temp,ct+1,length-ct);
		}
		else 
			return num.substring(0,length);
		
	}
	private static void dumpString(String str, int w) {   // output string to current output destination
		for (int i=str.length(); i<w; i++)
			out.print(' ');
		for (int i=0; i<str.length(); i++) {
			if ((int)str.charAt(i) >= 0x20 && (int)str.charAt(i) != 0x7F)  // no control chars or delete
				out.print(str.charAt(i));
			else if (str.charAt(i) == '\n' || str.charAt(i) == '\r')
				newLine();
		}
		if (out.checkError()) {
			if (outputFileName == null)	
				throw new IllegalArgumentException("Error occurred while writing to standard output???");
			else
				throw new IllegalArgumentException("Error occurred while trying to write to file \"" + outputFileName + "\".");
		}
	}
	
	private static void errorMessage(String message, String expecting) {
		if (inputFileName == null) {
			// inform user of error and force user to re-enter.
			newLine();
			dumpString("  *** Error in input: " + message + "\n", 0);
			dumpString("  *** Expecting: " + expecting + "\n", 0);
			dumpString("  *** Discarding Input: ", 0);
			if (lookChar() == '\n')
				dumpString("(end-of-line)\n\n",0);
			else {
				while (lookChar() != '\n')
					out.print(readChar());
				dumpString("\n\n",0);
			}
			dumpString("Please re-enter: ", 0);
			readChar();  // discard the end-of-line character
		}
		else
			throw new IllegalArgumentException("Error while reading from file \"" + inputFileName + "\":\n" + message);
	}
	
	private static char lookChar() {  // return next character from input
		if (buffer == null || pos > buffer.length())
			fillBuffer();
		if (buffer == null)
			return EOF;
		else if (pos == buffer.length())
			return '\n';
		else 
			return buffer.charAt(pos);
	}
	
	private static char readChar() {  // return and discard next character from input
		char ch = lookChar();
		if (buffer == null) {
			if (inputFileName == null)
				throw new IllegalArgumentException("Attempt to read past end-of-file in standard input???");
			else
				throw new IllegalArgumentException("Attempt to read past end-of-file in file \"" + inputFileName + "\".");
		}
		pos++;
		return ch;
	}
	
	private static void newLine() {   // output a CR to current output destination
		out.println();
		out.flush();
	}
	
	private static void fillBuffer() {    // Wait for user to type a line and press return,
		try {
			buffer = in.readLine();
		}
		catch (Exception e) {
			if (inputFileName == null)
				throw new IllegalArgumentException("Error while reading standard input???");
			else
				throw new IllegalArgumentException("Error while attempting to read from file \"" + inputFileName + "\".");
		}
		pos = 0;
	}
	
	private static void emptyBuffer() {   // discard the rest of the current line of input
		buffer = null;
	}
	
	
} // end of class TextIO
