File I/O
[Agent practical 6 of 9]


Now we're going to read some data in.


First we're going to need to import some classes. Specifically classes from within the java.io and java.util packages. Using your notes on the lecture about using others' code, import everything from these packages into IO. Compile your files to check the packages have been recognised.


Now we're going to put the code to read the file into the readData method of the IO class.

First, we need to tell the method which file to read. For now, we're going to hardwire this in - this will save us continually having to open the file with a FileDialog while we do development.

   public double[][] readData() {
      File f = new File("in.txt");
      return new double[][] {};
   }

Again, compile the file to check it. Note that we have't wired in the whole filepath, just the filename. This is fine -- java will look for the file in the directory that the application starts from. With unpackaged files, this is the directory the .class files are in. With packages, it is the directory that contains the package hierarchy. With executable jars (which we'll look at later), it is the directory the jar runs from. With an IDE, which often alter classpaths quite a lot, you'll need to write something out to find where it considers the current directory.


Now, after this we need the code from the the input/output lecture that:

1) Opens a FileReader on the file, f, above (but doesn't use this directly to read).
2) Opens a BufferedReader on the FileReader
3) Uses this to read data into an array of Strings, each line in the file being a single String in the array.

Ultimately, once we've finished this bit, readData will look something like this:

   public double[][] readData() {

      File f = new File("in.txt");

      // Set up a FileReader (must be in a try-catch block).
      // Wrap this in a BufferedReader
      // Declare any variables needed below, including the two arrays.

      // Open a try block
         // Read though counting the lines in the file.
         // Make an appropriately sized 1D String array.
         // Close the buffer and make a new FileReader and BufferedReader
            // (you can use the same labels).
         // Loop, copying file lines into the array, one line per cell.
         // Close the Buffer (and thus the Reader as well).
      // Close try-catch block.

   }

You'll need a couple of bits from the lecture. First, the bit that makes a FileReader and then the bit that wraps a BufferedReader around it and reads data into a 1D String array. Find these bits of code and copy them into the readData method as above (again, you can find a powerpoint containing these bits of code on the course outline page -- right postit-like note on the homepage, or helpful code on the 'Key Ideas' page for the lecture).

Note that the bit that everyone always forgets is closing the BufferedReader after counting the lines, and then making a new FileReader/Buffer so you can then read in the data. If you don't close it and make a new FileReader/Buffer you end up reading off the end of the file, which results in reading in nulls, and an empty data array.

It is worth a quick note here on exception-handling. The above outline includes a number of things that need try-catch blocks around them. In general you might as well let the compiler tell you when these are needed. Usually you also try to keep the blocks tight around the code generating them, so separate specific exceptions are caught by separate specific try-catch blocks. However, in the above description I've suggested putting a try-catch block around several things that need one for the same type of exception (reading lines, closing the FileReader). This is mainly to make it easier for you. Ultimately you might want to split these up, though in this case if one thing works, chances are the others will. If you're not sure of which exception is thrown by the code, you can find this information in the API docs; however, the compiler will also tell you if you leave out the try-catch blocks initially.

Once you've written the code, can you work out a way to test it is working? (hint, as we can't yet return a proper 2D double array you'll need to change the return type to String[] at least if you want to test it from Model, but you might be better testing it in IO).


Next we need to parse the 1D String array into a 2D double array.

Again, the code for doing this with a StringTokenizer is in the lecture notes. The end of our readData method will look something like this:

      // Run through the array and use a StringTokenizer
            // to parse the data into a double[][] array.

      // return the full double[][] array rather than an empty one.

Once you've done that, you should be in a position to return the double 2D array and test the method fully.

Once you're sure your code is working, we need to make sure the data is stored properly in our Environment class. Ultimately we want our Model class constructor to do this:

   private Environment world = new Environment();

   public Model () {

      IO io = new IO();
      double[][] a = io.readData();
      world.setData(a);

      // buildWorld(); <-- remember to comment out this code
      buildAgents();
      runAgents();

   }

If you don't already have a setData() method in Environment, you'll have to write one. Remember to comment out any code that fills the world with default data values of 255.0, or this will overwrite the data you read in.

Note that we don't need to know how large the array being drawn into Model is - we just attach the label "a" to whatever readData() gives us. We can actually do the above with less code:

   private Environment world = new Environment();

   public Model () {

      IO io = new IO();
      world.setData(io.readData());

      // buildWorld();
      buildAgents();
      runAgents();

   }

Add one of these sets of code to your Model class and test the code to make sure it is working ok.

NB: If it comes back complaining of a null pointer exception in using the StringTokenizer, check you've closed the buffer and re-opened both the FileReader and the BufferedReader where you need to re-set the file to the start, above. If you don't do this, the code to read the file will just count to the end of the file, then start reading code from that point off the end of the file, rather than restarting from the beginning. If this happens, your StringTokenizer will just be full of nulls.


Next, go on to Part Three, where we'll write out the data.