7. Initializers

Part of CS:2820 Object Oriented Software Development Notes, Spring 2017
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

Where were we?

Putting everything together from the previous lecture, we have the following class definitions for building our road network:

import java.io.File;
import java.util.LinkedList;
import java.util.Scanner;

/** Roads are one-way streets linking intersections
 *  @see Intersection
 */
class Road {
        float travelTime;         //measured in seconds
        Intersection destination; //where the road goes
        Intersection source;      //where the comes from
        // name of road is source-destination
}

/** Intersections join roads
 *  @see Road
 */
class Intersection {
        String name;
        LinkedList <Road> outgoing = new LinkedList <Road> ();
        LinkedList <Road> incoming = new LinkedList <Road> ();
        // Bug: multiple types of intersections (uncontrolled, stoplight)
}

/** RoadNetwork, the main class to build a network of roads and intersections.
 *  @see Road
 *  @see Intersection
 */
public class RoadNetwork {
        public static void main(String[] args) {
                // Bug:  Ought to check to see if there is a file name
                Scanner sc = new Scanner(new File(args[0]));
                // Bug:  What if the file doesn't exist?
        }
}

Recall also that we have decided to use a file containing a list of intersections and roads to describe the road network. Ignoring all details of how roads and intersections are described, the file might look something like this:

intersection ...
intersection ...
intersection ...
road ...
road ...
road ...
road ...
road ...

The Skeletal code given above contains bugs, but it also contains a potential problem for programmers familiar with C or C++: The first command line argument after the program name is args[0]. This will seem strange to programmers accustomed to C or C++, where argv[0] is the program name and argv[1] is the first parameter after the program name. Another problem for C or C++ programmers is that there is no count of command line arguments in Java. To find out how many command line arguments there are in an Java program, use args.length where a C or C++ programmer would use argc.

What does a scanner do? We can ask the scanner whether there is more input with sc.hasNext(). We can ask if the next input is a number with sc.hasNextInteger() or sc.hasNextFloat(). We can ask for the next string from the input with sc.next() or the next integer from the input with sc.nextInt().

Processing the text file

The outermost loop of the road network initializer is pretty obvious: Read lines from the text file and process them. We could do all the processing in the outer loop, but that means that the outer loop needs to know about every detail of describing roads and intersections. We are better off if we can move the details of road description into the road class, and the details of intersection description into the intersection class. We do that here:

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

/** RoadNetwork, the main class to build a network of roads and intersections.
 *  @see Road
 *  @see Intersection
 */
public class RoadNetwork {

        /* the sets of all roads and all intersections */
        static LinkedList <Road> roads
                = new LinkedList <Road> ();
        static LinkedList <Intersection> inters
                = new LinkedList <Intersection> ();

        public static void main(String[] args) {
                // Bug:  Ought to check to see if there is a file name
                try {
                        Scanner sc = new Scanner(new File(args[0]));
                        while (sc.hasNext()) {
                                // until the input file is finished
                                string command = sc.next()
                                if ((command == "intersection")
                                ||  (command == "i")) {
                                        // Bug: Something about an intersection
                                        inters.add(
                                                new Intersection( sc, inters )
                                        );
                                } else if ((command == "road")
                                ||         (command == "r" )) {
                                        // Bug: Something about a road
                                        roads.add( new Road( sc, inters ) );
                                } else {
                                        // Bug: Unknown command
                                }
                        }
                } catch (FileNotFoundException e) {
                        // Bug:  What if the file doesn't exist?
                }
        }
}

The central tool used above is the next() method of class Scanner. You should look up class scanner to see all of its next methods, but the simplest of these is simply called next(), and it simply returns the next string from the input stream, where successive strings are delimited by things like spaces, tabs and newlines. Other next methods get the next integer, the next boolean, the next character, or the next float. We will use some of these later.

The above code assumes that we can use initializers for classes Road and Intersection to create a new class members, where the initializer scans the description of the new object from the source file. When we create the new road or intersection, we needs to know the set of intersections. In the case of creating intersections, this is because we need to prevent multiple declarations of the same intersection. In the case of roads, this is because we need to look up the source and destination intersections of the road.

The above code allows a shorthand description of the road network using r as an abbreviation for road and i as an abbreviation for intersection. Typing road is not too onerous, but typing intersection gets very old very fast.

It's a bit of a nuisance indenting things as deeply as they are above, but the alternative is to put the loop outside the try block, and that makes it possible that sc could be null. Alternatively, we can put the bulk of the code in a second method:

        /** Initialize this road network by scanning its description
         */
        static void initializeNetwork( Scanneer sc ) {
                while (sc.hasNext()) {
                        // until the input file is finished
                        string command = sc.next()
                        if ((command == "intersection")
                        ||  (command == "i")) {
                                // Bug: Something about an intersection
                                inters.add( new Intersection( sc, inters ) );
                        } else if ((command == "road")
                        ||         (command == "r" )) {
                                // Bug: Something about a road
                                roads.add( new Road( sc, inters ) );
                        } else {
                                // Bug: Unknown command
                                // Bug: Should we allow comments?
                        }
                }
        }

        /** Main program
         * @see initializeNetwork
         */
        public static void main(String[] args) {
                // Bug:  Ought to check to see if there is a file name
                try {
                        initializeNetwork( new Scanner(new File(args[0])) );
                } catch (FileNotFoundException e) {
                        // Bug:  What if the file doesn't exist?
                }
        }
}

As we've already indicated, the above code assumes that the initializers for classes Road and Intersection exist. In order to write this code, we need to start fleshing out the details of the source file describing the road network. Here, we will assume that the first item in each line is the name of the road or intersection. For roads, we'll assume that the next attribute is the travel time. So, our network description file will look something like this:

intersection a
intersection b
intersection c
road a b 30
road b a 30
road a c 12
road c a 12
road b c 22

Those numbers above, giving the travel times, could be seconds (for short roads in an urban setting) or they could be minutes (for long roads connecting nearby cities). All that matters to a computer simulation is that all of the times given are in the same units.

Here is class Road with a preliminary version of a reasonable initializer to read from the above file:

/** Roads are one-way streets linking intersections
 *  @see Intersection
 */
class Road {
        float travelTime;         //measured in seconds
        Intersection destination; //where the road goes
        Intersection source;      //where the comes from
        // name of road is source-destination

        // initializer
        public Road( Scanner sc, LinkedList <Intersection> inters ) {
                // code here must scan & process the road definition

                string sourceName = sc.next();
                string dstName = sc.next();
                // Bug:  Must look up source and dest names to find objects
                
                // Bug:  What if the next isn't a float
                travelTime = sc.nextFloat();
                string skip = sc.nextLine();
        }       
}       

There are, of course, some bugs here! We need to do quite a bit of work, and, of course, we need to write the corresponding code for class Intersection.