Handout HW2, Part I

Contents:

This is the first part of a two-part assignment. Remember to complete Part II as well!

The purpose of this assignment is to help you set up your development environment, get reacquainted with Java, and familiarize yourself with tools you will use for the rest of the course. Although the assignment description is long, we expect that our step-by-step instructions will make doing the assignment less overwhelming than reading it may be.

You are strongly encouraged to complete this assignment first on the instructional workstations (IWS) in one of the Allen Center software labs using exactly the tools we describe here.

If you would like to get more practice with Java, then we recommend walking through Oracle's Java tutorials described under Problem 7. Try to complete Homework 2 first so you can use the tools we describe here when doing the examples in Oracle's tutorial.

You are free to get help from anyone, on any portion of this problem set; that is, the standard collaboration policy does not apply to this homework. You will need to know this material for the remainder of the quarter, however, so make sure you understand the tools and the concepts.

If you are having trouble with finishing this assignment on time, get in touch with the staff immediately so we can try to get you back on track. Here are some steps to take if you get stuck on something in this (or any) homework:

  1. check the FAQs and corrections for the homework
  2. check the Catalyst forum to see if someone else has posted a similar question
  3. and then, ask the staff for help.

Introduction

In this part of the assignment you will edit, run and test Java code using your development environment as well as Subversion (SVN) and JUnit (real tools used commonly in both industry and academia).

Problem 1: Choosing and Setting-Up a Development Environment

You are free to choose your own Java development environment. We strongly recommend using Eclipse for CSE 331 homeworks; we also provide instructions for working from the Linux command line. We focus on the use of the CSE instructional computers. If you plan to work on your own laptop or home machine, we don't promise help for individual configuration issues. Of course, you can use your own machines to access the instructional computers remotely via SSH or other methods.

Once you have chosen a development environment, you should set it up for CSE 331 use. See Starting the environment for instructions on how to do this. (Note: if you switch development environments later on in the quarter, make sure to run the appropriate setup instructions again.)

Important: Eclipse is a widely-used Java development environment with a number of excellent features including integration with SVN, Ant, and JUnit — tools that we use in CSE 331. However, instead of using the official Java compiler ("javac") from Oracle/Sun, Eclipse uses an Eclipse-specific Java compiler. Bugs in either compiler are rare, but they do manifest on rare occasions, causing the same code to have different behavior with different compilers. Course staff will compile and grade your code using the javac compiler, so you'll need to make sure that your code behaves correctly when compiled with javac. One way to verify this is to use the Ant build file we provide for each homework. See the Compiling Java Source Files document for details.

Refer to the Eclipse quick reference to learn about some of the handy features Eclipse provides.

Problem 2: Obtaining files from SVN

In Homework 1, you followed the instructions in the Tools: Version Control handout to checkout a "working copy" of the cse331 repository. To obtain the starter files for this assignment, follow the instructions to "update" your working copy.

Problem 3: Warm-Up Exercise—HolaWorld

For this problem, you will fix some buggy code we provide.

Editing and Compiling Source Files

See Editing and Compiling Source Files. This part will help you become familiar with how to perform the following basic tasks in your environment of choice: adding new files to your directory structure, compiling Java code, and reading the Java compiler's output (which may indicate errors).

Also see the CSE 331 Java Style Guide. That will familiarize you with standards of Java style, which we will expect you to follow in this class.

Fixing HolaWorld

Try to compile the provided code in HolaWorld.java. You should be notified of compilation errors in the file. (And possibly in hw2/test/RandomHelloTest.java as well; if so, ignore these for now since we will fix them in the next part.) In particular, the lines:

        System.out.println(world.);

and:

        return spanishGree;

are problematic. (If you're using the the Ant builder, you may only see the second error after you've fixed the first one.) If you are using Eclipse, these errors will be marked with red squiggly lines in HolaWorld.java, and HolaWorld.java itself will be marked with a red crossmark in the Package Explorer.

Fix these errors and run HolaWorld.

After you've fixed the errors and run the code, it would be a good time to commit your changes to SVN.

Problem 4: Your first Java class—RandomHello

Create your first Java class with a main method that will randomly choose, and then print to the console, one of five possible greetings that you define.

Create the file RandomHello.java, which will define a Java class named RandomHello that will reside in the Java package hw2; that is, its file name is YourWorkspaceDirectory/cse331/src/hw2/RandomHello.java.

Java requires every runnable class to contain a main method whose signature is public static void main(String[] args) (for example, the main methods in HelloWorld and in HolaWorld).

A code skeleton for the RandomHello class is shown below. Eclipse will generate some of this skeleton for you when you create the new RandomHello class.

RandomHello.java:

package hw2;

/**
 * RandomHello selects a random greeting to display to the user.
 */
public class RandomHello {

    /**
     * Uses a RandomHello object to print
     * a random greeting to the console.
     */
    public static void main(String[] argv) {
        RandomHello randomHello = new RandomHello();
        System.out.println(randomHello.getGreeting());
    }

    /**
     * @return a random greeting from a list of five different greetings.
     */
    public String getGreeting() {
        // YOUR CODE GOES HERE
    }
}

This skeleton is meant only to serve as a starting point; you are free to organize it as you see fit.

No Need to Reinvent the Wheel

Don't write your own random number generator to decide which greeting to select. Instead, take advantage of Java's Random class. (This is a good example of the adage "Know and Use the Libraries" as described in Chapter 7 of Joshua Bloch's Effective Java. Learning the libraries will take some time, but it's worth it!)

Type the following into the body of your getGreeting() method:

Random randomGenerator = new Random();

This line creates a random number generator. (Not a random number, which comes later, but a Java object that can generate random numbers._ In Eclipse, your code may be marked as an error by a red underline. This is because the Random class is defined in a package that has not yet been imported (java.lang and hw2 are the only packages that are implicitly imported). Java libraries are organized as packages and you can only access Java classes in packages that are imported. To import java.util.Random, add the following line under the line package hw2; at the top of your file (after the package hw2; declaration):

import java.util.Random;

This will import the class Random into your file. To automatically add all necessary imports and remove unused imports, Eclipse lets you type CTRL-SHIFT-O to Organize your imports. Because there is only one class named Random, Eclipse will figure out that you mean to import java.util.Random and will add the above line of code automatically. (If the name of the class that needs to be imported is ambiguous — for example, there is a java.util.List as well as a java.awt.List — then Eclipse will prompt you to choose the one to import.)

Using java.util.Random

Read the documentation for Random's nextInt(int n) method by going to the Java API and selecting Random from the list of classes in the left-hand frame. Many classes also allow you to pull up documentation directly in Eclipse. Just hover over the class or method name and press SHIFT+F2.

Use the nextInt(int n) method to choose your greeting. You don't have to understand all the details of its behavior specification, only that it returns a random number from 0 to n-1.

One way to choose a random greeting is using an array. This approach might look something like:

String[] greetings = new String[5];
greetings[0] = "Hello World";
greetings[1] = "Hola Mundo";
greetings[2] = "Bonjour Monde";
greetings[3] = "Hallo Welt";
greetings[4] = "Ciao Mondo";

The main method in the skeleton code above prints the value returned by getGreeting. So if you insert code in getGreeting to select a greeting randomly, when the class is run it will will print that greeting.

When you are finished writing your code and it compiles, run it several times to ensure that all five greetings can be displayed.

Again, now it would be a good idea to add your new class to version control and commit your code.

Problem 5: Testing Java Code with JUnit

Screenshot: Run Junit from Eclipse

Part of your job as a software engineer is to verify that the software you produce works according to its specification. One form of verification is testing. JUnit is a framework for creating unit tests in Java. A unit test is a test for verifying that a given method in a class conforms to its specification. In this problem, we will provide you with a quick overview and simple example of how JUnit works. (The next homework will look more deeply into unit testing.)

Open both hw2/Fibonacci.java and hw2/test/FibonacciTest.java. From the comments, you can see that FibonacciTest is a test of the Fibonacci class.

Now run the JUnit test hw2.test.FibonacciTest.

A window or panel with a menacing red bar will appear, indicating that some of the tests in FibonacciTest did not complete successfully (see screenshot at right). The top pane displays the list of tests that failed, while the bottom pane shows the Failure Trace for the highlighted test. The first line in the Failure Trace should display an error message that explains why the test failed (it is the responsibility of the author of the test code to produce this error message).

As shown in the figure above, if you click on the failure testThrowsIllegalArgumentException(), the bottom pane will switch to the appropriate error message. In this example, the first line of the failure trace shows that Fibonacci.java improperly threw a IllegalArgumentException when tested with zero (0) as its argument. (You may have to scroll the pane to the right to see this). If you double-click on the name of a test in the top pane, Eclipse will jump to the line where the failure occurred in the editor pane. Figure out the problem in Fibonacci.java, fix it, and rerun the JUnit test. Eclipse will automatically rebuild when you make changes, but if you are running Junit from the command line, you must manually rebuild (compile) Fibonacci.java before you rerun JUnit. This can be done by rerunning the ant command (which also compiles the files) or by following the compiling instructions and then clicking the "Run" button.

Use the information in the Failure Trace box to help you continue debugging Fibonacci. Keep a record of what you did to debug Fibonacci as you will have to answer questions about your debugging experience in the next problem. After you have fixed all the problems in Fibonacci, you should see a bright green bar instead of a red one when you run FibonacciTest.

Now look at the JUnit tests that we wrote for HolaWorld and RandomHello in Problem 5 earlier. They are called HolaWorldTest and RandomHelloTest, respectively. Ensure that your modified code passes these tests before you turn in your homework.

Problem 6: Answering Questions About the Code

In a newly created text file, answer some questions about the Fibonacci class. Most design projects that you will be assigned will require you to submit some sort of response or write-up in addition to your code. Follow these instructions to create a text file, named hw2/answers/problem6.txt, with answers to the following questions:

  1. Why did Fibonacci fail the testThrowsIllegalArgumentException test? What did you have to do to fix it?
  2. Why did Fibonacci fail the testBaseCase test? What (if anything) did you have to do to fix it?
  3. Why did Fibonacci fail the testInductiveCase test? What (if anything) did you have to do to fix it?

Problem 7: Getting a Real Taste of Java—Balls and Boxes

Until now, we have only been introducing tools. In this problem, we will delve into a real programming exercise. If you are not familiar with Java, we recommend working through the Optional Problems and Oracle's Learning the Java Language tutorial. (Skip the section on generics for now.) Fragments of Oracle's other tutorials may also be useful, specifically "Getting Started", "Essential Java Classes", and "Collections".

This problem is intended to give you a better sense of what Java programming entails. This problem will likely be somewhat challenging for most of you. Don't be discouraged, we're here to help. And we expect that time spent now will pay off significantly during the rest of the course.

As you work on this problem, record your answers to the various questions in problem7.txt in the project's hw2/answers/ directory.

  1. Warm-Up: Creating a Ball:

    Take a look at Ball.java. A Ball is a simple object that has a volume.

    We have included a JUnit test called BallTest.java to help you out. If you are using Eclipse, one of its warnings should help you find at least one of the bugs without referring to the JUnit results.

  2. Using Pre-Defined Data Structures:

    Next, we want to create an class called BallContainer. As before, skeleton code is provided (see BallContainer.java). A BallContainer is a container for Balls. BallContainer must support the following methods and your task is to fill in the code that will implement all these methods correctly:

    1. add(Ball)
    2. remove(Ball)
    3. getVolume()
    4. size()
    5. clear()
    6. contains(Ball)
    The specifications for these methods are found in the javadoc file for BallContainer.

    In BallContainer, we use a java.util.Set to keep track of the balls. This is a great example of using a predefined Java data-structure to save yourself significant work.

    Before implementing each method, read the documentation for Set. Some of your methods will be as simple as calling the appropriate predefined methods for Set. To help you out, we have included a JUnit test called BallContainerTest.java.

    Before you start coding, please take time to think about the following question (which you need to answer in the text file):

    There are two obvious approaches for implementing getVolume():
    1. Every time getVolume() is called, go through all the Balls in the Set and add up the volumes. (Hint: one solution might use a for-each loop to extract Balls from the Set.)
    2. Keep track of the total volume of the Balls in BallContainer whenever Balls are added and removed. This eliminates the need to perform any computations when getVolume is called.

    Which approach do you think is the better one? Why?

  3. Implementing Algorithms:

    In this problem, you will do a little more design and thinking and a little less coding. You will implement the Box class. A Box is also a container for Balls. The key difference between a Box and a BallContainer is that a Box has only finite volume. Once a box is full, we cannot put in more Balls. The size (volume) of a Box is defined when the constructor is called:

    public Box(double volume);
    

    Since a Box is in many ways similar to a BallContainer, we internally keep track of many things in the Box with a BallContainer, allowing us to reuse code. Many of the methods in Box can simply "delegate" to the equivalent in BallContainer; for example, removing from a Box cannot cause it to exceed its volume limit. This design of having one class contain an object of another class and reusing many of the latter class's methods is called composition.

    (Optional Note: If you are familiar with Java, you may wonder why we did not simply make Box extend BallContainer via "inheritance"; that is, why did we not make Box a subclass of BallContainer. We will discuss this much more deeply later in the course, but the key idea is that Box is not what we call a true subtype of BoxContainer because it is in fact more limited than BallContainer (a Box can only hold a limited amount); hence, a user who uses a BallContainer in his code can not simply substitute that BallContainer with a Box and assume the same behavior in his program. (The code may cause the Box to fill up, but he did not have this concern when using a BallContainer). For this reason, it is not a good idea to make Box extend BallContainer.)

    In addition to the constructor described above, you will need to implement the following new methods in Box:

    1. add(Ball)
    2. getBallsFromSmallest()

    The specifications for these methods is found in the javadoc file for Box.

    You shouldn't need to change your implementation of BallContainer or Ball for this problem. In particular, you should not make it implement the Comparable interface. If you are tempted to do so, then note that most interfaces that take Comparable have a companion interface that takes Comparator. For example, consider, in java.util.Collections java.util.Collections, the two sort methods.

    If you make any changes to BallContainer or Ball for this problem, then explicitly document what changes you made and why in problem7.txt. Oh yeah, don't forget to commit your code more than occasionally.

    The JUnit test BoxTest.java should help you out. Before you start working on getBallsFromSmallest(), we recommend strongly that you consider using Iterator.

    Also, take some time to answer the following questions in your text file:

    1. There are many ways to implement getBallsFromSmallest(). Briefly describe them at least two ways.
    2. Which of the above ways do you think is the best? Why?

    There is no one correct answer. Our intent is to help you fight that urge to code up the first thing that comes to mind. Remember: More thinking, less coding.

Problem 8: Turning In Your Homework

You will turn in the solutions for the problem sets, including this one, by checking them into your SVN repository. All the relevant files should be included, for example:

Each homework will indicate exactly what you need to turn in a Section entitled “What to Turn In”.

If you add a new file, you must explicitly add it to your SVN repository. This means that you need to tell SVN to "track this file" as part of the repository. In addition, you need to commit the file to actually put the contents in the repository. Make sure that, when you're done, your working copy is committed to the repository so that we get and grade your most up-to-date version.

Since files like RandomHello.java, problem6.txt and problem7.txt are not in your SVN repository, you must add them. You also want to commit the changes you made to HolaWorld.java, Fibonacci.java,Ball.java, BallContainer.java, and Box.java.

After completing the above steps, all your code and materials to turn in should be in your SVN repository. The final step to turn in is to validate your solutions. The validate script performs general sanity checks on your solutions. In particular, it checks out a fresh copy of the module from your SVN repository into a temporary directory, checks that it has the required files, compiles the Java files, and tests your code against our suite of public tests (see here for more information about this testing framework, and about the test classes hw2.test.SpecificationTests and hw2.test.ImplementationTests, which you do NOT have to modify for this homework).

To validate, execute the validate target in your Ant buildfile (build.xml). This does not work well with Eclipse's integrated Ant support, so even if you are using Eclipse as your development environment, you should validate on the command-line, by running the following on attu:

cd ~/workspace331/cse331/src/hw2/
ant validate

You can do this via SSH from any machine. (Even if you are working on an Allen Center Linux machine you still need to SSH into attu.) If you are working at home you will first need to check out a copy of your code on attu using the command line. Note that if you check out your working copy in the location suggested there, the path to your project (as listed in the directions above and in the ouput below) will not include the workspace331/ directory.

If validation was successful, you should see output that looks something like:

Buildfile: /homes/iws/username/workspace331/cse331/src/hw2/build.xml

validate:

     [echo] Validate checks out a fresh copy of the hw, checks for the
     [echo]       presence of required files, and runs all your tests to make sure
     [echo]       they pass.  This target is meant to be called from attu; don't
     [echo]       trust it to make sure everything works unless you are on attu.
     [echo]       Note: the test reports will be generated under the scratch
     [echo]       directory the validate target creates.
     [echo]
   [delete] Deleting directory /homes/iws/username/workspace331/cse331/scratch
    [mkdir] Created dir: /homes/iws/username/workspace331/cse331/scratch
     [echo] /projects/instr/10sp/cse331/username/REPOS
     [exec] A    cse331
     [exec] A    cse331/.classpath
     [exec] A    cse331/.project

     ...

     [exec] Buildfile: /homes/iws/username/workspace331/cse331/scratch/cse331/src/hw2/build.xml
     [exec]
     [exec] cleancopy:
     [exec]      [echo] hw directory: /homes/iws/username/workspace331/cse331/scratch/cse331/src/hw2
     [exec]

     ...

     [exec] BUILD SUCCESSFUL
     [exec] Total time: 2 seconds
     [exec] Buildfile: /homes/iws/username/workspace331/cse331/scratch/cse331/src/hw2/build.xml
     [exec]
     [exec] build:
     [exec]
     [exec] single.build:
     [exec]      [echo] This hw is independent; building only this one.
     [exec]     [javac] Compiling 23 source files to /homes/iws/username/workspace331/cse331/scratch/cse331/bin
     [exec]
     [exec] multi.build:
     [exec]
     [exec] test.impl:
     [exec]     [mkdir] Created dir: /homes/iws/username/workspace331/cse331/scratch/cse331/src/hw2/test/reports
     [exec]     [junit] Running hw2.test.ImplementationTests
     [exec]     [junit] Tests run: 0, Failures: 0, Errors: 0, Time elapsed: 0.004 sec
     [exec]
     [exec] test.spec:
     [exec]     [junit] Running hw2.test.SpecificationTests
     [exec]     [junit] Tests run: 24, Failures: 0, Errors: 0, Time elapsed: 0.181 sec
     [exec]
     [exec] test:
     [exec]      [echo] Records of this testing can be found in /homes/iws/username/workspace331/cse331/scratch/cse331/src/hw2/test/reports/
     [exec]
     [exec] test.strict:
     [exec]
     [exec] BUILD SUCCESSFUL
     [exec] Total time: 4 seconds
     [delete] Deleting directory /homes/iws/username/workspace331/cse331/scratch

BUILD SUCCESSFUL
Total time: 9 seconds

If there is an error, the validate script should provide some information about what is wrong:

Buildfile: /homes/iws/username/workspace331/cse331/src/hw2/build.xml

validate:
     [echo] Validate checks out a fresh copy of the hw, checks for the
     [echo]       presence of required files, and runs all your tests to make sure
     [echo]       they pass.  This target is meant to be called from attu; don't
     [echo]       trust it to make sure everything works unless you are on attu.
     [echo]       Note: the test reports will be generated under the scratch
     [echo]       directory the validate target creates.
     [echo]
...
     [exec] cleancopy.check:
     [exec]      [echo] Found required file BallContainer.java
     [exec]
     [exec] cleancopy.check:
     [exec]      [echo] Found required file Box.java
     [exec]
     [exec] cleancopy.check:
     [exec]      [echo] Found required file answers/problem6.txt
     [exec]
     [exec] cleancopy.check:
     [exec]
     [exec] BUILD FAILED
     [exec] /homes/iws/username/workspace331/cse331/scratch/cse331/src/common.xml:81: The following error occurred while executing this line:
     [exec]
     /homes/iws/username/workspace331/cse331/scratch/cse331/src/common.xml:96:
     Could not find required file: answers/problem7.txt
     [exec]
     [exec] Total time: 2 seconds

BUILD FAILED
/homes/iws/username/workspace331/cse331/src/common.xml:129: exec returned: 1

This error would indicate that a required file, answers/problem7.txt is missing. Make sure you've committed this file to SVN.

If validate failed because the test suite failed, you can view a summary of the JUnit failures in your YourWorkspaceDirectory/cse331/scratch/src/hw2/test/reports/ directory.

Validate your homework as many times as necessary until there are no errors. You have now successfully turned in your first CSE 331 homework. We encourage you to give the Optional Tutorial Problems a try.

What to Turn In

Your TA should be able to find the following when he or she checks your src directory of SVN:

Please include your first and last name in every text file you turn in (i.e., files ending with .txt).

Optional Tutorial Problems

If you have not had much experience with Java, we recommend that you do these extra Optional Problems (source code for these problems can be found in the hw2.optional package that came with your hw2 checkout). If you do not know Java, CSE 331 will require you to learn it quite quickly. The optional problems, although not required, will help you become more comfortable with Java.

Optional Debugger Tutorial

In this part, you will learn to Eclipse's built-in debugger. As the name suggests, a debugger helps you debug your program by allowing you to "watch" your program as it executes, one line at a time, and inspect the state of variables along the way. In the past, you may have used System.out.println() to probe the state of a variable or find out whether a particular line is reached. In that sense, debuggers are like an ultra-super-powerful System.out.println().

One of the most useful features of a debugger is the ability to set breakpoints in your program. When you run your program through the debugger, it will pause when it reaches a line with a breakpoint. You can inspect the current state of variables, then continue running your program normally or step through one line at a time to watch the variables change.

Hints

We strongly encourage you to use the CSE 331 Online Forum.

Errata

In Problem 7, the two references to problem8.txt should have been problem7.txt.

Q & A

This section will list clarifications and answers to common questions about homeworks. We'll try to keep it as up-to-date as possible, so this should be the first place to look (after carefully rereading the homework handout and the specifications) when you have a problem.