content: package mars.util;
  
  import java.io.BufferedReader;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  
  import mars.Globals;
  import mars.Settings;
  
  /*
   * Copyright (c) 2003-2013, Pete Sanderson and Kenneth Vollmar
   *
   * Developed by Pete Sanderson (psanderson@otterbein.edu) and Kenneth Vollmar
   * (kenvollmar@missouristate.edu)
   *
   * 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.
   *
   * (MIT license, http://www.opensource.org/licenses/mit-license.html)
   */
  
  /**
   * Provides standard i/o services needed to simulate the MIPS syscall routines.
   * These methods will detect whether the simulator is being run from the command
   * line or through the GUI, then do I/O to System.in and System.out in the
   * former situation, and interact with the GUI in the latter.
   *
   * @author Pete Sanderson and Ken Vollmar
   * @version August 2003-2005
   */
  public class SystemIO {
  
  	/** Buffer size for syscalls for file I/O */
  	public static final int SYSCALL_BUFSIZE = 128;
  	/** Maximum number of files that can be open */
  	public static final int SYSCALL_MAXFILES = 32;
  	/** String used for description of file error */
  	public static String fileErrorString = new String("File operation OK");
  
  	private static final int O_RDONLY = 0x00000000;
  	private static final int O_WRONLY = 0x00000001;
  	private static final int O_RDWR = 0x00000002;
  	private static final int O_APPEND = 0x00000008;
  	private static final int O_CREAT = 0x00000200; // 512
  	private static final int O_TRUNC = 0x00000400; // 1024
  	private static final int O_EXCL = 0x00000800; // 2048
  
  	// standard I/O channels
  	private static final int STDIN = 0;
  	private static final int STDOUT = 1;
  	private static final int STDERR = 2;
  
  	// Will use one buffered reader for all keyboard/redirected/piped input.
  	// Added by DPS 28 Feb 2008.  See getInputReader() below.
  	private static BufferedReader inputReader = null;
  
  	/**
  	 * Implements syscall to read an integer value. Client is responsible for
  	 * catching NumberFormatException.
  	 *
  	 * @param serviceNumber the number assigned to Read Int syscall (default 5)
  	 * @return int value corresponding to user input
  	 */
  
  	public static int readInteger(final int serviceNumber) {
  		String input = "0";
  		if (Globals.getGui() == null) {
  			try {
  				input = getInputReader().readLine();
  			} catch (final IOException e) {}
  		} else {
  			if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
  				input = Globals.getGui().getMessagesPane().getInputString("Enter an integer value (syscall "
  						+ serviceNumber + ")");
  			} else {
  				input = Globals.getGui().getMessagesPane().getInputString(-1);
  			}
  		}
  
  		// Client is responsible for catching NumberFormatException
  		return new Integer(input.trim());
  	}
  
  	/**
  	 * Implements syscall to read a float value. Client is responsible for catching
  	 * NumberFormatException.
  	 *
  	 * @param serviceNumber the number assigned to Read Float syscall (default 6)
  	 * @return float value corresponding to user input Feb 14 2005 Ken Vollmar
  	 */
  	public static float readFloat(final int serviceNumber) {
  		String input = "0";
  		if (Globals.getGui() == null) {
  			try {
  				input = getInputReader().readLine();
  			} catch (final IOException e) {}
  		} else {
  			if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
  				input = Globals.getGui().getMessagesPane().getInputString("Enter a float value (syscall "
  						+ serviceNumber + ")");
  			} else {
  				input = Globals.getGui().getMessagesPane().getInputString(-1);
  			}
  		}
  		return new Float(input.trim());
  
  	}
  
  	/**
  	 * Implements syscall to read a double value. Client is responsible for catching
  	 * NumberFormatException.
  	 *
  	 * @param serviceNumber the number assigned to Read Double syscall (default 7)
  	 * @return double value corresponding to user input 1 Aug 2005 DPS, based on Ken
  	 *         Vollmar's readFloat
  	 */
  	public static double readDouble(final int serviceNumber) {
  		String input = "0";
  		if (Globals.getGui() == null) {
  			try {
  				input = getInputReader().readLine();
  			} catch (final IOException e) {}
  		} else {
  			if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
  				input = Globals.getGui().getMessagesPane().getInputString("Enter a double value (syscall "
  						+ serviceNumber + ")");
  			} else {
  				input = Globals.getGui().getMessagesPane().getInputString(-1);
  			}
  		}
  		return new Double(input.trim());
  
  	}
  
  	/**
  	 * Implements syscall having 4 in $v0, to print a string.
  	 */
  	public static void printString(final String string) {
  		if (Globals.getGui() == null) {
  			System.out.print(string);
  		} else {
  			Globals.getGui().getMessagesPane().postRunMessage(string);
  		}
  
  	}
  
  	/**
  	 * Implements syscall to read a string.
  	 *
  	 * @param serviceNumber the number assigned to Read String syscall (default 8)
  	 * @param maxLength     the maximum string length
  	 * @return the entered string, truncated to maximum length if necessary
  	 */
  	public static String readString(final int serviceNumber, final int maxLength) {
  		String input = "";
  		if (Globals.getGui() == null) {
  			try {
  				input = getInputReader().readLine();
  			} catch (final IOException e) {}
  		} else {
  			if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
  				input = Globals.getGui().getMessagesPane().getInputString("Enter a string of maximum length "
  						+ maxLength + " (syscall " + serviceNumber + ")");
  			} else {
  				input = Globals.getGui().getMessagesPane().getInputString(maxLength);
  				if (input.endsWith("\n")) { input = input.substring(0, input.length() - 1); }
  			}
  		}
  
  		if (input.length() > maxLength) {
  			// Modified DPS 13-July-2011.  Originally: return input.substring(0, maxLength);
  			return maxLength <= 0 ? "" : input.substring(0, maxLength);
  		} else {
  			return input;
  		}
  	}
  
  	/**
  	 * Implements syscall having 12 in $v0, to read a char value.
  	 *
  	 * @param serviceNumber the number assigned to Read Char syscall (default 12)
  	 * @return int value with lowest byte corresponding to user input
  	 */
  	public static int readChar(final int serviceNumber) {
  		String input = "0";
  		int returnValue = 0;
  		if (Globals.getGui() == null) {
  			try {
  				input = getInputReader().readLine();
  			} catch (final IOException e) {}
  		} else {
  			if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
  				input = Globals.getGui().getMessagesPane().getInputString("Enter a character value (syscall "
  						+ serviceNumber + ")");
  			} else {
  				input = Globals.getGui().getMessagesPane().getInputString(1);
  			}
  		}
  		// The whole try-catch is not really necessary in this case since I'm
  		// just propagating the runtime exception (the default behavior), but
  		// I want to make it explicit.  The client needs to catch it.
  		try {
  			returnValue = input.charAt(0); // first character input
  		} catch (final IndexOutOfBoundsException e) // no chars present
  		{
  			throw e;  // was: returnValue = 0;
  		}
  
  		return returnValue;
  
  	}
  
  	/**
  	 * Write bytes to file.
  	 *
  	 * @param fd              file descriptor
  	 * @param myBuffer        byte array containing characters to write
  	 * @param lengthRequested number of bytes to write
  	 * @return number of bytes written, or -1 on error
  	 */
  
  	public static int writeToFile(final int fd, final byte[] myBuffer, final int lengthRequested) {
  		/////////////// DPS 8-Jan-2013  ////////////////////////////////////////////////////
  		/// Write to STDOUT or STDERR file descriptor while using IDE - write to Messages pane.
  		if ((fd == STDOUT || fd == STDERR) && Globals.getGui() != null) {
  			final String data = new String(myBuffer);
  			Globals.getGui().getMessagesPane().postRunMessage(data);
  			return data.length();
  		}
  		///////////////////////////////////////////////////////////////////////////////////
  		//// When running in command mode, code below works for either regular file or STDOUT/STDERR
  
  		if (!FileIOData.fdInUse(fd, 1)) // Check the existence of the "write" fd
  		{
  			fileErrorString = new String("File descriptor " + fd + " is not open for writing");
  			return -1;
  		}
  		// retrieve FileOutputStream from storage
  		final OutputStream outputStream = (OutputStream) FileIOData.getStreamInUse(fd);
  		try {
  			// Oct. 9 2005 Ken Vollmar
  			// Observation: made a call to outputStream.write(myBuffer, 0, lengthRequested)
  			//     with myBuffer containing 6(ten) 32-bit-words <---> 24(ten) bytes, where the
  			//     words are MIPS integers with values such that many of the bytes are ZEROES.
  			//     The effect is apparently that the write stops after encountering a zero-valued
  			//     byte. (The method write does not return a value and so this can't be verified
  			//     by the return value.)
  			// Writes up to lengthRequested bytes of data to this output stream from an array of bytes.
  			// outputStream.write(myBuffer, 0, lengthRequested); // write is a void method -- no verification value returned
  
  			// Oct. 9 2005 Ken Vollmar  Force the write statement to write exactly
  			// the number of bytes requested, even though those bytes include many ZERO values.
  			for (int ii = 0; ii < lengthRequested; ii++) {
  				outputStream.write(myBuffer[ii]);
  			}
  			outputStream.flush();// DPS 7-Jan-2013
  		} catch (final IOException e) {
  			fileErrorString = new String("IO Exception on write of file with fd " + fd);
  			return -1;
  		} catch (final IndexOutOfBoundsException e) {
  			fileErrorString = new String("IndexOutOfBoundsException on write of file with fd" + fd);
  			return -1;
  		}
  
  		return lengthRequested;
  
  	} // end writeToFile
  
  	/**
  	 * Read bytes from file.
  	 *
  	 * @param fd              file descriptor
  	 * @param myBuffer        byte array to contain bytes read
  	 * @param lengthRequested number of bytes to read
  	 * @return number of bytes read, 0 on EOF, or -1 on error
  	 */
  	public static int readFromFile(final int fd, final byte[] myBuffer, final int lengthRequested) {
  		int retValue = -1;
  		/////////////// DPS 8-Jan-2013  //////////////////////////////////////////////////
  		/// Read from STDIN file descriptor while using IDE - get input from Messages pane.
  		if (fd == STDIN && Globals.getGui() != null) {
  			final String input = Globals.getGui().getMessagesPane().getInputString(lengthRequested);
  			final byte[] bytesRead = input.getBytes();
  			for (int i = 0; i < myBuffer.length; i++) {
  				myBuffer[i] = i < bytesRead.length ? bytesRead[i] : 0;
  			}
  			return Math.min(myBuffer.length, bytesRead.length);
  		}
  		////////////////////////////////////////////////////////////////////////////////////
  		//// When running in command mode, code below works for either regular file or STDIN
  
  		if (!FileIOData.fdInUse(fd, 0)) // Check the existence of the "read" fd
  		{
  			fileErrorString = new String("File descriptor " + fd + " is not open for reading");
  			return -1;
  		}
  		// retrieve FileInputStream from storage
  		final InputStream InputStream = (InputStream) FileIOData.getStreamInUse(fd);
  		try {
  			// Reads up to lengthRequested bytes of data from this Input stream into an array of bytes.
  			retValue = InputStream.read(myBuffer, 0, lengthRequested);
  			// This method will return -1 upon EOF, but our spec says that negative
  			// value represents an error, so we return 0 for EOF.  DPS 10-July-2008.
  			if (retValue == -1) { retValue = 0; }
  		} catch (final IOException e) {
  			fileErrorString = new String("IO Exception on read of file with fd " + fd);
  			return -1;
  		} catch (final IndexOutOfBoundsException e) {
  			fileErrorString = new String("IndexOutOfBoundsException on read of file with fd" + fd);
  			return -1;
  		}
  		return retValue;
  
  	} // end readFromFile
  
  	/**
  	 * Open a file for either reading or writing. Note that read/write flag is NOT
  	 * IMPLEMENTED. Also note that file permission modes are also NOT IMPLEMENTED.
  	 *
  	 * @param filename string containing filename
  	 * @param flag     0 for read, 1 for write
  	 * @return file descriptor in the range 0 to SYSCALL_MAXFILES-1, or -1 if error
  	 * @author Ken Vollmar
  	 */
  	public static int openFile(final String filename, final int flags) {
  		// Internally, a "file descriptor" is an index into a table
  		// of the filename, flag, and the File???putStream associated with
  		// that file descriptor.
  
  		int retValue = -1;
  		FileInputStream inputStream;
  		FileOutputStream outputStream;
  		int fdToUse;
  
  		// Check internal plausibility of opening this file
  		fdToUse = FileIOData.nowOpening(filename, flags);
  		retValue = fdToUse; // return value is the fd
  		if (fdToUse < 0) { return -1; }   // fileErrorString would have been set
  
  		if (flags == O_RDONLY) // Open for reading only
  		{
  			try {
  				// Set up input stream from disk file
  				inputStream = new FileInputStream(filename);
  				FileIOData.setStreamInUse(fdToUse, inputStream); // Save stream for later use
  			} catch (final FileNotFoundException e) {
  				fileErrorString = new String("File " + filename + " not found, open for input.");
  				retValue = -1;
  			}
  		} else if ((flags & O_WRONLY) != 0) // Open for writing only
  		{
  			// Set up output stream to disk file
  			try {
  				outputStream = new FileOutputStream(filename, (flags & O_APPEND) != 0);
  				FileIOData.setStreamInUse(fdToUse, outputStream); // Save stream for later use
  			} catch (final FileNotFoundException e) {
  				fileErrorString = new String("File " + filename + " not found, open for output.");
  				retValue = -1;
  			}
  		}
  		return retValue; // return the "file descriptor"
  
  	}
  
  	/**
  	 * Close the file with specified file descriptor
  	 *
  	 * @param fd the file descriptor of an open file
  	 */
  	public static void closeFile(final int fd) {
  		FileIOData.close(fd);
  	}
  
  	/**
  	 * Reset all files -- clears out the file descriptor table.
  	 */
  	public static void resetFiles() {
  		FileIOData.resetFiles();
  	}
  
  	/**
  	 * Retrieve file operation or error message
  	 *
  	 * @return string containing message
  	 */
  	public static String getFileErrorMessage() { return fileErrorString; }
  
  	///////////////////////////////////////////////////////////////////////
  	// Private method to simply return the BufferedReader used for
  	// keyboard input, redirected input, or piped input.
  	// These are all equivalent in the eyes of the program because they are
  	// transparent to it.  Lazy instantiation.  DPS.  28 Feb 2008
  
  	private static BufferedReader getInputReader() {
  		if (inputReader == null) { inputReader = new BufferedReader(new InputStreamReader(System.in)); }
  		return inputReader;
  	}
  
  	// //////////////////////////////////////////////////////////////////////////////
  	// Maintain information on files in use. The index to the arrays is the "file descriptor."
  	// Ken Vollmar, August 2005
  
  	private static class FileIOData {
  
  		private static String[] fileNames = new String[SYSCALL_MAXFILES]; // The filenames in use. Null if file descriptor i is not in use.
  		private static int[] fileFlags = new int[SYSCALL_MAXFILES]; // The flags of this file, 0=READ, 1=WRITE. Invalid if this file descriptor is not in use.
  		private static Object[] streams = new Object[SYSCALL_MAXFILES]; // The streams in use, associated with the filenames
  
  		// Reset all file information. Closes any open files and resets the arrays
  		private static void resetFiles() {
  			for (int i = 0; i < SYSCALL_MAXFILES; i++) {
  				close(i);
  			}
  			setupStdio();
  		}
  
  		// DPS 8-Jan-2013
  		private static void setupStdio() {
  			fileNames[STDIN] = "STDIN";
  			fileNames[STDOUT] = "STDOUT";
  			fileNames[STDERR] = "STDERR";
  			fileFlags[STDIN] = SystemIO.O_RDONLY;
  			fileFlags[STDOUT] = SystemIO.O_WRONLY;
  			fileFlags[STDERR] = SystemIO.O_WRONLY;
  			streams[STDIN] = System.in;
  			streams[STDOUT] = System.out;
  			streams[STDERR] = System.err;
  			System.out.flush();
  			System.err.flush();
  		}
  
  		// Preserve a stream that is in use
  		private static void setStreamInUse(final int fd, final Object s) {
  			streams[fd] = s;
  
  		}
  
  		// Retrieve a stream for use
  		private static Object getStreamInUse(final int fd) {
  			return streams[fd];
  
  		}
  
  		// Determine whether a given filename is already in use.
  		private static boolean filenameInUse(final String requestedFilename) {
  			for (int i = 0; i < SYSCALL_MAXFILES; i++) {
  				if (fileNames[i] != null && fileNames[i].equals(requestedFilename)) {
  					// System.out.println("Mars.SystemIO.FileIOData.filenameInUse: rtng TRUE for " + requestedFilename);
  					return true;
  				}
  			}
  
  			// System.out.println("Mars.SystemIO.FileIOData.filenameInUse: rtng TRUE for " + requestedFilename);
  			return false;
  
  		}
  
  		// Determine whether a given fd is already in use with the given flag.
  		private static boolean fdInUse(final int fd, final int flag) {
  			if (fd < 0 || fd >= SYSCALL_MAXFILES) {
  				return false;
  			} else if (fileNames[fd] != null && fileFlags[fd] == 0 && flag == 0) {  // O_RDONLY read-only
  				return true;
  			} else if (fileNames[fd] != null && (fileFlags[fd] & flag & O_WRONLY) == O_WRONLY) {  // O_WRONLY write-only
  				return true;
  			}
  			return false;
  
  		}
  
  		// Close the file with file descriptor fd. No errors are recoverable -- if the user's
  		// made an error in the call, it will come back to him.
  		private static void close(final int fd) {
  			// Can't close STDIN, STDOUT, STDERR, or invalid fd
  			if (fd <= STDERR || fd >= SYSCALL_MAXFILES) { return; }
  
  			fileNames[fd] = null;
  			// All this code will be executed only if the descriptor is open.
  			if (streams[fd] != null) {
  				final int keepFlag = fileFlags[fd];
  				final Object keepStream = streams[fd];
  				fileFlags[fd] = -1;
  				streams[fd] = null;
  				try {
  					if (keepFlag == O_RDONLY) {
  						((FileInputStream) keepStream).close();
  					} else {
  						((FileOutputStream) keepStream).close();
  					}
  				} catch (final IOException ioe) {
  					// not concerned with this exception
  				}
  			} else {
  				fileFlags[fd] = -1; // just to be sure... streams[fd] known to be null
  			}
  		}
  
  		// Attempt to open a new file with the given flag, using the lowest available file descriptor.
  		// Check that filename is not in use, flag is reasonable, and there is an available file descriptor.
  		// Return: file descriptor in 0...(SYSCALL_MAXFILES-1), or -1 if error
  		private static int nowOpening(final String filename, final int flag) {
  			int i = 0;
  			if (filenameInUse(filename)) {
  				fileErrorString = new String("File name " + filename + " is already open.");
  				return -1;
  			}
  
  			if (flag != O_RDONLY && flag != O_WRONLY && flag != (O_WRONLY | O_APPEND)) // Only read and write are implemented
  			{
  				fileErrorString = new String("File name " + filename + " has unknown requested opening flag");
  				return -1;
  			}
  
  			while (fileNames[i] != null && i < SYSCALL_MAXFILES) {
  				i++;
  			} // Attempt to find available file descriptor
  
  			if (i >= SYSCALL_MAXFILES) // no available file descriptors
  			{
  				fileErrorString = new String("File name " + filename + " exceeds maximum open file limit of "
  						+ SYSCALL_MAXFILES);
  				return -1;
  			}
  
  			// Must be OK -- put filename in table
  			fileNames[i] = new String(filename); // our table has its own copy of filename
  			fileFlags[i] = flag;
  			fileErrorString = new String("File operation OK");
  			return i;
  
  		}
  
  	} // end private class FileIOData
  		////////////////////////////////////////////////////////////////////////////////
  
  }
  