// RoadNetwork.java // author Doug Jones // version 2019-03-04 import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.List; import java.util.LinkedList; import java.util.regex.MatchResult; import java.util.regex.Pattern; import java.util.Scanner; /** Error reporting package * Provide a standard prefix and behavior for error reporting * @author Douglas W. Jones * @version 2019-02-13 */ class Errors { /** Prefix string for error messages */ private static String prefix = "??: "; /** Set prefix on error reports, should be done before any error reports * @arg p the prefix on any error messages */ public static void setPrefix( String p ) { prefix = p; } /** Report nonfatal errors, output a message and return * @arg m the message to output */ public static void warn( String m ) { System.err.println( prefix + ": " + m ); } /** Report fatal errors, output a message and die * @arg m the message to output */ public static void fatal( String m ) { warn( m ); System.exit( 1 ); } } /** Support package for application dependent extensions to class Scanner * @author Douglas Jones * @version 2019-03-04 * @see Errors * If class Scanner wasn't final, this could be a subclass */ class ScanSupport { // patterns used in methods below private static final Pattern name = // match one legal name Pattern.compile( "[A-Za-z]*[A-Za-z0-9]" ); private static final Pattern blanks = // match any number of blanks Pattern.compile( "[ \t]*" ); private static final Pattern theRest = // match text up to newline Pattern.compile( "[^\n]*" ); /** Support interface for passing deferred computation of message text * set up so that lambda notation can be used */ static interface MessageCarrier { String content(); } /** Scan and return one name, if available * @arg sc the scanner to read from * @arg msg the message to output if there is no name * Typically called as ScanSupport.scanName( sc, () -> string + expr ) */ public static String scanName( Scanner sc, MessageCarrier msg ) { if (sc.hasNext( name )) return sc.next(name); Errors.warn( msg.content() + ": name expected" ); return null; } /** Scan and return one float, if available * @arg sc the scanner to read from * @arg msg the message to output if there is no float * Typically called as ScanSupport.scanPosFloat( sc, () -> string + expr ) */ public static float scanPosFloat( Scanner sc, MessageCarrier msg ) { if (sc.hasNextFloat()) { final Float n = sc.nextFloat(); if (n > 0) return n; Errors.warn( msg.content() + ": expected " + n + " > 0.0" ); return n; } Errors.warn( msg.content() + ": positive number expected" ); return Float.NaN; } /** Skip the rest of this line, and complain if it's nonblank noncomment * @arg sc the scanner to read from * @arg msg the object that computes the message to output if needed * Typically called as ScanSupport.nextLine( sc, () -> string + expr ) * This code improves on the posted solution for homework 6, problem 2 */ public static void nextLine( Scanner sc, MessageCarrier msg ) { // first skip trailing blanks sc.skip( blanks ); // then get everything that remains on this line sc.skip( theRest ); final String remainder = sc.match().group( 0 ); // then see if it all makes sense if ("".equals( remainder )) return; if (remainder.startsWith ("--")) return; Errors.warn( msg.content() + ": " + remainder ); } } /** Roads are one-way paths between Intersections * @author Douglas Jones * @version 2019-03-04 * @see Intersection */ class Road { private final float travelTime; // how long to get to the other end private final Intersection destination; // where does this road go private final Intersection source; // where does this road come from // Bug Need attributes of a road /** construct a new Road * @param sc the scanner used to get the attributes of this road * When called, the keyword "road" has already been scanned, * so we are ready to scan the source and destination plus other stuff. */ public Road( Scanner sc ) { final String srcName; // source intersection's name final String dstName; // destination intersection's name final String errName; srcName = ScanSupport.scanName( sc, () -> "Road ???" ); if (srcName == null) { errName = "???"; } else { errName = srcName; } dstName = ScanSupport.scanName( sc, () -> "Road " + errName + " ???" ); // names are defined here but may be null if missing source = RoadNetwork.findIntersection( srcName ); destination = RoadNetwork.findIntersection( dstName ); // it is legal to call toString now because all fields are initialized // deal with errors if ((source == null) && (srcName != null)) { Errors.warn( "" + this + " ill defined source" ); } if ((destination == null) && (dstName != null)) { Errors.warn( "" + this + " ill defined destination" ); } // deal with delay travelTime = ScanSupport.scanPosFloat( sc, () -> this.toString() ); // ScanSupport.nextLine( sc, this + " followed by junk" ); ScanSupport.nextLine( sc, () -> this + " followed by Junk" ); } public String toString() { // prepare for missing fields of a badly declared intersection String src = "???"; String dst = "???"; // find real values if ((source != null) && (source.name != null)) { src = source.name; } if ((destination != null) && (destination.name != null)) { dst = destination.name; } return "Road " + src + ' ' + dst + ' ' + travelTime; } } /** Intersections are joined by Roads * @author Douglas Jones * @version 2019-03-01 * @see Road * There are many subclasses of Intersection * @see StopLight */ abstract class Intersection { /** The name of this interseciton */ public String name; // the interesection's name or null if broken // where this intersection connects LinkedList outgoing = new LinkedList (); // Bug do we need to know what roads lead here? // Bug how about different kinds of intersections /** construct a new generic Intersection * @param sc the scanner used to get the attributes of this intersection * When called, the keyword "intersection" has already been scanned, * so we are ready to scan the additional attributes, if any. * This does not deal with the rest of the line, because each * specific kind of Intersection is assumed to do that. */ public Intersection( Scanner sc ) { name = ScanSupport.scanName( sc, () -> "Intersection ???" ); if (RoadNetwork.findIntersection( name ) != null) { Errors.warn( "Name reused for intersection " + name ); name = "reused-" + name; // Bug -- would we be better off throwing an exception here? } // Bug -- what what about other attributes? } public String toString() { if (name != null) { return "Intersection " + name; } else { return "Intersection ???"; } } } /** Stoplights are a kind of Intersection * @author Douglas Jones * @version 2019-03-01 * @see Intersection */ final class StopLight extends Intersection { /** construct a new StopLight * @param sc the scanner used to get the attributes of this StopLight * When called, the keyword "stoplight" has already been scanned, * so we are ready to scan the additional attributes, if any. */ public StopLight( Scanner sc ) { super( sc ); // Bug -- scan attributes of this stoplight // ScanSupport.nextLine( sc, this + " followed by junk" ); ScanSupport.nextLine( sc, () -> this + " followed by junk" ); } public String toString() { if (name != null) { return "Stoplight " + name; } else { return "Stoplight ???"; } } } /** Main program * @author Douglas Jones * @version 2019-02-13 * @see Road * @see Interseciton */ public class RoadNetwork { // lists of all the parts of this model private static final List intersections = new LinkedList (); private static final List roads = new LinkedList (); /** look up an intersection by name * @param n the name of the intersection, possibly null (matches nothing) * @returns the Intersection with that name, or null if no match */ public static Intersection findIntersection( String n ) { // stupid code, a linear search, but does it matter? for (Intersection i: intersections) { if (i.name.equals( n )) return i; } return null; } // build the road network by scanning a source file private static void buildNetwork( Scanner sc ) { // Bug -- what if there is no next while (sc.hasNext()) { // pick off the next part of the network description String command = sc.next(); if ("stoplight".equals( command )) { intersections.add( new StopLight( sc ) ); } else if ("road".equals( command )) { roads.add( new Road( sc ) ); } else { Errors.warn( "invalid command " + command ); } // Bug -- it would be nice to allow some kind of comments } } // print out the entire road network private static void printNetwork() { for (Intersection i: intersections) { System.out.println( i ); } for (Road r: roads) { System.out.println( r ); } } public static void main( String[] args ) { Errors.setPrefix( "RoadNetwork" ); if (args.length < 1) { Errors.fatal( "missing argument" ); } if (args.length > 1) { Errors.warn( "extra arguments" ); } try { // args[0] is the text file holding the road network, buildNetwork( new Scanner( new FileInputStream( args[0] ) ) ); printNetwork(); // something testable! } catch( FileNotFoundException e ) { Errors.fatal( "can't open file" ); } } }