# You may have to edit this file to delete header lines produced by
# mailers or news systems from this file (all lines before these shell
# comments you are currently reading).
# Shell archive made by dwjones on Thu Apr 13 21:58:14 CDT 2017
# To install this software on a UNIX system:
# 1) create a directory (e.g. with the shell command mkdir stuff)
# 2) change to that directory (e.g. with the command cd stuff),
# 3) direct the remainder of this text to sh (e.g. sh < fileHoldingThisText).
# This will make sh create files in the new directory; it will do
# nothing else (if you're paranoid, you should scan the following text
# to verify this before you follow these directions). Then read README
# in the new directory for additional instructions.
cat > README <<\xxxxxxxxxx
A discrete event simulation of a road network, written in Java
Contents:
classes -- a list of all the java class-description files in this directory
Makefile -- documents the interfile dependencies in this directory
*.java -- see classes and Makefile for more information
testRoadNetwork -- an example input file to test the simulation
To compile the code, use either:
javac @classes
or
make
To test the simulation
java RoadNetwork testRoadNetwork | more
xxxxxxxxxx
cat > classes <<\xxxxxxxxxx
Errors.java
PRNG.java
ScanSupport.java
Simulation.java
Road.java
Intersection.java
Traversable.java
NoStop.java
StopLight.java
Sink.java
Source.java
Vehicle.java
RoadNetwork.java
xxxxxxxxxx
cat > Makefile <<\xxxxxxxxxx
# Makefile documents relationships between parts of RoadNetwork application
# author Douglas Jones
# version 2017-04-10
# main program
RoadNetwork.class: RoadNetwork.java ScanSupport.class Errors.class Simulation.class PRNG.class Road.class Intersection.class
javac RoadNetwork.java
# utilities
ScanSupport.class: ScanSupport.java Errors.class
javac ScanSupport.java
Errors.class: Errors.java
javac Errors.java
Simulation.class: Simulation.java
javac Simulation.java
PRNG.class: PRNG.java
javac PRNG.java
# simulation components
Road.class: Road.java Simulation.class Errors.class ScanSupport.class
javac Road.java
Intersection.class: Intersection.java Simulation.class Errors.class ScanSupport.class PRNG.class NoStop.class StopLight.class Source.class Sink.class Vehicle.class
javac Intersection.java
Traversable.class: Traversable.java Errors.class ScanSupport.class
# in the above, we had to omit dependency on Intersection.class in
# order to avoide creating a circular dependency relationship
javac Traversable.java
NoStop.class: NoStop.java Traversable.class Errors.class ScanSupport.class
javac NoStop.java
StopLight.class: StopLight.java Traversable.class Errors.class ScanSupport.class
javac StopLight.java
Vehicle.class: Vehicle.java ScanSupport.class PRNG.class
javac Vehicle.java
# make utilities
clean:
rm -f *.class
rm -f *.html
xxxxxxxxxx
cat > Errors.java <<\xxxxxxxxxx
/* Errors.java -- error reporting support package */
/** Utility package for error handling.
*
* General purpose error reporting package for command-line applications.
* It allows reporting fatal errors and warnings to the user.
*
* @author Douglas Jones
* @version 2017-04-05
* this code is ripped from RoadNetwork.java version 2017-03-31.
*/
public class Errors {
private Errors(){}; // you may never instantiate this class
private static int count = 0; // warning count, really public read only
/** Provide public read only access to the count of warnings.
* @return the count of the non-fatal warnings
*/
public static int count() {
return count;
}
/** Warn of non fatal errors with a message on system.err
* @param message the string to output as an error message.
*/
public static void warn( String message ) {
System.err.println( "Warning: " + message );
count = count + 1;
}
/** Report fatal errors with a message on system.err
* and then exit the application.
* @param message the string to output as an error message.
*/
public static void fatal( String message ) {
System.err.println( "Fatal error: " + message );
System.exit( -1 );
}
}
xxxxxxxxxx
cat > Intersection.java <<\xxxxxxxxxx
/* Intersection.java -- abstract definition of an intersection */
import java.util.LinkedList;
import java.util.Scanner;
/** Intersections are linked by one-way roads.
* There are many subclasses of Intersection. The parent class,
* defined here, establishes a shared framework used by all subclasses.
* Only subclasses of Intersection may be instantiated.
*
* @author Douglas Jones
* @version 2017-04-10
* This code is ripped from RoadNetwork.java version 2017-03-31.
*
* @see Road
* @see Traversable
* @see NoStop
* @see StopLight
* @see Source
* @see Sink
*/
abstract class Intersection {
/** List of Road objects leading from this intersection.
* Ideally this would be public read-append only, but we use
* getter and setter methods to achieve this.
* @see outgoingSize
* @see outgoingGet
* @see addOutgoing
*/
protected final LinkedList outgoing = new LinkedList ();
/** Getter method for outgoing road list.
* Vehicles use this to "read their road map" to navigate.
* @return the size of the list.
* @see outgoing
*/
public int outgoingSize() {
return outgoing.size();
}
/** getter method for outgoing road list.
* Vehicles use this to "read their road map" to navigate.
* @return a selected element of the list.
* @see outgoing
*/
public Road outgoingGet( int element ) {
return outgoing.get( element );
}
/** setter method to inform this intersection what roads leave it.
* @param r a road that leaves this intersection.
* @see outgoing
*/
public void addOutgoing( Road r ) {
outgoing.add( r );
}
/** List of Road objects leading to this intersection.
* Ideally this would be public append only, but we use
* a setter methods to achieve this.
* Subclasses of Road need access to this so they can
* associate one queue with each incoming road.
* @see addIncoming
*/
protected final LinkedList incoming = new LinkedList ();
/** setter method, how the intersection learns what roads enter it.
* @param r a road that enters this intersection
* @returns index of the incoming road
*/
public int addIncoming( Road r ) {
incoming.add( r );
return incoming.size() - 1;
}
/** The name of the intersection.
* We wish we could declare it to be final, but it is set by the
* subclass constructors.
*/
public String name;
/** factory method to scans and processes one intersection
* definition.
* @param sc Scanner
from which the definition is read
* @return either a new Intersection
* or null
if error.
*/
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 if ("source".equals( kind )) { // source of vehicles
return new Source( sc, myName );
} else if ("sink".equals( kind )) { // sink consumes vehicles
return new Sink( sc, myName );
} else { // error
Errors.warn(
"Intersection '" + myName +
"' '" + kind +
"' not an intersection kind"
);
sc.nextLine();
}
return null;
}
/** Check this Intersection to see if it meets global sanity
* constraints. Errors are reported through calls to
* Errors.warn
.
* Currently, the only sanity constraints checked here involve making
* sure that the intersection is connected by roads.
* Subclasses of intersection may define their own sanity checks.
* @see Errors.warn
*/
public void check() {
if (incoming.isEmpty()) {
Errors.warn(
this.toString() +
": no incoming roads"
);
}
if (outgoing.isEmpty()) {
Errors.warn(
this.toString() +
": no outgoing roads"
);
}
}
/** Get the textual representation of this intersection.
* Subclasses are likely to augment this with more content.
* @return the textual representation of this intersection.
*/
public String toString() {
return(
"intersection " +
name
);
}
/********** simulation code for this intersection **********/
/** Simulation method for arrival at this intersection.
* Every subclass must define this method.
* @param time The time at which the vehicle arrives.
* @param v The vehicle that arrives.
* @param v The arrival direction, important only for intersections
* that maintain one incoming queue per incoming road.
*/
public abstract void arrivalEvent( float time, Vehicle v, int dir );
/** Simulation method for departure from this intersection.
* Every subclass must define this method.
* @param time the time at which the vehicle departs.
* @param v the vehicle that departs.
*/
public abstract void departureEvent( float time, Vehicle v );
}
xxxxxxxxxx
cat > NoStop.java <<\xxxxxxxxxx
/* NoStop.java -- implements uncontrolled intersections */
import java.util.LinkedList;
import java.util.Scanner;
/** Uncontrolled Intersections, cars pass through first-come first-served.
* Cars wait only if the intersection is currently blocked by another car.
* @author Douglas Jones
* @version 2017-04-12
* this code is ripped from RoadNetwork.java version 2017-03-31,
* with minor changes to improve the documentation.
* @see Traversable
* @see Intersection
*/
public class NoStop extends Traversable {
/** Scan and processes one uncontrolled Intersection
.
* @param sc Scanner
from which the description is read
* @param myName the value to be put in the name
* field of the new Intersection
.
*/
public NoStop( Scanner sc, String myName ) {
// now keyword for intersection type was scanned
name = myName;
getTraversalTime( sc );
ScanSupport.lineEnd( sc,
() -> "Intersection '" + name + "'"
);
}
/** Get this uncontrolled intersection's name in a form like the input
* @return the textual representation of the intersection
*/
public String toString() {
return(
super.toString() + " " +
traversalTime
);
}
/********** simulation code for this intersection **********/
// control over vehicles waiting to get through the intersection
private boolean occupied = false; // the intersection is initially open
private final LinkedList waiting = new LinkedList ();
/** Simulation method for arrival at this uncontrolled intersection.
* @param time the time at which the vehicle arrives.
* @param v the vehicle that arrives.
* @param dir arrival direction.
*/
public void arrivalEvent( float time, Vehicle v, int dir ) {
if (occupied) {
// make this vehicle wait
waiting.add( v );
} else {
// let this vehicle continue onward
Simulation.schedule(
//Bug: actual traversal could involve vehicle
time + traversalTime,
(float t)-> this.departureEvent( t, v )
);
occupied = true;
}
}
/** Simulation method for departure from this intersection.
* @param time the time at which the vehicle departs.
* @param v the vehicle that departs.
*/
public void departureEvent( float time, Vehicle v ) {
// first, make vehicle v arrive at an outgoing road now
Simulation.schedule(
time,
(float t)->v.selectRoad( this ).arrivalEvent( t, v )
);
System.out.println(
"At " + time + " vehicle " + v + " left " + name
);
// second, see if someone else can enter this intersection
if (waiting.isEmpty()) {
// if someone arrives later, they won't wait
occupied = false;
} else {
// get a waiting vehicle and let it continue
Vehicle v1 = waiting.remove();
Simulation.schedule(
//Bug: actual traversal could involve vehicle
time + traversalTime,
(float t)-> this.departureEvent( t, v1 )
);
}
}
}
xxxxxxxxxx
cat > PRNG.java <<\xxxxxxxxxx
/* PRNG.java -- pseudo-random number generator support */
import java.util.Random;
/** Pseudo-random number generator support.
*
* Java's utility class Random allows users to create unlimited numbers
* of pseudo-random number streams that are, unfortunately strongly
* correlated. Therefore, this class gives us a single stream from
* which all random numbers needed by the application are drawn.
*
* @author Douglas Jones
* @version 2017-03-31
* this code is ripped from RoadNetwork.java version 2017-03-31.
*/
public class PRNG {
private static final Random rand = new Random();
//Bug: seeding on the above is dumb
/** Draw a single integer from the pseudo-random stream.
* @param cieling the least integer greater than the output.
* @return an integer from zero (inclusive) to cieling (exclusive).
*/
public static int nextInt( int cieling ) {
return rand.nextInt( cieling );
}
//Notice: other distributions should be computed from rand here
}
xxxxxxxxxx
cat > Road.java <<\xxxxxxxxxx
/* Road.java -- simulation of one road */
import java.util.Scanner;
/** Roads link intersections.
* @author Douglas Jones
* @version 2017-04-10
* This code is ripped from RoadNetwork.java version 2017-03-31.
*
* @see Intersection
* @see Simulation
*/
public 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
private int dir; // direction of this road
// 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( dstName );
if (destination == null) {
Errors.warn(
"Road '" + srcName +
"' '" + dstName +
"' destination undefined."
);
}
travelTime = ScanSupport.nextFloat( sc );
ScanSupport.lineEnd( sc,
() -> "Road '" + srcName + "' '" + dstName + "'"
);
// do sanity checks on fields, at this point, toString is legal
if (travelTime < 0.0f) Errors.warn(
this.toString() +
": has a negative travel time?"
);
if (travelTime != travelTime) Errors.warn(
// odd condition above! True when travelTime is NaN
this.toString() +
": no travel time given."
);
// let the source and destination know about this road
if (destination != null) dir = destination.addIncoming( this );
if (source != null) source.addOutgoing( this );
}
/** check this road to see if it meets global sanity constraints
*/
public void check() {
// nothing to check.
}
/** 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
);
}
/********** simulation code for this road **********/
/** simulation method for arrival at this road
* @param time the time at which the vehicle arrives
* @param v the vehicle that arrives
*/
public void arrivalEvent( float time, Vehicle v ) {
// schedule arrival event at the next intersection
Simulation.schedule(
time + travelTime,
(float t) -> departureEvent( t, v )
);
}
/** simulation method for departure from this road
* @param time the time at which the vehicle departs
* @param v the vehicle that departs
*/
public void departureEvent( float time, Vehicle v ) {
// schedule arrival event at the next intersection
Simulation.schedule(
time,
(float t) -> destination.arrivalEvent( t, v, dir )
);
// could just call destination.arrivalEvent( time, v, dir );
}
}
xxxxxxxxxx
cat > RoadNetwork.java <<\xxxxxxxxxx
/* RoadNetwork.java -- the main class for a road network simulation */
import java.util.LinkedList;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
/** The main class that controls the reading, writing and simulation of a
* road network.
* @author Douglas Jones
* @version 2017-04-12
* this code is ripped from RoadNetwork.java version 2017-03-31
* with minor changes to the comments.
* @see Road
* @see Intersection
* @see Simulation
* @see ScanSupport
* @see Errors
* @see main
*/
public class RoadNetwork {
/** The list of all roads in the road network.
*/
private static LinkedList roads
= new LinkedList ();
/** The list of all intersections in the road network.
*/
private static LinkedList inters
= new LinkedList ();
/** Utility method to look up an intersection by name.
* Class Road
needs this to find the intersections
* linked by a road.
* Class Intersection
needs this to find if the new
* intersection has a duplicate name.
*
* @param s the name of the intersection.
* @return the Intersection
with that name
* or null
if there is no such object
*/
public static Intersection findIntersection( String s ) {
// Bug: this uses a linear search.
for ( Intersection i: inters ) {
if (i.name.equals( s )) return i;
}
return null;
}
/** read in a road network and build the lists
* roads
and inters
.
* @param sc the scanner from which input text is read.
*/
private 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
}
}
}
/** Check the sanity of the network by calling the check
* method of each component. Sanity checking outputs error messages
* through class Errors
.
*/
private static void checkNetwork() {
for ( Intersection i: inters ) {
i.check();
}
for ( Road r: roads ) {
r.check();
}
}
/** Write out a road network
* by calling the toString
method of each component.
*/
private 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 a road network and then,
* if the network does not contain errors, simulates it, and
* if it does contain errors, just writes it out.
* @param args the command line arguments.
*/
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] ) ) );
checkNetwork();
if (Errors.count() > 0) {
writeNetwork();
} else {
Simulation.run();
}
} catch (FileNotFoundException e) {
Errors.fatal( "Could not read '" + args[0] + "'" );
}
}
}
xxxxxxxxxx
cat > ScanSupport.java <<\xxxxxxxxxx
/* ScanSupport.java -- package of support methods for scanning */
import java.util.Scanner;
import java.util.regex.Pattern;
/** Support methods for scanning input files.
* @author Douglas Jones
* @version 2017-04-05
* This is ripped from RoadNetwork.java version 2017-03-31 with a change:
* nextFloat method is added based on the homework and the comments are
* somewhat improved.
* @see Errors
*/
public class ScanSupport {
/** Pattern for recognizing identifers
*/
public static final Pattern name // letter followed by alphanumeric
= Pattern.compile( "[a-zA-Z][a-zA-Z0-9_]*|" );
/** Pattern for recognizing floating point numbers
*/
public static final Pattern numb // Digits.Digits or .Digits or nothing
= Pattern.compile( "[0-9]+\\.?[0-9]*|\\.[0-9]+|" );
/** Pattern for recognzing whitespace excluding newlines
*/
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();
}
/** Get next float without skipping lines (unlike sc.nextFloat()).
* @param sc the scanner from which end of line is scanned.
* @return the name, if there was one, or NaN if not.
*/
public static Float nextFloat( 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( numb );
String f = sc.match().group();
// now convert what we can or return NaN
if (!"".equals( f )) {
return Float.parseFloat( f );
} else {
return Float.NaN;
}
}
/** Class used only for deferred evaluation of lambda expressions
* passed to lineEnd
.
*/
public interface EndMessage {
/** Method to compute the error message text
* @return the text the error message
*/
public abstract String myString();
}
/** Advance to next line and complain if there is junk at the line end;
* call this when all useful content has been consumed from the line
* it skips optional line-end comments and complains about anything
* it finds while advancing to the next line.
* @see Errors
* @see EndMessage
* @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,
* {@code 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 + "'"
);
}
}
}
xxxxxxxxxx
cat > Simulation.java <<\xxxxxxxxxx
/* Simulation.java -- discrete event simulation framework */
import java.util.PriorityQueue;
/** Discrete event simulation support framework
* @author Douglas Jones
* @version 2017-04-10
* this code is ripped from RoadNetwork.java version 2017-03-31
*/
class Simulation {
/** Interface allowing actions to be passed as lambda expressions.
*/
public interface Action {
void trigger( float time );
}
/** Events are the core of the control structure of the simulation.
*/
private static class Event {
/** Each event has a time */
float time;
/** Each event has an action */
Action act;
/** Construct and initialize a new Event
.
* @param t the time
of the event.
* @param a the act
to be triggered then.
*/
Event( float t, Action a ) {
time = t;
act = a;
};
/** Trigger the event's act
at the indicated
* time.
*/
void trigger() {
act.trigger( time );
}
}
/** Events are queued for {@code run} retrieve in chronological order.
*/
private static PriorityQueue eventSet =
new PriorityQueue (
(Event e1, Event e2)->Float.compare( e1.time, e2.time )
);
/** Users call schedule to schedule one action at some time,
* usually a later time but possibly the current time.
*
* @param time specifies when the event should occur.
* @param act specifies what to do at that time.
* Typically, {@code act} is a lambda expression,
* so a call to schedule could look like this:
*
* {@code
* Simulation.schedule( t, (float t)->object.method( params, t ) )
* }
*/
public static void schedule( float time, Action act ) {
eventSet.add( new Event( time, act ) );
}
/** the main program should build the model,
* this inolves scheduling some initial events
* and then, just once, it should call {@code run}.
*/
public static void run() {
while (!eventSet.isEmpty()) {
Event e = eventSet.remove();
e.trigger();
}
}
}
xxxxxxxxxx
cat > Sink.java <<\xxxxxxxxxx
/* Sink.java -- sink intersections consume vehicles from the road network */
import java.util.Scanner;
/** Sink Intersections destroy vehicdles and could collect statistics.
* @author Douglas Jones
* @version 2017-04-12
* ripped from RoadNetwork.java version 2017-03-31
* with minor changes to improve comments.
* @see Intersection
*/
public class Sink extends Intersection {
// attributes of the sink intersection? None!
/** Initializer scans and processes one sink intersection.
* @param sc {@code Scanner} from which to read the description.
* @param myName the value to be put in the {@code name} field.
*/
public Sink( Scanner sc, String myName ) {
// the name and keyword for the Source were already scanned
name = myName;
ScanSupport.lineEnd( sc,
() -> this.toString()
);
}
/** Output this {@code Intersection} in a format like that used for
* input.
* @return the textual representation of this object
*/
public String toString() {
return(
super.toString() + " sink"
);
}
/** Check this Sink to see if it meets global sanity constraints.
* This overrides the default check because it requires that there
* be incoming roads but forbids any outgoing roads.
*/
public void check() {
if (incoming.isEmpty()) {
Errors.warn(
this.toString() +
": no incoming roads"
);
}
if (!outgoing.isEmpty()) {
Errors.warn(
this.toString() +
": no outgoing roads allowed"
);
}
}
/********** simulation code for this intersection **********/
/** Simulate the arrival of a vehicle at this sink intersection.
* @param time the time at which the vehicle arrives.
* @param v the vehicle that arrives.
* @param dir arrival direction.
*/
public void arrivalEvent( float time, Vehicle v, int dir ) {
//nothing happens unless we add code to gather statistics
System.out.println(
"At " + time + " crushed vehicle " + v + " at " + name
);
}
/** Simulate the departure of a vehicle from this intersection.
* This never happens but we must provide a method.
* @param time the time at which the vehicle departs.
* @param v the vehicle that departs.
*/
public void departureEvent( float time, Vehicle v ) {
//Bug: No vehicles ever depart from a sink, thow something.
}
}
xxxxxxxxxx
cat > Source.java <<\xxxxxxxxxx
/* Source.java -- source intersections inject traffic into the road network */
import java.util.Scanner;
/** Source Intersections create vehicles.
* Each source intersection periodically launches new vehicles into the
* road network.
*
* @author Douglas Jones
* @version 2017-04-12
* this code is ripped from RoadNetwork.java version 2017-03-31,
* with minor changes to improve comments.
* @see Intersection
*/
public class Source extends Intersection {
/** the interval between successive arriving vehicles */
final float arrivalTime;
/** Scan and processes one source Intersection description.
* @param sc {@code Scanner} from which the description is read.
* @param myName the value to be put in the {@code name} field.
*/
public Source( Scanner sc, String myName ) {
// now keyword for Source was scanned
name = myName;
arrivalTime = ScanSupport.nextFloat( sc );
if (arrivalTime != arrivalTime) { // it is NaN
Errors.warn(
this.toString() +
"' stoplight, no interarrival time given."
);
} else if (arrivalTime < 0.0f) {
Errors.warn(
this.toString() +
"' has a negative interarrival time?"
);
}
ScanSupport.lineEnd( sc,
() -> this.toString()
);
//launch this source vehicle generation process
Simulation.schedule(
arrivalTime,
(float t)->this.departureEvent( t, new Vehicle() )
);
}
/** Get the intersection description in a form like that used for input.
* @return the textual representation of this intersection.
*/
public String toString() {
return(
super.toString() + " source " +
arrivalTime
);
}
/** Check this to see if it meets global sanity constraints.
* This overrides the default {@code check} method.
* In this case, no incoming roads are permitted
* but there must be at least one outgoing road.
*/
public void check() {
if (!incoming.isEmpty()) {
Errors.warn(
this.toString() +
": no incoming roads allowed"
);
}
if (outgoing.isEmpty()) {
Errors.warn(
this.toString() +
": no outgoing roads"
);
}
}
/********** simulation code for this intersection **********/
/** Simulate the arrival of a vehicle at this intersection.
* Our framework requires this, but no vehicles ever arrive at sources.
* @param time the time at which the vehicle arrives.
* @param v the vehicle that arrives.
* @param dir the arrival direction.
*/
public void arrivalEvent( float time, Vehicle v, int dir ) {
//Bug: No vehicles ever arrive at a source, thow something.
}
/** Simulate the departure of a vehicle from this intersection
* @param time the time at which the vehicle departs
* @param v the vehicle that departs
*/
public void departureEvent( float time, Vehicle v ) {
// first, make vehicle v arrive at an outgoing road now
Simulation.schedule(
time,
(float t)->v.selectRoad( this ).arrivalEvent( t, v )
);
// we could have done v.selectRoad( ).arrivalEvent( )
System.out.println(
"At " + time + " create vehicle " + v + " at " + name
);
// second, schedule the next departure from the source
Simulation.schedule(
time + arrivalTime,
//Bug: the above suggests a really stupid arrival model
(float t)->this.departureEvent( t, new Vehicle() )
);
}
}
xxxxxxxxxx
cat > StopLight.java <<\xxxxxxxxxx
/* StopLight.java -- traversable intersections controlled by stoplights */
import java.util.LinkedList;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
/** StopLight protected Intersections, cars pass through on green lights
* so long as the interseciton is clear. If the light is red, cars
* wait until the light changes and the intersection clears. Only one
* incoming road sees a green light at a time.
* Each stoplight has a logical process that periodically changes the state
* of the light.
*
* @author Douglas Jones
* @version 2017-03-31
* This code is ripped from RoadNetwork.java version 2017-03-31,
* with minor changes to improve the comments and a small bug fix:
* the old version let a vechicles into the intersection without marking it
* as occupied when the light changed.
* @see Intersection
* @see Traversable
*/
public class StopLight extends Traversable {
/** how long is the light green in each direction. */
private final float greenTime;
/** how long is the light yellow in each direction. */
private final float yellowTime;
/** Scan and initialize one stoplight Intersection.
* @param sc Scanner from which the description for this is read.
* @param myName the value to be put in the {@code name} field.
*/
public StopLight( Scanner sc, String myName ) {
// now keyword for StopLight was scanned
name = myName;
getTraversalTime( sc );
greenTime = ScanSupport.nextFloat( sc );
if (greenTime != greenTime) { // it is NaN
Errors.warn(
"Intersection '" + name +
"' stoplight, no green time given."
);
} else if (greenTime < 0.0f) {
Errors.warn(
"Intersection '" + name +
"' Stoplight '" + greenTime +
"' has a negative green time?"
);
}
yellowTime = ScanSupport.nextFloat( sc );
if (yellowTime != yellowTime) { // it is NaN
Errors.warn(
"Intersection '" + name +
"' stoplight, no yellow time given."
);
} else if (yellowTime < 0.0f) {
Errors.warn(
"Intersection '" + name +
"' Stoplight '" + greenTime +
"' '" + yellowTime +
"' has a negative yellow time?"
);
}
ScanSupport.lineEnd( sc,
() -> "Intersection '" + name +
"' stoplight '" + greenTime +
"' '" + yellowTime
);
// start the logical process for this stoplight
Simulation.schedule(
greenTime + yellowTime,
//Bug: the above is simplistic, isn't yellow different?
(float t) -> lightChangeEvent( t )
);
}
/** Get a description of this in a format like that used for input.
* @return the textual representation of this intersection.
*/
public String toString() {
return(
super.toString() + " stoplight " +
traversalTime + " " +
greenTime + " " +
yellowTime
);
}
/********** simulation code for this stoplight **********/
/** The green light direction. */
private int lightDir = 0;
/** Is this intersection currently occupied? */
private boolean occupied = false;
/** Queues of waiting vehicles, one for each incoming road indexed
* by road number.
*/
private LinkedList waiting[];
/** The size of the set of incoming roads.
* {@code incoming.size()} could be used, and this would be final
* except that Java does not permit final here because of the way
* it is initialized in {@code check}.
*/
private int incomingSize;
/** Check this stop light to see if it's sane and finish initialization.
* This uses the default check and then does more initialization.
*/
public void check() {
super.check(); // do the regular checks
// finish the initialization
incomingSize = incoming.size();
// create the queues, one per incoming road
// the code should read
// waiting = new LinkedList [incomingSize];
// unfortunately, that isn't allowed, so we fake it as follows:
class LinkedListVehicle extends LinkedList {}
waiting = new LinkedListVehicle [incomingSize];
for (int i = 0; i < incomingSize; i++) {
waiting[i] = new LinkedListVehicle ();
}
}
/** Simulate a vehicle arriving at this stoplight.
* @param time the time at which the vehicle arrives.
* @param v the vehicle that arrives.
* @param dir the arrival direction.
*/
public void arrivalEvent( float time, Vehicle v, int dir ) {
if ((lightDir != dir) || (occupied)) {
// make this vehicle wait
waiting[dir].add( v );
} else {
// let this vehicle continue onward
Simulation.schedule(
//Bug: actual traversal could involve vehicle
time + traversalTime,
(float t)-> this.departureEvent( t, v )
);
occupied = true;
}
}
/** Simulate a vehicle departing this stoplight.
* @param time the time at which the vehicle departs.
* @param v the vehicle that departs.
*/
public void departureEvent( float time, Vehicle v ) {
// first, make vehicle v arrive at an outgoing road now
Simulation.schedule(
time,
(float t)->v.selectRoad( this ).arrivalEvent( t, v )
);
System.out.println(
"At " + time + " vehicle " + v + " left " + name
);
// second, see if someone else can enter this intersection
if (waiting[lightDir].isEmpty()) {
// if someone arrives later, they won't wait
occupied = false;
} else {
// get a waiting vehicle and let it continue
Vehicle v1 = waiting[lightDir].remove();
Simulation.schedule(
//Bug: actual traversal could involve vehicle
time + traversalTime,
(float t)-> this.departureEvent( t, v1 )
);
}
}
/** Simulate a change of state of this stoplight.
* @param time the time at which the light changes.
*/
private void lightChangeEvent( float time ) {
// change the light
lightDir = lightDir + 1;
if (lightDir >= incomingSize) lightDir = 0;
// see if light change lets a car into the intersection
if ((!waiting[lightDir].isEmpty()) && (!occupied)) {
// get a waiting vehicle and let it continue
Vehicle v = waiting[lightDir].remove();
Simulation.schedule(
//Bug: actual traversal could involve vehicle
time + traversalTime,
(float t)-> this.departureEvent( t, v )
);
occupied = true;
}
// schedule the next change
Simulation.schedule(
time + greenTime + yellowTime,
//Bug: the above is simplistic, isn't yellow different?
(float t) -> lightChangeEvent( t )
);
}
}
xxxxxxxxxx
cat > Traversable.java <<\xxxxxxxxxx
/* Traversable.java -- behavior shared by all traversable intersections */
import java.util.Scanner;
/** all Intersections that are traversable share this common behavior.
* @author Douglas Jones
* @version 2017-04-12
* This version of the code is ripped from RoadNetwork.java version 2017-04-31
* @see Intersection
* @see NoStop
* @see StopLight
*/
public abstract class Traversable extends Intersection {
/** traversable intersections are characterized by how long it takes
* to cross the intersection
*/
protected float traversalTime;
/** Scan the traversal time of a traversable intersection.
* @param sc the input from which the time is scanned.
*/
protected void getTraversalTime( Scanner sc ) {
traversalTime = ScanSupport.nextFloat( sc );
if (traversalTime != traversalTime) { /* it is NaN */
Errors.warn(
"Intersection '" + name +
"' no traversal time given."
);
} else if (traversalTime < 0.0f) {
Errors.warn(
"Intersection '" + name +
"' '" + traversalTime +
"' has a negative traversal time?"
);
}
}
}
xxxxxxxxxx
cat > Vehicle.java <<\xxxxxxxxxx
/* Vehicle.java -- implements what little behavior vehicles exhibit */
import java.util.LinkedList;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
/** Simulated vehicles.
* @author Douglas Jones
* @version 2017-03-31
* code ripped from RoadNetwork.java version 2017-03-31,
* with small improvements to comments.
* @see PRNG
*/
public class Vehicle {
// Bug: Vehicles could have many more attributes, such as a plan or
// an intended destination.
/** Make a navigation decision.
* @param i The {@code Intersection} at which the decision is made.
* @return The {@code Road} to take away from that intersection.
*/
public Road selectRoad( Intersection i ) {
//Bug: we use the most stupid algorithm, random decision
return i.outgoingGet( PRNG.nextInt( i.outgoingSize() ) );
}
}
xxxxxxxxxx
cat > testRoadNetwork <<\xxxxxxxxxx
intersection A 0.05
intersection B stoplight 0.05 1 4
intersection C source 12
intersection D sink
road A B 1.5
road A B 1.8
road B A 0.5
road C A 0.015
road A D 0.024
xxxxxxxxxx