Computer Science 252
Problem Solving with Java
Spring 2014, The College of Saint Rose
For this lab assignment, you will complete the implementation of a simple drawing program. You gain experience working with ArrayLists, and will get a refresher on reading and writing files from Java and get some practice with String methods.
You may work alone or in a group of 2 or 3 on this lab. Only one submission per group is needed.
Please follow the step-by-step instructions to complete this lab.
There are lab questions a programming assignment in this lab. Please refer to the "Submission Guidelines" on the course home page and syllabus for the requirements for each of these items.
Setting Up and Understanding the Starter Code
To get started, download and extract the files from this zip file into your folder for today's lab.
You should have a BlueJ project with 3 classes. You will be modifying all of these classes as you complete the lab.
You will notice some very messy-looking code in the TerminalDraw class. You do not need to modify this part of the program and really don't need to understand the details. But for those interested, this code in the main method:
new TerminalDraw().startController(600,600);
sets up the canvas and calls the begin method. This is what BlueJ normally does for us when we choose "Run Controller" from the menu.
It then does goes into a loop to keep the main method active until the command-line processing thread has finished. This is not something we would normally need to do, but BlueJ was not designed for the kind of program we are running this week and needs this messy workaround to function properly. Those working in other IDEs might need to modify this setup procedure.
Since we want to start execution of our program by executing the main method, we need to choose the "void main(String[] args)" menu option instead of "Start Controller" to run correctly. The "if (startedMain)" line in the begin method is a check to make sure the program was invoked correctly. If not, you will see a message in the terminal window and on the canvas.
Start up the program by invoking the main method and make sure it works properly for you before continuing.
If the program was started properly, you can see that the begin method does two things: it creates a DrawingManager and a CommandLine.
The given DrawingManager class doesn't do much yet. So far it consists of only a constructor. We'll add plenty more here later.
The given CommandLine class does what it needs to do to be a successful ActiveObject. Our main focus will be on the run method, which is responsible for reading in commands in the terminal window and calling appropriate methods of the DrawingManager to process them. So far it only knows how to process the "quit" command, but you will add several others as we move through the lab.
We habe not used terminal keyboard input much this semester, but this is all likely familiar from your previous course work. In this case we are creating a Scanner which takes its input from System.in (the keyboard via the terminal window). In the loop, we read a command from the keyboard with cmd.nextLine(), which will return to us a String that contains whatever was typed in on a single line up input.
Completing the Program
We will now add functionality to your program, one feature at a time.
Adding new graphical objects
We will first work on the addition of new graphical objects. This will be accomplished with a command like:
add FilledRect 100 100 50 50
which will, as you might guess, create a new FilledRect at (100,100) with size 50 ×50. To keep things manageable, we will restrict our program to be able to add 4 of the basic graphical objects: FilledRect, FilledOval, FramedRect, and FramedOval.
Several steps will be needed to make this possible.
The first step is to be able to read the command in the CommandLine class. We need to add a new entry to what will grow to be a long sequence of "if...else if..." statements that will check for the valid commands.
If we were to match the "quit" command, we would have something like:
else if (command.equals("add")) { // do stuff to add the new item }
This won't work - the command won't be equal to "add" - it will just start with the word "add". Fortunately, Java's String class has just what we need: the startsWith method. As the name implies, the startsWith method returns a boolean that indicates whether a String starts with another String.
In this case:
if (command.startsWith("add")) { // do stuff to add the new item }
Add this to your program and run it make sure any command that starts with "add" is now recognized by your program (i.e., it doesn't print the "Unknown command" message).
Next, we need to make this command do something. Before this can happen, the CommandLine class needs to know about the DrawingManager class. We want all of the graphical objects to be created and managed by the DrawingManager class. As it stands, the DrawingManager exists only in the TerminalDraw class. We need to have it in the run method in the CommandLine class. We have done this in many contexts before: add a parameter to the CommandLine constructor to pass in the DrawingManager and save it in an instance variables so we have access to it where we need it in the run method.
Now that our run method has access to the DrawingManager, we can need to implement a method of DrawingManager that creates a and remembers new graphical object, and we need to call that method from our run method. Let's call this new method add.
The add method will need to take as its only parameter the String that describes which type of graphical object and the 4 numbers that describe the coordinates and dimensions of the object as a parameter.
So let's add such a method to the DrawingManager class. For now, just add the method signature - don't worry yet about how to implement it, just place a printout in the method that prints the message "Processing add:" followed by the String it is passed.
And then place a call to the add method in your run method where you are processing the "add" command. Pass in the entire command for now as a parameter. Compile and run your program.
It turns out there's no need to include the word "add" in the String you pass as the parameter to the add method. All we really need is the rest of that String. Again, Java's String class provides us with exactly what we need - the substring method. Read about the substring method in Java documentation about Strings or in the text and replace the parameter you pass to the add method with one that consists only of the part of the command that follows the "add" and the space that follows it.
Now we need to finish up the add method. To do this, we need to break our String passed as a parameter up into the 5 parts: the object type, the x and y coordinates, the width, and the height. There are many ways we can do this in Java. We will do this by making use of the Scanner class again. If we construct a Scanner by passing in a String instead of System.in, it will use that String instead of the keyboard as its input:
Scanner s = new Scanner(descr);
if "descr" is the name of the parameter you passed.
Now, we can use the Scanner's methods to pull out the name and numbers. The next method will give us the next String on the Scanner's input up to but not including the next white space (spaces, tabs, new lines). The nextDouble method will return the next double value on the Scanner's input (if there is one). So with one String and four numbers expected in the Scanner's input. So add appropriate calls to these methods to place these values into local variables.
We are finally ready to be able to construct the graphical objects on the canvas (which has been saved by the DrawingManager's constructor in an instance variable called c).
So we need to decide, based on the String we obtained from the Scanner's next method, which graphical object type we are supposed to construct. Then we can construct that type of object using the values we obtained from the Scanner's nextDouble method calls. Be sure to add a case where you print an error message if the graphical object type is not one of the four our program will support. However, we will not add checks to make sure our numbers are valid. If we input invalid numbers, our program will crash. (Yes, this is bad, but there's enough to worry about already in this program to keep us busy for this week.)
Add this code and try it out. You should now be able to add rectangles and ovals to your canvas. How exciting!
One more detail here about the Scanner. When we are finished reading from a Scanner we should call its close method so the system knows we will no longer be attempting to read from it, so it can be cleaned up. Add this to your method after the last call to nextDouble.
However, we have not yet remembered these graphical objects to be able to look at them or modify them later. We could put these into an array, but we have covered ArrayLists first, so let's put them into an ArrayList. But ArrayLists need to contain only objects of the same type, and we are going to be creating four different graphical object types! One possible solution is to create not just one ArrayList to store all of our graphics objects, but four ArrayLists, one for all of the instances of each type of object. The latter is not very desirable for a lot of reasons. So we will make use of a Java construct we saw earlier: the interface, which lets us treat objects that are closely related to each other (by which we mean that they are guaranteed to have a specific set of methods defined) as the same common data type, so long as we agree to restrict our usage of those objects to calling methods from the set which they all share. bjectDraw provides for us an interface called Drawable2DInterface that can be used to define names for objects that can be any one of several ObjectDraw types, including the 4 we have in this program. Any object that meets the Drawable2DInterface interface is guaranteed to have the standard methods we've come to know and love like getX, getY, getHeight, getWidth, contains, removeFromCanvas, hide, show, setColor, move, and moveTo, among others.
So what all this means for us is that we can declare and construct an ArrayList whose elements are Drawable2DInterface. We can then add all of our graphical objects to that ArrayList. This is all we need to do for now, to make this enhancement to the add method.
Listing graphical objects
We next will add a "listing" capability to our program.
The first step here is to return to the run method in CommandLine and add another entry to our if..else if block that checks for the command "list". In this case, we require exactly that command and nothing else on the command line, so we can use .equals to look for it. Add this case and run your program to make sure it does not give you the "Unknown command" message when you enter the "list" command.
Next, we'll make it do something. Add a method list that takes no parameters to your DrawingManager and an appropriate call to that method inside the new if statement you just added.
This list method should loop through each element in the ArrayList and print out some information about each. If we have a FilledRect and a FramedOval on the canvas, the output should look something like this:
0: FilledRect 50.0 50.0 50.0 50.0 1: FramedOval 25.0 25.0 100.0 100.0
The first number is the position within the ArrayList, followed by the object type, then its coordinates and dimensions.
Most of this isn't too bad. We can write a for loop that counts from 0 to the ArrayList's size-1, as we have many times before. However, when we get each element from the ArrayList to print its information, we don't know which of our 4 graphical object types it is. So we have to store it in a variable of type Drawable2DInterface. And as long as we only call methods common to all types that meet that interface, we will not have any problems.
The tricky part is to get the name of the type of the graphical object. It turns out Java lets us ask an instance of any class exactly this. If we have a reference to some object in a variable called "object", we can use
object.getClass().getName()
to get back a String that tells us the actual type of the object (in our case, one of the 4 graphical object types as we constructed them in add) instead of the name of the interface.
Write a loop that prints out something similar to the output specified above.
If all went well in the previous step, you are probably getting the names of the graphical objects as something like "objectdraw.FilledRect" instead of simply "FilledRect". This isn't quite what we want, so let's update the program so it uses the substring method to print only that part of the object name after the "objectdraw." (which you may safely assume will always be part of the name returned by getName).
Deleting graphical objects
Next up - deleting objects. We will delete them based on the number associated with each from the "list" command (which conveniently enough corresponds to its index in our ArrayList). So a correct delete command will look something like "delete 2".
Add a new case in the run method that will look for a command that starts with "delete" and then calls a new method of the DrawingManager called delete that takes as its only parameter the String entered after the "delete" command.
In the delete method, we will need to convert the String parameter to an int that can be used as the index into our ArrayList. We could once again create a Scanner with that String as its input and call the nextInt method of the Scanner but there's a simpler way in this case. Any time we have a String that we know (or at least expect) contains something that looks like an integer and want to convert it to its int equivalent, we can use the method Integer.parseInt.
So your delete method should convert its String parameter to an int, make sure that int is a valid index into the ArrayList (i.e., that its value is in the range 0 to size()-1) and then it should remove that object from the canvas and from the ArrayList.
(Getting there...)
Reading a file of graphical objects
Any useful drawing program is going to need the ability to read and write files that describe drawings. We'll do reading first.
A load command will look like this: "load filename", where "filename" is the name of some text file in the same folder as your program that contains lines like:
FilledRect 50 50 50 50 FramedOval 25 25 100 100
This will be implemented in a new method loadFromFile that takes the file name as its only parameter. Add the appropriate code to the run method to handle the "load" command which calls loadFromFile. Then implement the loadFromFile method (first as an empty stub). When you have gotten this far, compile and run your program to make sure it can call your new method.
The implementation of this method will be surprisingly simple, although it will use a couple of constructs we haven't seen before and another in a different way than previously.
First, we need to get access to the file. There are a number of ways to do this, but we will use a mechanism that is hopefully starting to feel familiar: the Scanner. But instead of constructing our Scanner to read in from the keyboard or from a String, we will instruct it to read its input from a file. If our file name is specified in a String called fileName, this line will construct such a Scanner:
Scanner inFile = new Scanner(new File(fileName));
Add the above (or something very similar) to your loadFromFile method. Note that you will need to add some import statements. File is in java.io.File.
Assuming the answer to the question above is correct, you need to deal with some exception handling. Once we start dealing with file input and output, there are a great many things that can go wrong. Perhaps we try to open a file that doesn't exist, or we don't have proper permissions to open the file. In those cases, Java provides a very powerful mechanism that will allow us to handle such errors. We will just scratch the surface of this idea. Basically, if something goes wrong when we create our Scanner or try to read from it, Java will "throw an exception". In some cases, we ignore these exceptions. In your previous experience with File I/O, you might have added the "throws IOException" to the method signature of your main method. This means you will allow such errors to cause your program to terminate. In other cases, they cause our programs to crash (as you all know well from the dreaded NullPointerException or IllegalStateException or ArrayIndexOutOfBoundsException).
In our program, we will simply "catch" the exception and print out an error message based on the exception we caught. Don't worry too much about the details - you'll see plenty more of this when you move on to more advanced courses. For us, it just means that all of the code for our loadFromFile method needs to be in a try..catch block. It will look like this:
try { Scanner inFile = new Scanner(new File(fileName)); // do our reading from the Scanner here } catch (Exception e) { System.err.println(e); }
What this says to Java is that we would like to execute the statements inside the try block, but we know that they might throw an exception. If so, we want to "catch" that exception with the catch block and execute the code inside that block in response. In this case, we're just printing out the exception that Java threw us, and hopefully it will print something useful and meaningful like "file not found" or "permission denied" (and it will).
OK, so now we have to fill in that comment where we read in the contents of the file through the Scanner. The basic form of this part of the code will be:
while (inFile.hasNextLine()) { String line = inFile.nextLine(); // process the line we just read }
This says that as long as there is another line in the file being processed by the Scanner, we will read in that line and do something with it.
Add the above loop inside your try block below the Scanner construction. We just need to decide what it means to process the line. Well, a line in the input file looks a lot like another line we saw earlier. It's the same format of a String that we send to the add method when processing the "add" command. So let's just reuse that add method! Pass it the String read in on each line of the file and watch the magic happen!
There is one more bit of housekeeping we should attend to here. Remember to close your Scanner when you're done with it.
Writing a file of graphical objects
We would also like to be able to save a description of our drawing in a file so we can load it back in later.
A save command will look similar to load: "save filename", where "filename" is the name of some text file that will be created in the same folder as your program. As with the files we load, the files we save should contain lines like:
FilledRect 50 50 50 50 FramedOval 25 25 100 100
This will be implemented in a new method saveToFile that takes the file name as its only parameter. Add the appropriate code to the run method to handle the "save" command which calls saveToFile. Then implement the saveToFile method (first as an empty stub). When you have gotten this far, compile and run your program to make sure it can call your new method.
This method will look a lot like the list method: you will loop through the objects in the ArrayList and print out a text representation of each. The only difference is that we do not want to print to the terminal window (System.out), we want to print into a file.
To do this, we need to create a PrintWriter. This is a construct that we can use just like System.out - in particular we can call its println method - except that it prints to a file instead of the terminal.
The construction of a PrintWriter that will write to a file named fileName looks like this:
PrintWriter w = new PrintWriter(new File(fileName));
This will need to be wrapped in a try..catch block just like the one we used for our Scanner, and you will need an appropriate import statement. The PrintWriter class is in java.io.PrintWriter.
Once created, we can call w.println just like we call System.out.println to add lines to the file.
When finished, be sure to call the PrintWriter's close method so the operating system knows you are done adding to the file and it can be made available on the disk.
Try it out. Draw some graphical objects, save them to a file, restart your program and make sure you are seeing the same drawing.
Adding a dragging capability
Your final task is to add a dragging capability to the objects on the canvas (and in your ArrayList). In many ways, this is similar to what we have been doing for our labs all semester. It doesn't matter which object you choose to select for dragging when more than one object contains the mouse press point. (So do whatever's easiest.)
You are quite familiar with implementing dragging operations by now, so it's up to you to figure out most of what goes in the TerminalDraw class to support this. But there is one method you'll need to add to the DrawingManager to determine which object contains a given point.
Create a method named objectThatContains in DrawingManager. It should take a Location as its parameter and will return a Drawable2DInterface which is either a reference to a graphical object on the canvas that contains the point or null if no such object is found.
Once you think this method is working, use it in your onMousePress method to decide which, if any, object you will need to move around in subsequent onMouseDrag and onMouseRelease calls.
When your dragging is working, you're finally done.
Extra Credit Opportunities
There are many, many possible extensions to this program that would be worthy of some extra credit. Up to 10 points is available. You might consider these possibilities and of course you can come up with your own:
Submission
Before 11:59 PM, Thursday, April 17, 2014, submit your lab for grading. There are four things you need to do to complete the submission: (i) Copy your file with the answers to the lab questions into your project directory. Be sure to use the correct file name. If you prepared your answers in Word, export to a PDF file and submit that. (ii) Upload a copy of your lab (a .7z or .zip file containing your project directory) using Submission Box under assignment "Lab8". (iii) Demonstrate the execution of your programs for your instructor. (iv) Hand a printout of the Java files that make up the programming assignment to your instructor. (2 business day grace period for demos and printouts).
Grading
This assignment is worth 50 points, which are distributed as follows:
> Feature | Value | Score |
Questions | ||
Answers to lab questions | 4 | |
Style | ||
Appropriate comments | 3 | |
Good variable names | 2 | |
Appropriate variable declarations | 3 | |
Appropriate formatting | 1 | |
Appropriate use of language constructs | 2 | |
Program Correctness | ||
add command | 7 | |
list command | 6 | |
delete command | 5 | |
load command | 6 | |
save command | 6 | |
dragging | 5 | |
Extra Credit (up to 10 total) | ||
Total | 50 | |