/** * WorldCreator.java * * @author David Tran * @version 3.0 */ package world; import java.io.BufferedReader; import java.io.IOException; import java.io.FileReader; import java.util.StringTokenizer; import java.util.zip.DataFormatException; import java.io.FileNotFoundException; import java.io.File; import javax.swing.JFileChooser; import java.net.URL; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import background.*; /** * A WorldCreator creates a World with particular Background objects * whose description and location are given in a specified file, * where each line of data is of the following format:

* * backgroundName xCoordinate yCoordinate width height

* * where backgroundName is the predetermined name refering to some background * (e.g. Vertical-Road, Lake, House)
* where xCoordinate refers to the leftmost x-coordinate of the background
* where yCoordinate refers to the uppermost y-coordinate of the background
* where width refers to the maximum horizontal distance from the xCoordinate * of the background object
* where height refers to the maximum vertical distance from the yCoordinate * of the background object

*/ public class WorldCreator { /** Name of a file to use if a good file name is not supplied as a parameter. *Generally the best place to put this file is in the same package (directory) *as the class which is processing it; in this case, the same directory as *WorldCreator.java. */ public static final String defaultFilename = "World.txt"; /** * Modifies a World with Background objects taken from a file. * * @return - World instance of a World object, with the modifed background objects. */ public static World modifyWorld(World w) { return modifyWorld(null, w); } /** * Modifies a currently existing World by adding Background objects * specified in the fileName. * If the world does not exist (i.e. w is null), a new World is created * and returned, in which the Background objects will be added. * If the file has bad data, a null World is returned. * * @param fileName the name of the file describing the background objects; * if the fileName is null or empty or if the file cannot be found, * a built-in default is tried; if that fails, * the user sees a File Chooser dialog and can browse to the desired file. * @param w the currently existing World * @return World - instance of a World object if file was read successfully, * otherwise null */ public static World modifyWorld(String fileName, World w) { //The starter code in the method can be removed for Step 1. //For Step 2, you should put it back in. if (w == null) { w = new World(); } if (fileName == null || fileName.equals("")) { fileName = defaultFilename; } java.io.File f = getFile(fileName); //make sure there is such a file //At this point, if f is not null, then the file exists. String fullAbsolutePath = null; if (f != null) { fullAbsolutePath = f.getAbsolutePath(); assert fullAbsolutePath != null; System.out.println("Using background text file " + fullAbsolutePath); } else{ System.out.println("Tried and tried, but STILL could not open a file," + " not even " + fileName); } //If fullAbsolutePath is not null, you should be able to create a stream on it. //You might also use f directly for creating the stream. //Starter code for the method ends here... try { // Read a line of text from a character-input stream, buffering // characters so as to provide for efficient reading BufferedReader input = new BufferedReader( new FileReader(f) ); // Continue reading lines of text from the file as long as we have not // we have not reached the end of file (EOF), in which case readLine() // returns null // For each valid line of data, parse the string and create the background String str = input.readLine(); int lineNumber = 1; while (str != null) { try { /*** statically load background classes using this method ***/ createBackgroundUsingWorld(str, w); /*** OR, dynamically load background classes using this method (commented out for now) ***/ //dynamicallyCreateBackgroundUsingWorld(str, w); } catch (DataFormatException dfe) { System.out.println("Came across invalid data format on line " + lineNumber + "..." + dfe.getMessage()); } catch (NumberFormatException nfe) { System.out.println("Came across invalid number format on line " + lineNumber + "; skipping line with data: " + str); }finally { // Read the next line, no matter what happens str = input.readLine(); lineNumber++; } } // Close input stream input.close(); return w; } catch (FileNotFoundException fNFE) { System.out.println("Error reading file; file not found"); return null; } catch (IOException e) { System.out.println("An IOException occured"); e.printStackTrace(); return null; } } /** * Helper method which creates a background and adds it to the * specified world. DOES NOT USE DYNAMIC LOADING OF CLASSES. * Precondition: lineOfFile is not null and there are no empty lines in files * * Reminder for correct format of background: * A WorldCreator creates a World with particular Background objects * by reading a file where each line of data is of the following format: * * backgroundName xCoordinate yCoordinate width height * *. */ private static boolean createBackgroundUsingWorld(String lineOfFile, World w) throws DataFormatException, NumberFormatException { assert lineOfFile != null; StringTokenizer st = new StringTokenizer(lineOfFile); if (st.countTokens() != 5 ) { throw new DataFormatException("Expected 5 tokens per line, found " + st.countTokens() + " for the line: " + lineOfFile); } Background bg = null; String backgroundName = st.nextToken(); double xCoor = Double.parseDouble(st.nextToken()); double yCoor = Double.parseDouble(st.nextToken()); double width = Double.parseDouble(st.nextToken()); double height = Double.parseDouble(st.nextToken()); // Determine which background to add to the world, then add it. if(backgroundName.equals("Vertical-Road")) { bg = new VerticalRoad(xCoor, yCoor, width, height); } else if(backgroundName.equals("Horizontal-Road")) { bg = new HorizontalRoad(xCoor, yCoor, width, height); } else if(backgroundName.equals("Tree")) { bg = new Tree(xCoor, yCoor, width, height); } else if(backgroundName.equals("House")) { bg = new House(xCoor, yCoor, width, height); } else if(backgroundName.equals("Lake")) { bg = new Lake(xCoor, yCoor, width, height); } else if(backgroundName.equals("Baseball-Field")) { bg = new BaseballField(xCoor, yCoor, width, height); } else if(backgroundName.equals("Intersection")) { bg = new Intersection(xCoor, yCoor, width, height); } else { throw new DataFormatException("Invalid background object specified: " + backgroundName); } assert bg.getName().equalsIgnoreCase(backgroundName) : "Parsed background name " + backgroundName + " doesn't equal internal getName value " + bg.getName(); w.addWorldObject(bg); return true; } /** * Helper method which creates a background and adds it to the * specified world. USES DYNAMIC LOADING OF CLASSES. * Precondition: lineOfFile is not null and there are no empty lines in files * * Reminder for correct format of background: * A WorldCreator creates a World with particular Background objects * by reading a file where each line of data is of the following format: * * backgroundName xCoordinate yCoordinate width height. * * NOTE: Since some backgrounds names in the file contain a dashed line, * in this implementation dashed lines are simply removed from the String. */ private static boolean dynamicallyCreateBackgroundUsingWorld(String lineOfFile, World w) throws DataFormatException, NumberFormatException { assert lineOfFile != null; StringTokenizer st = new StringTokenizer(lineOfFile); if (st.countTokens() != 5 ) { throw new DataFormatException("Expected 5 tokens per line, found " + st.countTokens() + " for the line: " + lineOfFile); } String backgroundName = st.nextToken(); backgroundName = backgroundName.replaceAll("-", ""); try { // Dynamically load the proper background class using the background name (with the package specified) Class backgroundClass = Class.forName("background."+backgroundName); // Set up the parameter types for the specific constructor we want to call (e.g. 4 doubles) Class[] parameterTypes = new Class[4]; for (int i = 0; i < 4; i++) { parameterTypes[i] = Double.TYPE; } // Set up/store the arguments for the constructor in an array. Double[] arguments = new Double[4]; arguments[0] = new Double(st.nextToken()); arguments[1] = new Double(st.nextToken()); arguments[2] = new Double(st.nextToken()); arguments[3] = new Double(st.nextToken()); // Dynamically call the constructor for the class using the arguments we set up // and construct the object Constructor backgroundConstructor = backgroundClass.getConstructor(parameterTypes); Background bg = (Background) backgroundConstructor.newInstance(arguments); w.addWorldObject(bg); } catch (ClassNotFoundException cnfe) { throw new DataFormatException("Invalid background object specified: " + lineOfFile); } catch (NoSuchMethodException nsme) { // Invalid constructor/method specified throw new DataFormatException(); } catch (InstantiationException ie) { throw new DataFormatException("Could not create a new instance of the background object."); } catch (IllegalAccessException ie) { throw new DataFormatException("Could not create background object because access to the background is not allowed."); } catch (InvocationTargetException ite) { throw new DataFormatException("Could not create background object because access to the background is not allowed."); } catch (IllegalArgumentException iae) { throw new DataFormatException("Could not create background object with the specified numbers."); } catch (ExceptionInInitializerError eiie) { throw new DataFormatException("Could not create background object because an exception was thrown during creation."); } return true; } /** * Displays a dialog box to choose the file, if the requested one doesn't exist. * @return the file that the user selected or null if user selected cancel */ private static File getFile () { return getFile(null); } /** Locate the requested file, if possible, or a file * selected by the user. * For best results, place the file in the same directory as this class, * or in some other package used by the application. * Displays a dialog box to choose the file, if the one requested doesn't exist * * @return the file that was requested, or one the user selected, or null if user selected cancel */ private static File getFile(String requestedFilename) { String startingPath; if (requestedFilename != null) { //see if the file exists java.io.File requestedFile = new File(requestedFilename); if (requestedFile.exists() && requestedFile.isFile()) { return requestedFile; } //The file wasn't found via its original requested name, in the default location. //Look for it on the class path java.net.URL fileURL = ClassLoader.getSystemResource(requestedFilename); if (fileURL != null) { //found it on the class path String fullName = fileURL.getPath(); requestedFile = new File(fullName); assert requestedFile.exists(); if (requestedFile.isFile()) { return requestedFile; } } } //Let user browse for it, starting from the directory of the current class Class wcc = WorldCreator.class; String myClassName = wcc.getName(); String myClassFilename = myClassName.replace('.', '/'); myClassFilename += ".class"; java.net.URL classURL = ClassLoader.getSystemResource(myClassFilename); if (classURL != null) { startingPath = classURL.getPath(); } else { startingPath = "."; //last resort } JFileChooser fc = new JFileChooser(startingPath); int retVal = fc.showOpenDialog(null); fc.requestFocus(); if (retVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); assert file != null && file.exists(); if (file.isFile()) { return file; } } //totally out of luck return null; } }