/* Road Network Simulator * Author: Douglas Jones * Status: Compiles and works, cleaner but poorly tested * Version: 9/17/2020 */ import java.util.LinkedList; import java.util.Iterator; import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; // Utility classes /** * Error handling */ class Error{ // BUG: Perhaps we should count warnings public static void warn( String message ) { System.err.println( message + "\n" ); // BUG: Perhaps count warnings and error out if too many } public static void fatal( String message ) { warn( message ); System.exit( 1 ); } } /** * Wrapper or Adapter for scanners that integrates error handling * @see java.util.Scanner * @see Error */ class MyScanner { Scanner self; // the scanner this object wraps // constructor we wish to inherit from Scanner but can't because it's Java public MyScanner( File f ) throws FileNotFoundException { self = new Scanner( f ); } // methods we wish could inherit from Scanner but can't beause it's final public boolean hasNext() { return self.hasNext(); } public boolean hasNext( String s ) { return self.hasNext( s ); } public boolean hasNextFloat() { return self.hasNextFloat(); } public String next() { return self.next(); } public float nextFloat() { return self.nextFloat(); } // new methods we add to this class /** Get the next string, if one is available * @param def the default value if no string is available * @param msg the error message to print if no string is available */ public String getNext( String def, String msg ) { if (self.hasNext()) return self.next(); Error.warn( msg ); return def; } /** Get the next float, if one is available * @param def the default value if no string is available * @param msg the error message to print if no string is available */ public float getNextFloat( float def, String msg ) { if (self.hasNextFloat()) return self.nextFloat(); Error.warn( msg ); return def; } } // Simulation Model Classes /** * Roads connect intersections * @see Intersection */ class Road { // instance variables private final float travelTime; private final Intersection destination; private final Intersection source; // the collection of all instances private static final LinkedList allRoads = new LinkedList (); /** The only constructor * @param sc MyScanner from which description comes * Input format scanned from sc: source-name destination-name travel-time *
where source-name is the name of the source intersection and *
destination-name is the name of the destination intersection and *
travel-time is a floating point time in seconds */ public Road( MyScanner sc ) { // keyword Road was already scanned final String src; // where does it come from final String dst; // where does it go src = sc.getNext( "???", "road source missing" ); dst = sc.getNext( "???", "road " + src + " to missing destination" ); travelTime = sc.getNextFloat( Float.NaN, "road " + src + " to missing destination" ); destination = Intersection.lookup( dst ); if (destination == null) { Error.warn( "road " + src + " " + dst + " undefined: " + dst ); } source = Intersection.lookup( src ); if (source == null) { Error.warn( "road " + src + " " + dst + " undefined: " + src ); } // BUG: Can we prevent creation of malformed roads (see toString bug) allRoads.add( this ); // this is the only place items are added! } /** Primarily for debugging * @return textual name and travel time of the road */ public String toString() { return "Road " + source.name + " " + destination.name + " " + travelTime; // BUG throws exception when source/destination is null } /** Allow outsiders to iterate over all roads * @return textual name and travel time of the road */ public static Iterator iterator() { return allRoads.iterator(); } } /** * Intersections are connected by roads, only subclasses are ever instantiated * @see Road */ abstract class Intersection { // instance variables final String name; private final LinkedList outgoing = new LinkedList (); private final LinkedList incoming = new LinkedList (); // the collection of all instances private static final LinkedList allIntersections = new LinkedList (); /** Constructor needed to initialize final fields * @param name of the intersection */ Intersection( String name ) { this.name = name; } /** The the factory for intersections * @arg sc the scanner from which the Intersection description is scanned * Input format scanned from sc: name sub *
where name is a string *
where subclass is a string *
The constructor for the subclass deals with other details, if any */ public static Intersection factory( MyScanner sc ) { // pick off name of intersection; String name = sc.getNext( "???", "Intersection name missing" ); Intersection self; // this is the intersection the factory is working on // find out what kind of intersection call right constructor if (sc.hasNext( "stoplight" )) { sc.next(); // skip keyword stoplight self = new StopLight( sc, name ); } else { // plain uncontrolled intersection self = new NoStop( sc, name ); } // BUG: Can we prevent creation of malformed interesections. allIntersections.add( self ); return self; } /** Primarily for debugging * @return textual name and travel time of the road */ public String toString() { return "Intersection " + name; } /** Allow outsiders to iterate over all roads * @return textual name and travel time of the road */ public static Iterator iterator() { return allIntersections.iterator(); } /** Allow finding intersections by name * @return textual name and travel time of the road */ public static Intersection lookup( String n ) { for (Intersection i: allIntersections) { if (i.name.equals( n )) return i; } return null; } } /** * Uncontrolled Intersections * @see Interesction */ class NoStop extends Intersection { /** Constructor * @param sc scanner from which to scan any extra details * @param name the name of the intersection */ NoStop( MyScanner sc, String name ) { super( name ); // needed because Interesection.name is final } /* Primarily for debugging * @return textual name and attributes of the intersection */ // public String toString() { // return "Intersection " + name; // the superclass version does this! // } } /** * Stoplight intersection * @see Interesction */ class StopLight extends Intersection { // BUG what are the parameters to a stoplight? Duration of red in any dir? /** Constructor * @param sc scanner from which to scan any extra details * @param name the name of the intersection */ StopLight( MyScanner sc, String name ) { super( name ); // needed because Interesection.name is final // BUG scan the parameters to the stop light } /** Primarily for debugging * @return textual name and attributes of the intersection */ public String toString() { return super.toString() + " Stoplight"; } } /** * Main class builds model and will someday simulate it * @see Road * @see Intersection */ public class RoadNetwork { private static void readNetwork( MyScanner sc ) { while (sc.hasNext()) { // until the input file is finished String command = sc.next(); if ("intersection".equals( command )) { Intersection.factory( sc ); } else if ("road".equals( command )) { new Road( sc ); } else { Error.warn( "unknown command: " + command ); } } } private static void writeNetwork() { for (Iterator i = Intersection.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } for (Iterator i = Road.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } } public static void main( String[] args ) { if (args.length < 1) { Error.fatal( "Missing file name argument" ); } else try { readNetwork( new MyScanner( new File( args[0] ) ) ); writeNetwork(); // BUG -- this code is for debugging only // BUG -- simulation code goes here if readNetwork success } catch ( FileNotFoundException e) { Error.fatal( "Can't open file: " + args[0] ); } } }