/* RoadNetwork.java * Program to process description of a road network * author Douglas W. Jones * version 2017-09-29 * incorporates code from version 2017-09-14 * incorporates code from the posted solution to MP2 * incorporates code from Lecture 10 */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.regex.Pattern; import java.util.Scanner; /** Error reporting package * provides standard prefix and behavior for messages */ class Errors { // error messages are counted. private static int errorCount = 0; /** Allow public read-only access to the count of error messages * @return the count */ public static int count() { return errorCount; } /** Report nonfatal errors, output a message and return * @arg message the message to output */ public static void warn( String message ) { System.err.println( "RoadNetwork: " + message ); errorCount = errorCount + 1; } /** Report fatal errors, output a message and exit, never to return * @arg message the message to output */ public static void fatal( String message ) { warn( message ); System.exit( 1 ); } } /** Support methods for scanning * @see Errors */ class ScanSupport { // patterns needed for scanning private static final Pattern name = Pattern.compile( "[a-zA-Z0-9_]*" ); private static final Pattern whitespace = Pattern.compile( "[ \t]*" ); // no newlines /** Get next name without skipping to next line (unlike sc.Next()) * @param sc the scanner from which end of line is scanned * @return the name, if there was one, or an empty string */ public static String nextName( Scanner sc ) { sc.skip( whitespace ); sc.skip( name ); return sc.match().group(); } /** Advance to next line and complain if is junk at the line end * @see Errors * @param sc the scanner from which end of line is scanned * @param message gives a prefix to give context to error messages * This version supports comments starting with -- */ public static void lineEnd( Scanner sc, String message ) { sc.skip( whitespace ); String lineEnd = sc.nextLine(); if ( (!lineEnd.equals( "" )) && (!lineEnd.startsWith( "--" )) ) { Errors.warn( message + " followed unexpected by '" + lineEnd + "'" ); } } } /** Roads are joined by Intersections and driven over by Vehicles * @see Intersection */ class Road { // constructors may throw this when an error prevents construction public static class ConstructorFailure extends Exception {} float travelTime; // measured in seconds, always positive Intersection destination; // where this road goes, never null Intersection source; // where this road comes from, never null // name of a road is source-destination /** construct a new road by scanning its description from the source file */ Road( Scanner sc ) throws ConstructorFailure { String sourceName = ScanSupport.nextName(sc); String dstName = ScanSupport.nextName(sc); source = RoadNetwork.findIntersection( sourceName ); destination = RoadNetwork.findIntersection( dstName ); if (source == null) { Errors.warn( "No such source intersection: Road " + sourceName + dstName ); sc.nextLine(); throw new ConstructorFailure(); } if (destination == null) { Errors.warn( "No such destination intersection: Road " + sourceName + dstName ); sc.nextLine(); throw new ConstructorFailure(); } if (sc.hasNextFloat()) { travelTime = sc.nextFloat(); if (travelTime < 0.0F) { Errors.warn( "Negative travel time:" + this.toString() ); travelTime = 99999.0F; // no failure needed, use bogus value } } else { Errors.warn( "Floating point travel time expected: Road " + sourceName + " " + dstName ); travelTime = 99999.0F; // no failure needed, use bogus value } ScanSupport.lineEnd( sc, this.toString() ); } /** output the road in a form like that used for input */ public String toString() { return "road " + source.name + " " + destination.name + " " + travelTime; } } /** Intersections pass Vehicles between Roads * @see Road */ class Intersection { // constructors may throw this when an error prevents construction public static class ConstructorFailure extends Exception {} public String name; // textual name of intersection, never null! LinkedList outgoing; // set of all roads out of this intersection LinkedList incoming; // set of all roads in to this intersection // Bug: Is incoming really needed? // Bug: When are the above ever set to anything? /** factory method to create intersections * @param sc the scanner to read intersection description from * @return either a new intersection or null */ public static Intersection newIntersection( Scanner sc ) throws ConstructorFailure { String name = ScanSupport.nextName( sc ); if ("".equals( name )) { Errors.warn( "Intersection has no name" ); sc.nextLine(); throw new ConstructorFailure(); } if (RoadNetwork.findIntersection( name ) != null) { Errors.warn( "Intersection redefined: " + name ); sc.nextLine(); throw new ConstructorFailure(); } String intersectionType = ScanSupport.nextName( sc ); if ("nostop".equals( intersectionType )) { return new NoStop( sc, name ); } else if ("stoplight".equals( intersectionType )) { return new StopLight( sc, name ); } if ("".equals( intersectionType )) { Errors.warn( "Intersection has no type: " + name ); sc.nextLine(); throw new ConstructorFailure(); } else { Errors.warn( "intersection " + name + " " + intersectionType + ": unknown type: " ); sc.nextLine(); throw new ConstructorFailure(); } } /** output the intersection in a form like that used for input */ public String toString() { return "intersection " + name; } } /** Intersection with no control, neither stopsign nor stoplight */ class NoStop extends Intersection { NoStop( Scanner sc, String name ) { this.name = name; ScanSupport.lineEnd( sc, this.toString() ); } public String toString() { return super.toString() + " nostop"; } } /** Intersection with a stoplight */ class StopLight extends Intersection { StopLight( Scanner sc, String name ) { this.name = name; ScanSupport.lineEnd( sc, this.toString() ); } public String toString() { return super.toString() + " stoplight"; } } /** Vehicles drive over Roads between Intersections * @see Road * @see Intersection */ class Vehicle { // Bug: what are the attributes of a vehicle? // Bug: do vehicles need to know where they are? } public class RoadNetwork { /* the sets of all roads and all intersections */ private static LinkedList roads = new LinkedList (); private static LinkedList inters = new LinkedList (); /** Find an intersection by textual name in the set inters * @param s name of an intersection * @return the intersection named s or null if none */ public static Intersection findIntersection( String s ) { // quick and dirty implementation for (Intersection i: inters) { if (i.name.equals( s )) { return i; } } return null; } /** Initialize this road network by scanning its description */ private static void readNetwork( Scanner sc ) { while (sc.hasNext()) { String command = sc.next(); if ("intersection".equals( command )) { try { inters.add( Intersection.newIntersection( sc ) ); } catch (Intersection.ConstructorFailure e) { // do nothing, the constructor already reported the error } } else if ("road".equals( command )) { try { roads.add( new Road( sc ) ); } catch (Road.ConstructorFailure e) { // do nothing, the constructor already reported the error } } else if ("--".equals( command )) { sc.nextLine(); } else { Errors.warn( "unknown command: " + command ); sc.nextLine(); } } } /** Print out the road network to system.out */ private static void printNetwork() { for (Intersection i: inters) { System.out.println( i.toString() ); } for (Road r: roads) { System.out.println( r.toString() ); } } /** Main program */ public static void main( String[] args ) { if (args.length < 1) { Errors.fatal( "Missing file name argument" ); } else if (args.length > 1) { Errors.fatal( "Too many arguments" ); } else try { readNetwork( new Scanner( new File( args[0] ) ) ); if (Errors.count() == 0) printNetwork(); } catch (FileNotFoundException e) { Errors.fatal( "Can't open the file" ); } } }