/* Epidemic Simulator * Author: Douglas Jones * Status: MP2 solution; it works, but with room for improvement. * Version: 9/22/2020 */ import java.util.LinkedList; import java.util.Iterator; import java.util.Scanner; import java.util.Random; import java.io.File; import java.io.FileNotFoundException; // Utility classes /** * Error handling */ class Error{ // BUG: Perhaps we should count warnings /** Report a warning to System.err * @param message the text of the warning */ public static void warn( String message ) { System.err.println( message + "\n" ); // BUG: Perhaps count warnings and error out if too many } /** Report a fatal error to System.err * @param message the text reporting the error * Note that this code exits the program with an error indication */ 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 /** Construct a MyScanner to read from a file * @param f the file to read from * @throws FileNotFoundException if the file could not be read */ 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 boolean hasNextInt() { return self.hasNextInt(); } public String next() { return self.next(); } public String next( String s ) { return self.next( s ); } public float nextFloat() { return self.nextFloat(); } public int nextInt() { return self.nextInt(); } public String nextLine() { return self.nextLine(); } // 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 match to pattern, if one is available * @param pat the pattern string we are trying to match * @param def the default value if no match available * @param msg the error message to print if no match available */ public String getNext( String pat, String def, String msg ) { if (self.hasNext( pat )) return self.next( pat ); Error.warn( msg ); return def; } /** Get the next float, if one is available * @param def the default value if no float is available * @param msg the error message to print if no float is available */ public float getNextFloat( float def, String msg ) { // BUG -- does not permit semicolon or comma at end of number if (self.hasNextFloat()) return self.nextFloat(); Error.warn( msg ); return def; } /** Get the next int, if one is available * @param def the default value if no int is available * @param msg the error message to print if no int is available */ public int getNextInt( int def, String msg ) { // BUG -- does not permit semicolon or comma at end of number if (self.hasNextInt()) return self.nextInt(); Error.warn( msg ); return def; } } // Model classes /** * People occupy places * @see Place */ class Person { // private stuff needed for instances enum States { // BUG does this go here? Is it public or private? uninfected, latent, bedridden, recovered, dead // the order of the above is significant: >= uninfected is infected } // instance variables private final Place home; public final String name; public States infectionState; // the collection of all instances private static final LinkedList allPeople = new LinkedList (); /** The only constructor * @param h the home of the newly constructed person */ public Person( Place h ) { name = super.toString(); home = h; infectionState = States.uninfected; h.addResident( this ); allPeople.add( this ); // this is the only place items are added! } /** Infect a person * called when circumstances call for a person to become infected */ public void infect() { // BUG -- what if this person is already infected? infectionState = States.latent; // BUG -- when simulation is added, will this launch a disease process? } /** Primarily for debugging * @return textual name and home of this person */ public String toString() { return name + " " + home.name + " " + infectionState; } /** Allow outsiders to iterate over all people * @return an iterator over people */ public static Iterator iterator() { return allPeople.iterator(); } } /** * Places are occupied by people * @see Person */ class Place { // instance variables public final String name; private final LinkedList residents = new LinkedList (); // the collection of all instances private static final LinkedList allPlaces = new LinkedList (); /** The only constructor for Place * Places are constructed with no occupants */ public Place() { name = super.toString(); allPlaces.add( this ); } /** Add a resident to a place * Should only be called from the person constructor * @param r a Person, the new resident */ public void addResident( Person r ) { residents.add( r ); // no need to check to see if the person already lives there? } /** Primarily for debugging * @return textual name and residents of the place */ public String toString() { String res = name; for (Person p: residents) { res = res + " " + p.name; } return res; } /** Allow outsiders to iterate over all places * @return an iterator over places */ public static Iterator iterator() { return allPlaces.iterator(); } } /** * Main class builds model and will someday simulate it * @see Person * @see Place */ public class Epidemic { // the following are set by readCommunity and used by buildCommunity // default values are used to check for failure to initialize static int pop = -1; /* the target population */ static int house = -1; /* the target household size average */ static int var = -1; /* the target household size variation */ static int infected = -1; /* the target household size variation */ /** Read and check the simulation parameters * @param sc the scanner to read the community description from * Called only from the main method. */ private static void readCommunity( MyScanner sc ) { while (sc.hasNext()) { // until the input file is finished String command = sc.next(); if ("pop".equals( command )) { if (pop > 0) Error.warn( "population already set" ); pop = sc.getNextInt( 0, "pop with no argument" ); sc.getNext( ";", "", "pop " + pop + ": missing semicolon" ); } else if ("house".equals( command )) { if (house > 0) Error.warn( "household size already set" ); if (var > 0) Error.warn( "household variance already set" ); house = sc.getNextInt( 1, "house with no argument" ); sc.getNext( ",", "", "house " + house + ": missing comma" ); var = sc.getNextInt( 1, "house " + house + " , missing argument " ); sc.getNext( ";", "", "house " + house + " , " + var + ": missing semicolon" ); } else if ("infected".equals( command )) { if (infected > 0) Error.warn( "infected already set" ); infected = sc.getNextInt( 1, "infected with no argument" ); sc.getNext( ";", "", "infected " + infected + ": missing semicolon" ); } else if ("//".equals( command )) { // comments sc.nextLine(); } else { Error.warn( "unknown command: " + command + "\n" ); } } // check for complete initialization if (pop < 0) Error.warn( "population not initialized" ); if (house < 0) Error.warn( "household size not set" ); if (var < 0) Error.warn( "household size variation not set" ); if (infected < 0) Error.warn( "infected number not given" ); if ((house - var) <= 0) Error.warn( "house - var <= 0" ); if (infected > pop) Error.warn( "infected number exceeds population" ); } /** Build a community that the simulation parameters describe * Called only from the main method. */ private static void buildCommunity() { // must always have a home available as we create people int currentHomeCapacity = 0; Place currentHome = null; // need a source of random numbers // BUG -- does this go here? Random rand = new Random(); // create the population for (int i = 0; i < pop; i++) { if (currentHomeCapacity < 1) { // must create a new home currentHome = new Place(); currentHomeCapacity = rand.nextInt( 2*var + 1 ) + house - var; } Person p = new Person( currentHome ); currentHomeCapacity = currentHomeCapacity - 1; // decide who to infect // note: pop - i = number of people not yet considered to infect // and infected = number we need to infect, always <= (pop - i) if (rand.nextInt( pop - i ) < infected) { p.infect(); infected = infected - 1; } } } /** Output the community * Called only from the main method. * This code exists only for debugging. */ private static void writeCommunity() { System.out.println( "People" ); // Note: Not required in assignment for (Iterator i = Person.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } System.out.println( "Places" ); // Note: Not required in assignment for (Iterator i = Place.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } } /** The main method * This handles the command line arguments. * If the args are OK, it calls other methods to build and test a model. */ public static void main( String[] args ) { if (args.length < 1) { Error.fatal( "Missing file name argument\n" ); } else try { readCommunity( new MyScanner( new File( args[0] ) ) ); buildCommunity(); // build what was read above writeCommunity(); // BUG -- this is just for debugging // BUG -- need to actually add simulation code here } catch ( FileNotFoundException e) { Error.fatal( "Can't open file: " + args[0] + "\n" ); } } }