/** RoadNetwork.java -- classes that describe a road network * @author Douglas Jones * @version 2017-03-03 * This version of the code builds on the version distributed on 03-01 by * using lambda expressions to delay evaluation of expressions used in * calls to the ScanSupport.lineEnd() routine. */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; import java.util.regex.Pattern; /** Utility package for error handling */ class Errors { private Errors(){}; // you may never instantiate this class /** Call this to warn of non fatal errors */ public static void warn( String message ) { System.err.println( "Warning: " + message ); } /** Call this to report fatal errors */ public static void fatal( String message ) { System.err.println( "Fatal error: " + message ); System.exit( -1 ); } } /** Support methods for scanning * @see Errors */ class ScanSupport { /** Pattern for identifers */ public static final Pattern name = Pattern.compile( "[a-zA-Z0-9_]*" ); /** Pattern for whitespace excluding things like newline */ public static final Pattern whitespace = Pattern.compile( "[ \t]*" ); /** 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 ); // the following is weird code, it skips the name // and then returns the string that matched what was skipped sc.skip( name ); return sc.match().group(); } /** Class used only for deferred parameter passing to lineEnd */ public interface EndMessage { public abstract String myString(); } /** Advance to next line and complain if is junk at the line end; * call this when all useful content has been consumed from the line * then call this to skip optional line-end comments to the next line * @see Errors * @param sc the scanner from which end of line is scanned * @param message will be evaluated only when there is an error; * it is typically passed as a lambda expression, for example, * ScanSupport.lineEnd( sc, () -> "this " + x + " that" ); */ public static void lineEnd( Scanner sc, EndMessage message ) { sc.skip( whitespace ); String lineEnd = sc.nextLine(); if ( (!lineEnd.equals( "" )) && (!lineEnd.startsWith( "--" )) ) { Errors.warn( "" + message.myString() + " followed unexpected by '" + lineEnd + "'" ); } } } /** Roads link intersections * @see Intersection */ class Road { private final float travelTime; // how long to travel down road private final Intersection destination; // where road goes, or null private final Intersection source; // source of road, or null // Road name is the source-destination names /** initializer scans and processes one road definition * @param sc the Scanner from which the Road description is read */ public Road( Scanner sc ) { String srcName = ScanSupport.nextName( sc ); String dstName = ScanSupport.nextName( sc ); // if there are no next names on this line, these are "" // therefore, the findIntersection calls below will fail source = RoadNetwork.findIntersection( srcName ); if (source == null) { Errors.warn( "Road '" + srcName + "' '" + dstName + "' source undefined." ); } destination = RoadNetwork.findIntersection( srcName ); if (destination == null) { Errors.warn( "Road '" + srcName + "' '" + dstName + "' destination undefined." ); } if (sc.hasNextFloat()) { travelTime = sc.nextFloat(); } else { Errors.warn( "Road '" + srcName + "' '" + dstName + "' no travel time given." ); travelTime = 99.999f; } ScanSupport.lineEnd( sc, () -> "Road '" + srcName + "' '" + dstName + "'" ); } /** output this road in a format like that used for input */ public String toString() { String srcName; String dstName; if (source == null) { srcName = "???"; } else { srcName = source.name; } if (destination == null) { dstName = "???"; } else { dstName = destination.name; } return( "road " + srcName + " " + dstName + " " + travelTime ); } } /** Intersections are linked by one-way roads * only subclasses of Intersection may be instantiated * @see Road * @see NoStop * @see StopLight */ abstract class Intersection { private final LinkedList outgoing = new LinkedList (); // Bug: Does the intersection need a list of incoming roads? public String name; // the name of the intersection /** factory scans and processes one intersection definition * @param sc Scanner from which the Intersectios description is read * @return either a new Intersection or null if description is broken */ public static Intersection newIntersection( Scanner sc ) { String myName = ScanSupport.nextName( sc ); if ((myName == null) || ("".equals( myName ))) { Errors.warn( "Intersection has no name" ); sc.nextLine(); return null; } if (RoadNetwork.findIntersection( myName ) != null) { Errors.warn( "Intersection '" + myName + "' redefined." ); sc.nextLine(); return null; } String kind = ScanSupport.nextName( sc ); if ((kind == null) || ("".equals( kind ))) { // default NoStop return new NoStop( sc, myName ); } else if ("stoplight".equals( kind )) { // stoplight return new StopLight( sc, myName ); } else { // error Errors.warn( "Intersection '" + myName + "' '" + kind + "' not an intersection kind" ); sc.nextLine(); } return null; } /** output this Intersection in a format like that used for input */ public String toString() { return( "intersection " + name ); } } /** Uncontrolled Intersections, cars pass through first-come first-served. * @see Intersection */ class NoStop extends Intersection { /** initializer scans and processes one uncontrolled Intersection * @param sc Scanner from which the Intersectios description is read * @param myName the value to be put in the name field */ public NoStop( Scanner sc, String myName ) { // now keyword for intersection type was scanned name = myName; ScanSupport.lineEnd( sc, () -> "Intersection '" + name + "'" ); } /** output this uncontrolled intersection in a format like the input */ public String toString() { return super.toString(); } } /** Stoplight protected Intersections, cars pass through on green lights. * @see Intersection */ class StopLight extends Intersection { // attributes of the stop light timing cycle final float greenTime; final float yellowTime; /** initializer scans and processes one uncontrolled Intersection * @param sc Scanner from which the Intersectios description is read * @param myName the value to be put in the name field */ public StopLight( Scanner sc, String myName ) { // now keyword for StopLight was scanned name = myName; if (sc.hasNextFloat()) { greenTime = sc.nextFloat(); } else { Errors.warn( "Intersection '" + name + "' stoplight, no green time given." ); greenTime = 99.999f; } if (sc.hasNextFloat()) { yellowTime = sc.nextFloat(); } else { Errors.warn( "Intersection '" + name + "' stoplight, no yellow time given." ); yellowTime = 99.999f; } ScanSupport.lineEnd( sc, () -> "Intersection '" + name + "' stoplight '" + greenTime + "' '" + yellowTime ); } /** output this Intersection in a format like that used for input */ public String toString() { return( super.toString() + " stoplight" + greenTime + " " + yellowTime ); } } class Vehicle { // Bug: does this go here? } class Event { // Bug: does this go here? } /** RoadNetwork -- main program that reads and writes a road network * @see Road * @see Intersection * @see Errors * @see main */ public class RoadNetwork { // lists of roads and intersectins static LinkedList roads = new LinkedList (); static LinkedList inters = new LinkedList (); /** utility method to look up an intersection by name * @param s is the name of the intersection, a string * @return is the Intersection object with that name */ public static Intersection findIntersection( String s ) { for ( Intersection i: inters ) { if (i.name.equals( s )) return i; } return null; } /** read a road network */ public static void initializeNetwork( Scanner sc ) { while (sc.hasNext()) { // until we hit the end of the file String command = ScanSupport.nextName( sc ); if (("intersection".equals( command )) || ("i".equals( command )) ) { Intersection i = Intersection.newIntersection(sc); if (i != null) inters.add( i ); } else if (("road".equals( command )) || ("r".equals( command )) ) { roads.add( new Road( sc ) ); } else if ("".equals( command )) { // blank line // line holding -- ends up here! ScanSupport.lineEnd( sc, () -> "Line" ); } else { Errors.warn( "Command '" + command + "' is not road or intersection" ); sc.nextLine(); // skip the rest of the error } } } /** write out a road network */ public static void writeNetwork() { for ( Intersection i: inters ) { System.out.println( i.toString() ); } for ( Road r: roads ) { System.out.println( r.toString() ); } } /** main program that reads and writes a road network */ public static void main( String[] args ) { // verify that the argument exists. if (args.length < 1) { Errors.fatal( "Missing file name on command line" ); } else if (args.length > 1) { Errors.fatal( "Unexpected command line args" ); } else try { initializeNetwork( new Scanner( new File( args[0] ) ) ); writeNetwork(); } catch (FileNotFoundException e) { Errors.fatal( "Could not read '" + args[0] + "'" ); } } }