/**
* 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;
}
}