Second Summer 2003

In which we survey Chapters 1, 2, 3, and 4, in the BlueJ book.


There are two parts to the BlueJ book:
A. Foundations of Object Orientation.

B. Application Structures.

Here's a brief run-down of the first part (Foundations):
1. Objects and Classes

2. Understanding Class Definitions

3. Object Interaction

4. Grouping Objects

5. More Sophisticated Behaviour

6. Well-Behaved Objects

7. Designing Classes

And here's the scoop on the second part (Applications):
8. Improving Structure with Inheritance

9. More About Inheritance

10. Further Abstraction Techniques

11. Handling Errors

12. Designing Applications

13. A Case-Study

14. Wrapping Things Up

So let's get started with an example.

Our first look at Java programming was through a Penguin.

The Penguin was described very much like the Robot below:

class Robot {
    String direction;
    int x, y;
    String name;
    Robot(String n, int x, int y, String dir) {
	name = n; this.x = x; this.y = y; direction = dir; 
    } 
    void moveForward() {
	System.out.println("Robot " + name + " now moves forward."); 
	if (direction.equals("North")) {
	    y -= 1; 
	} else if (direction.equals("South")) {
	    y += 1; 
	} else if (direction.equals("East")) {
	    x += 1; 
	} else if (direction.equals("West")) {
	    x -= 1; 
	} else {
	    // nothing 
	}
    } 
    void turnLeft() {
	System.out.println("Robot " + name + " now turns left."); 
	if (direction.equals("North")) {
	    direction = "West"; 
	} else if (direction.equals("South")) {
	    direction = "East"; 	 
	} else if (direction.equals("East")) {
	    direction = "North"; 	 
	} else if (direction.equals("West")) {
	    direction = "South"; 	 
	} else {
	    // nothing 
	}
    }
    int getX() { return x; }
    int getY() { return y; }
    String direction() { return direction; } 
    void report() {
	System.out.println( "Robot " + name + " located at (" + x + 
			    ", " + y + ") facing " + direction ); 
    } 
}
So here's an example of using this class:
class Walk {
    public static void main(String[] args) {
	Robot a = new Robot("Alice", 2, 3, "North"); 
	a.report(); 
	Robot q = new Robot("Queen", -4, -1, "West"); 
	q.report(); 
	a.turnLeft(); 
	a.report(); 
	a.moveForward(); 
	a.report(); 
	a.turnLeft(); 
	a.report(); 
	a.moveForward();
	a.report(); 
	a.moveForward();
	a.report();  
	a.moveForward();
	a.report(); 
	q.moveForward();
 	q.report(); 
	q.turnLeft(); 
 	q.report(); 
    } 
}
To summarize we can say: Objects are created from class definitions that have been written in a particular programming language. Much of programming in Java is about learning to write class definitions. To learn to develop Java programs, we need to learn how to write class definitions, including fields and methods, and how to put these classes together well. The rest of the course will try to provide you with plenty opportunities to experiment and guidance to discover a style of writing efficient and elegant Java programs. Let me now provide an additional summary of the concepts that should be clear at this point (1. Objects and Classes).

Java objects model objects from a problem domain. Objects are created from classes. The class describes the kind of object; the objects represent individual instantiations of the class. (Classes can also be used as containers of static methods and fields). Constructors are initialization procedures that appear to belong to the class but work on individual objects as they are being created (which is a one-time only process).

We can communicate with objects by invoking methods on them. Objects usually do something if we invoke a method. Methods can have parameters to provide additional information for a task. To a certain extent there's no difference between an object and a class as long we are only concerned with this container like of aspect. But of course, the main point is that a class is to be used as a blueprint, and a factory of objects.

Methods can have parameters to provide additional information for a task. The header of a method is called its signature. It provides information needed to invoke that method. Parameters have types. The type defines what kinds of values a parameter can take. Objects can communicate by calling each other's methods. Methods may return information about an object via a return value.

Many similar objects can be created from a single class. Objects have state. The state is represented by storing values in fields. Take for example a BankAccount object. Let's look at another example below, the well-known Triangle object.

class Experiment {
  public static void main(String[] args) {
      Triangle a = new Triangle(new Point(0, 3), 
				new Point(4, 0), 
				Point.origin); 
      System.out.println(a.area()); // should be 6.0 
  }
}

class Triangle {
    Line a, b, c; 
    Triangle (Point a, Point b, Point c) {
	this.a = new Line(a, b); 
	this.b = new Line(a, c); 
	this.c = new Line(b, c); 
    }
    double area() {
	double s = (this.a.length() + this.b.length() + this.c.length()) / 2; 
	return Math.sqrt(s * (s - a.length()) * 
                             (s - b.length()) * 
                             (s - c.length())); 
    }
}

class Line {
    Point a, b;
    double length() {
	return a.distanceTo(b);
    }
    Line(Point u1, Point u2) {
	this.a = u1;
	this.b = u2;
    }
}

class Point {
    double x, y;
    Point(double x, double y) { this.x = x; this.y = y; }
    Point() { this(0, 0); }
    static Point origin = new Point(); 
    double distanceTo(Point other) {
	double dx = this.x - other.x;
	double dy = this.y - other.y;
	return Math.sqrt(dx * dx + dy * dy);
    }
}
In general we write classes to describe objects, and then create objects. Objects (typically) have methods that we use to communicate with them. We can use a method to make a change to the object or get some information from the object. Methods have parameters and parameters have types. Methods have return types, which specify what type of data they return. If the return type is void, they do not return anything.

Objects store data in fields (which also have types). All the data values of an together are referred to as the object's state. Objects can be parameters to (static or instance) methods.

Ancillaries as we wrap this Chapter 1:

Just kidding. Keep reading. We move now to the second chapter.

Actually, hang on. This just in:

class BetterRobot extends Robot {
    void turnRight() {
      this.turnLeft(); 
      this.turnLeft(); 
      this.turnLeft(); 
    }
}
This is just a pointer into (or for) the near future.

2. Understanding Class Definitions

Fields, constructors and methods: that's all there is to it.

I bet you already know these things, but anyway.

Fields store data for an object to use. Fields are also known as instance variables. (Again we look at objects as containers. From this point of view there's little difference between objects and classes, since classes can also be containers, as mentioned before).

Constructors allow each object to be set up properly when it is first created. The scope of a variable defines the section of source code from where the variable can be accessed (by its first name). The lifetime of a variable describes how long the variable continues to exist before it is destroyed. Assignment statements store the value represented by the right-hand side of the statement in the variable named on the left. Methods consist of two parts: a header and a body. The body is a block of statements. Blocks can be used by themselves and are an unbiquitous concept in Java.

Mutator methods change the state of an object. Accessor methods return information about the state of the object. These are just conventions. The idea behind mutators and accessors is that direct access to an instance or class variable cannot be read-only. So we regulate access by closing direct access to variables (making them private) and setting up a protocol of access (using a method) that allows us to check the actions as we perform them. The System.out.println method prints its parameters to the terminal.

A conditional statement takes one of two possible actions based upon the result of a test. Each one of the actions to be taken can involve additional conditional statements, so the result could be very complex. (See the leap year problem discussed in class). Boolean expressions have only two possible values: true and false. They are commonly found controlling the choice between the two paths through a conditional statement. A local variable is a variable declared and used within a single method. Its scope and lifetime are limited to that of the method.

The code presented thus far covers all concepts discussed. So it's time to move on to

3. Object Interaction

Practice Notes Three and Four have been posted. They used to be actual Homework Assignments in the past. They are currently used for practice and solutions to all the problems listed there are available (just ask).

Abstraction is the ability to ignore details of parts to focus attention on a higher level of a problem. Modularization is the process of dividing a whole into well-defined parts, which can be built and examined separately, and which interact in well-defined ways. Classes define types (user-defined types, or reference types, they are called). A class name can be used as the type for a variable. Variables that have a class as their type can store objects of that class. The class diagram shows the classes of an application and the relationships between them. It gives information about the source code. It presents the static view of the program. The object diagram shows the objects and their relationships at one moment in time during the execution of an application. It presents the dynamic view of a program. Object reference: variables of object types (user-defined, reference types) store references to objects. The primitive types in Java are the non-object types: whole numbers, floating-point numbers, characters and booleans. (Strings are not primitive types. int's are). Primitive types have no methods.

Objects can create other objects as we have seen already, using the new operator. Overloading: a class can contain more than one constructor. That simply means that the customer can place an order for the same type of object in several different ways. Again, this is something we have seen already. Methods can call other methods of the same class, as part of their implementation. This is called internal method call. Methods can call methods of other objects also, using the dot notation. This is called external method call. Every internal method call can be represented as an external method call, and that's the approach we take in this course, at least at this stage in the course.

Here's an exercise: define a class Clock(Display) to match the sample usage illustrated below.

frilled.cs.indiana.edu%cat Experiment.java
class Experiment {
    public static void main(String[] args) {
	ClockDisplay a = new ClockDisplay(10, 26); 
	a.timeTick(); 
        a.timeTick();
	a.timeTick(); 
	ClockDisplay b = new ClockDisplay(); 
	b.setTime(9, 59); 
	b.timeTick(); 
	b.timeTick(); 
	b.timeTick(); 
	a.setTime(23, 57); 
	a.timeTick(); 
	a.timeTick(); 
	a.timeTick(); 
	a.timeTick(); 
	a.timeTick(); 
	a.timeTick(); 
	b.timeTick(); 
	a.timeTick(); 
	b.timeTick(); 
	a.timeTick(); 
	b.timeTick(); 
	a.timeTick(); 
	b.timeTick(); 
    }
}
frilled.cs.indiana.edu%javac Experiment.java
frilled.cs.indiana.edu%java Experiment
A clock with id (000) is being created. Request is for 10:26
Time is set on clock with id (000). Current time was: null it becomes now: 10:26
One more minute has passed for clock with id (000). It now shows: 10:27
One more minute has passed for clock with id (000). It now shows: 10:28
One more minute has passed for clock with id (000). It now shows: 10:29
A clock with id (001) has been created with time: unspecified.
Time is set on clock with id (001). Current time was: 00:00 it becomes now: 09:59
One more minute has passed for clock with id (001). It now shows: 10:00
One more minute has passed for clock with id (001). It now shows: 10:01
One more minute has passed for clock with id (001). It now shows: 10:02
Time is set on clock with id (000). Current time was: 10:29 it becomes now: 23:57
One more minute has passed for clock with id (000). It now shows: 23:58
One more minute has passed for clock with id (000). It now shows: 23:59
One more minute has passed for clock with id (000). It now shows: 00:00
One more minute has passed for clock with id (000). It now shows: 00:01
One more minute has passed for clock with id (000). It now shows: 00:02
One more minute has passed for clock with id (000). It now shows: 00:03
One more minute has passed for clock with id (001). It now shows: 10:03
One more minute has passed for clock with id (000). It now shows: 00:04
One more minute has passed for clock with id (001). It now shows: 10:04
One more minute has passed for clock with id (000). It now shows: 00:05
One more minute has passed for clock with id (001). It now shows: 10:05
One more minute has passed for clock with id (000). It now shows: 00:06
One more minute has passed for clock with id (001). It now shows: 10:06
frilled.cs.indiana.edu%
This will be your Homework Three, in fact.

After this example we can move on to

4. Grouping Objects

Before we get into this let's clarify that we still have a lab for today.

What we'll be doing in the remaining part of these notes will be to:

The basic idea here is to group objects in flexible-size collections.

Everything can be modelled with objects, collections too. We will be looking at one such object (by the name of ArrayList) that is defined in the package java.util (where other useful classes are defined too). Collection objects are objects that can store an arbitrary number of other objects. This is too general, you will soon see that there's more to what we just said (we wrote) but for that we need to look at an example.

The first example is the Notebook example.

Let's model a Notebook.

A Notebook is something that can store short notes.

A Note is essentially a String.

What can a Notebook do for you?

What buttons should it have (for you to push?).

Here are some ideas:

Let's look at the code:

import java.util.ArrayList;

/**
 * A class to maintain an arbitrarily long list of notes.
 * Notes are numbered for external reference by a human user.
 * In this version, note numbers start at 0.
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.06.08
 */
public class Notebook
{
    // Storage for an arbitrary number of notes.
    private ArrayList notes;

    /**
     * Perform any initialization that is required for the
     * notebook.
     */
    public Notebook()
    {
        notes = new ArrayList();
    }

    /**
     * Store a new note into the notebook.
     * @param note The note to be stored.
     */
    public void storeNote(String note)
    {
        notes.add(note);
    }

    /**
     * @return The number of notes currently in the notebook.
     */
    public int numberOfNotes()
    {
        return notes.size();
    }

    /**
     * Remove a note from the notebook if it exists.
     * @param noteNumber The number of the note to be removed.
     */
    public void removeNote(int noteNumber)
    {
        if(noteNumber < 0) {
            // This is not a valid note number, so do nothing.
        }
        else if(noteNumber < numberOfNotes()) {
            // This is a valid note number.
            notes.remove(noteNumber);
        }
        else {
            // This is not a valid note number, so do nothing.
        }
    }

    /**
     * Show a note.
     * @param noteNumber The number of the note to be shown.
     */
    public void showNote(int noteNumber)
    {
        if(noteNumber < 0) {
            // This is not a valid note number, so do nothing.
        }
        else if(noteNumber < numberOfNotes()) {
            // This is a valid note number, so we can print it.
            System.out.println(notes.get(noteNumber));
        }
        else {
            // This is not a valid note number, so do nothing.
        }
    }

    /**
     * List all notes in the notebook.
     */
    public void listNotes()
    {
        int index = 0;
        while(index < notes.size()) {
            System.out.println(notes.get(index));
            index++;
        }
    }
}
OK now the question is: can we use this code?

I am going to write a main method for that.

Here's the program and its output:

frilled.cs.indiana.edu%cat ExpNotebook.java
class ExpNotebook {
    public static void main(String[] args) {
	Notebook a = new Notebook(); 
	a.storeNote("Note One"); 
	a.storeNote("Note Two"); 
	System.out.println("The Notebook contains " + a.numberOfNotes() + 
			   " notes.\n-----"); 
	a.storeNote("Note Three");
	a.storeNote("Note Four"); 
	System.out.println("The Notebook contains " + a.numberOfNotes() + 
			   " notes.\n-----"); 
        a.showNote(1); 
        a.showNote(3); 
        System.out.println("Entire notebook before removeNote(1):"); 
	a.listNotes(); 
	a.removeNote(1); 
        System.out.println("Entire notebook after removeNote(1):"); 
	a.listNotes(); 
        System.out.println("The Notebook contains " + a.numberOfNotes() + 
			   " notes.\n-----"); 
    }
}
frilled.cs.indiana.edu%java ExpNotebook
The Notebook contains 2 notes.
-----
The Notebook contains 4 notes.
-----
Note Two
Note Four
Entire notebook before removeNote(1):
Note One
Note Two
Note Three
Note Four
Entire notebook after removeNote(1):
Note One
Note Three
Note Four
The Notebook contains 3 notes.
-----
frilled.cs.indiana.edu%
The question now is this: do you see something weird in the output?

What is it? Explain.

You should now be able to read up to the end of chapter 4 in the BlueJ book.

Assignment: check the Collections Framework Tutorial (right here).

The Auction system.

It contains for classes:

Our purpose below will be to give you an example of use.

Here's the code:

/*************
 Auction.java
**************/
import java.util.*;

/**
 * A simple model of an auction.
 * The auction maintains a list of lots of arbitrary length.
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.06.08
 */
public class Auction
{
    // The list of Lots in this auction.
    private ArrayList lots;
    // The number that will be given to the next lot entered
    // into this auction.
    private int nextLotNumber;

    /**
     * Create a new auction.
     */
    public Auction()
    {
        lots = new ArrayList();
        nextLotNumber = 1;
    }

    /**
     * Enter a new lot into the auction.
     * Lots can only by entered into the auction by an
     * Auction object.
     * @param description A description of the lot.
     */
    public void enterLot(String description)
    {
        lots.add(new Lot(nextLotNumber, description));
        nextLotNumber++;
    }

    /**
     * Show the full list of lot numbers and lot descriptions in
     * this auction. Include any details of the highest bids.
     */
    public void showLots()
    {
        Iterator it = lots.iterator();
        while(it.hasNext()) {
            Lot lot = (Lot) it.next();
            System.out.println(lot.getNumber() + ": " +
                               lot.getDescription());
            // Include any details of a highest bid.
            Bid highestBid = lot.getHighestBid();
            if(highestBid != null) {
                System.out.println("    Bid: " + 
                                   highestBid.getValue());
            }
            else {
                System.out.println("    (No bid)");
            }
        }
    }

    /**
     * Return the lot with the given number. Return null
     * if a lot with this number does not exist.
     * @param number The number of the lot to return.
     */
    public Lot getLot(int number)
    {
        if((number >= 1) && (number < nextLotNumber)) {
            // The number seems to be reasonable.
            Lot selectedLot = (Lot) lots.get(number-1);
            // Include a confidence check to be sure we have the
            // right lot.
            if(selectedLot.getNumber() != number) {
                System.out.println("Internal error: " +
                                   "Wrong lot returned. " +
                                   "Number: " + number);
            }
            return selectedLot;
        }
        else {
            System.out.println("Lot number: " + number +
                               " does not exist.");
            return null;
        }
    }
}
/*************
   Bid.java
**************/
/**
 * A class that models an auction bid. The bid contains a reference
 * to the Lot bid for and the user making the bid.
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.05.31
 */
public class Bid
{
    // The user making the bid.
    private final Person bidder;
    // The value of the bid. This could be a large number so
    // the long type has been used.
    private final long value;

    /**
     * Create a bid.
     * @param bidder Who is bidding for the lot.
     * @param value The value of the bid.
     */
    public Bid(Person bidder, long value)
    {
        this.bidder = bidder;
        this.value = value;
    }

    /**
     * @return The bidder.
     */
    public Person getBidder()
    {
        return bidder;
    }

    /**
     * @return The value of the bid.
     */
    public long getValue()
    {
        return value;
    }
}
/*************
   Lot.java
**************/
/**
 * A class to model an item (or set of items) in an
 * auction: a lot.
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.06.08
 */
public class Lot
{
    // A unique identifying number.
    private final int number;
    // A description of the lot.
    private String description;
    // The current highest bid for this lot.
    private Bid highestBid;

    /**
     * Construct a Lot, setting its number and description.
     * @param number The lot number.
     * @param description A description of this lot.
     */
    public Lot(int number, String description)
    {
        this.number = number;
        this.description = description;
    }

    /**
     * Attempt to bid for this lot. A successful bid
     * must have a value higher than any existing bid.
     * @param bidder Who is bidding.
     * @param value The value of the bid.
     */
    public void bidFor(Person bidder, long value)
    {
        // We trust that lot is genuine. There is nothing to
        // prevent a spurious lot from being bid for, but it
        // would not appear in the auction list.
        if((highestBid == null) ||
               (highestBid.getValue() < value)) {
            // This bid is the best so far.
            setHighestBid(new Bid(bidder, value));
        }
        else {
            System.out.println("Lot number: " + getNumber() +
                               " (" + getDescription() + ")" +
                               " already has a bid of: " +
                               highestBid.getValue());
        }
    }

    /**
     * @return The lot's number.
     */
    public int getNumber()
    {
        return number;
    }

    /**
     * @return The lot's description.
     */
    public String getDescription()
    {
        return description;
    }

    /**
     * @return The highest bid for this lot. This could be null if
     *         there are no current bids.
     */
    public Bid getHighestBid()
    {
        return highestBid;
    }

    /**
     * @param highestBid The new highest bid.
     */
    private void setHighestBid(Bid highestBid)
    {
        this.highestBid = highestBid;
    }
}
/*************
  Person.java
**************/
/**
 * Maintain details of someone who participates in an auction.
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.05.31
 */
public class Person
{
    // The name of this user.
    private final String name;

    /**
     * Create a new user with the given name.
     * @param name The user's name.
     */
    public Person(String name)
    {
        this.name = name;
    }

    /**
     * @return The user's name.
     */
    public String getName()
    {
        return name;
    }
}
The question now is this: can you put together a sample main?

Or, equivalently, can you explain these classes if I show you a main?

Once you run javadoc on it you get this.

From now on this would be the standard way of summarizing what a a class can do.

So can we put together a main in an AucExperiment to illustrate (or test) our understanding?

The answer is a very arguable: yes.

The argument follows.

class AucExperiment {
    public static void main(String[] args) {
	// first I create a few people 
	Person larry = new Person("Larry Bird"); 
	Person michael = new Person("Michael Jordan"); 
	Person toni = new Person("Toni Kukoc"); 
	// then I create some lots 
	Lot one   = new Lot(0, "Boston"),
	    two   = new Lot(1, "New York"), 
	    three = new Lot(2, "Indianapolis"), 
	    four  = new Lot(3, "Chicago"); 
	// now we need to enter the lots in the auction 
	Auction auction = new Auction(); 
	auction.enterLot("Boston"); 
	auction.enterLot("New York"); 
	auction.enterLot("Indianapolis"); 
	auction.enterLot("Chicago"); 
        // that shows you can't create the lots outside 
        // that's not very good (lots can also exist outside auctions, no?)
        // put that off for a while, let's see what the auction looks like now
        auction.showLots();         
        // can we look up a lot by its number? 
        System.out.println(auction.getLot(1)); 
        // as you can see, that doesn't work, so we adjust it: 
        System.out.println((auction.getLot(1)).getDescription()); 
        // so far it looks like we should never worry about lots in general
        // that is, that Bid and Lot are to help Auction (does Person too?) 
        // but look, we still need to know the nature of things we store... 
        (auction.getLot(1)).bidFor(larry, 100); 
        (auction.getLot(2)).bidFor(michael, 200); 
        (auction.getLot(3)).bidFor(toni, 400); 
        (auction.getLot(4)).bidFor(larry, 300); 
        auction.showLots(); 
        (auction.getLot(1)).bidFor(toni, 1000); 
        auction.showLots(); 
        // what's the highest bid for lot number 1 (and what is it?)
        System.out.println((auction.getLot(1)).getHighestBid()); 
        // a(rg)hhh... so it's a bid. Get the info inside in a bid-wise way

        Lot toBeExamined = auction.getLot(1); 
        Bid toBePrinted = toBeExamined.getHighestBid(); 
        
        System.out.println(toBeExamined.getDescription() + ": (" + 
                           toBePrinted.getBidder()+ ", " + 
                           toBePrinted.getValue() + ")"); 

        // but we forgot the printed needs to be even more specific... 

        System.out.println(toBeExamined.getDescription() + ": (" + 
                           (toBePrinted.getBidder()).getName()+ ", " + 
                           toBePrinted.getValue() + ")"); 

        // larry bird never quits they used to say 
        (auction.getLot(1)).bidFor(larry, 10000);         

        // so he's now in pole position  
        System.out.println(toBeExamined.getDescription() + ": (" + 
                           (toBePrinted.getBidder()).getName()+ ", " + 
                           toBePrinted.getValue() + ")"); 

        // oops, why not? what can we do to fix it? here's what: 
        toBePrinted = toBeExamined.getHighestBid(); 

        System.out.println(toBeExamined.getDescription() + ": (" + 
                           (toBePrinted.getBidder()).getName()+ ", " + 
                           toBePrinted.getValue() + ")"); 

	// do you see what happened? 

        // that was _quite_ instructive, I'd say... 

    }
}
Here's the output of the program above:
frilled.cs.indiana.edu%javac AucExperiment.java
frilled.cs.indiana.edu%java AucExperiment
1: Boston
    (No bid)
2: New York
    (No bid)
3: Indianapolis
    (No bid)
4: Chicago
    (No bid)
Lot@cac268
Boston
1: Boston
    Bid: 100
2: New York
    Bid: 200
3: Indianapolis
    Bid: 400
4: Chicago
    Bid: 300
1: Boston
    Bid: 1000
2: New York
    Bid: 200
3: Indianapolis
    Bid: 400
4: Chicago
    Bid: 300
Bid@1cde100
Boston: (Person@16f0472, 1000)
Boston: (Toni Kukoc, 1000)
Boston: (Toni Kukoc, 1000)
Boston: (Larry Bird, 10000)
frilled.cs.indiana.edu%
Please trace the program and match the comments with the output.

Last stop: the log-file analyzer.

This is not going to be a bad exercise, I can tell you that.

But will it be very good?

That depends on us.

So with this responsibility on our backs, let's go straight for it.

First the API.

And here's the code, class by class.

First, LogAnalyzer.java:

/**
 * Read web server data and analyze
 * hourly access patterns.
 * 
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.12.31
 */
public class LogAnalyzer
{
    // Where to calculate the hourly access counts.
    private int[] hourCounts;
    // Use a LogfileReader to access the data.
    private LogfileReader reader;

    /**
     * Create an object to analyze hourly web accesses.
     */
    public LogAnalyzer()
    { 
        // Create the array object to hold the hourly
        // access counts.
        hourCounts = new int[24];
        // Create the reader to obtain the data.
        reader = new LogfileReader();
    }

    /**
     * Analyze the hourly access data from the log file.
     */
    public void analyzeHourlyData()
    {
        while(reader.hasMoreEntries()) {
            LogEntry entry = reader.nextEntry();
            int hour = entry.getHour();
            hourCounts[hour]++;
        }
    }

    /**
     * Print the hourly counts.
     * These should have been set with a prior
     * call to analyzeHourlyData.
     */
    public void printHourlyCounts()
    {
        System.out.println("Hr: Count");
        for(int hour = 0; hour < hourCounts.length; hour++) {
            System.out.println(hour + ": " + hourCounts[hour]);
        }
    }
    
    /**
     * Print the lines of data read by the LogfileReader
     */
    public void printData()
    {
        reader.printData();
    }
}
Next, LogEntry.java:
/**
 * Store the data from a single line of a
 * web-server log file.
 * Individual fields are made available via
 * accessors such as getHour() and getMinute().
 * 
 * @author David J. Barnes and Michael Kolling.
 * @version 2002.01.01
 */
public class LogEntry implements Comparable
{
    // Where the data values extracted from a single
    // log line are stored.
    private int[] dataValues;
    // At which index in dataValues the different fields
    // from a log line are stored.
    private final int YEAR = 0, MONTH = 1, DAY = 2,
                      HOUR = 3, MINUTE = 4;
                      
    /**
     * Decompose a log line so that the individual fields
     * are available.
     * @param logline A single line from the log.
     */
    public LogEntry(String logline)
    {
        // The array to store the data for a single line.
        dataValues = new int[5];
        // Break up the log line.
        LoglineTokenizer tokenizer = new LoglineTokenizer();
        tokenizer.tokenize(logline,dataValues);
    }
    
    /**
     * @return The hour field from the log line.
     */
    public int getHour()
    {
        return dataValues[HOUR];
    }

    /**
     * @return The minute field from the log line.
     */
    public int getMinute()
    {
        return dataValues[MINUTE];
    }
    
    /**
     * Create a string representation of the data.
     * This is not necessarily identical with the
     * text of the original log line.
     *
     * @return A string representing the data of this entry.
     */
    public String toString()
    {
        StringBuffer buffer = new StringBuffer();
        for(int i = 0; i < dataValues.length; i++) {
            int value = dataValues[i];
            // Prefix a leading zero on single digit numbers.
            if(value < 10) {
                buffer.append('0');
            }
            buffer.append(value);
            buffer.append(' ');
        }
        // Drop any trailing space.
        return buffer.toString().trim();
    }
    
    /**
     * Compare the date/time combination of this log entry
     * with another.
     * @param other The other entry to compare against.
     * @return A negative value if this entry comes before the other.
     *         A positive value if this entry comes after the other.
     *         Zero if the entries are the same.
     */
    public int compareTo(Object other)
    {
        if(other == this) {
            // They are the same object.
            return 0;
        }
        else {
            LogEntry otherEntry = (LogEntry) other;
            // Compare corresponding fields.
            for(int i = 0; i < dataValues.length; i++) {
                int difference = dataValues[i] - otherEntry.dataValues[i];
                if(difference != 0) {
                    return difference;
                }
            }
            // No value is different, so the two entries represent
            // identical times.
            return 0;
        }
    }
}
import java.io.*;
import java.net.URL;
import java.util.*;

/**
 * A class to read information from a file of web server accesses.
 * Currently, the log file is assumed to contain simply
 * date and time information in the format:
 *
 *    year month day hour minute
 * Log entries are sorted into ascending order of date.
 * 
 * @author David J. Barnes and Michael Kolling.
 * @version 2002.01.01
 */
public class LogfileReader
{
    // The data format in the log file.
    private String format;
    // Where the file's contents are stored in the form
    // of LogEntry objects.
    private ArrayList entries;
    // An iterator over entries.
    private Iterator dataIterator;
    
    /**
     * Create a LogfileReader to supply data from a default file.
     */
    public LogfileReader()
    {
        this("weblog.txt");
    }
    
    /**
     * Create a LogfileReader that will supply data
     * from a particular log file. 
     * @param filename The file of log data.
     */
    public LogfileReader(String filename)
    {
        // The format for the data.
        format = "Year Month(1-12) Day Hour Minute";       
        // Locate the file with respect to the current environment.
        URL fileURL = getClass().getClassLoader().getResource(filename);
        // Where to store the data.
        entries = new ArrayList();
        
        // Attempt to read the complete set of data from file.
        try{
            BufferedReader logfile =
                new BufferedReader(new FileReader(fileURL.getFile()));
            // Read the data lines until the end of file.
            String logline;
            logline = logfile.readLine();
            while(logline != null) {
                // Break up the line.
                LogEntry entry = new LogEntry(logline);
                entries.add(entry);
                logline = logfile.readLine();
            }
            logfile.close();
        }
        catch(IOException e) {
            System.out.println("Failed to read the data file: " +
                               filename);
            System.out.println("Using simulated data instead.");
            createSimulatedData(entries);
        }
        // Sort the entries into ascending order.
        Collections.sort(entries);
        reset();
    }
    
    /**
     * Does the reader have more data to supply?
     * @return true if there is more data available,
     *         false otherwise.
     */
    public boolean hasMoreEntries()
    {
        return dataIterator.hasNext();
    }
    
    /**
     * Analyze the next line from the log file and
     * make it available via a LogEntry object.
     * 
     * @return A LogEntry containing the data from the
     *         next log line.
     */
    public LogEntry nextEntry()
    {
        // Create a LogEntry from the contents of the next
        // log line.
        return (LogEntry) dataIterator.next();
    }
    
    /**
     * @return A string explaining the format of the data
     *         in the log file.
     */
    public String getFormat()
    {
        return format;
    }
    
    /**
     * Set up a fresh iterator to provide access to the data.
     * This allows a single file of data to be processed
     * more than once.
     */
    public void reset()
    {
        dataIterator = entries.iterator();
    }
    
    public void printData()
    {
        Iterator it = entries.iterator();
        while(it.hasNext()) {
            System.out.println((LogEntry) it.next());
        }
    }

    /**
     * Provide a sample of simulated data.
     * NB: To simplify the creation of this data, no
     * days after the 28th of a month are ever generated.
     * @param data Where to store the simulated LogEntry objects.
     */
    private void createSimulatedData(ArrayList data)
    {
        // For each data item (year, month, day, hour, min) the lowest
        // valid value is listed.
        int[] lowest = { 2001, 1, 1, 0, 0, };
        // For each data item (year, month, day, hour, min) the range of
        // valid values is listed. (Note the simplification of having
        // only 28 days in any month to avoid generating invalid dates.)
        int[] range = { 3, 12, 28, 24, 60 };
        // Use a fixed seed to generate the random data, so
        // that the data is reproducable.
        Random rand = new Random(12345);
        // Build each simulated line in a string buffer.
        StringBuffer line = new StringBuffer();
        // How many simulated lines we want.
        int numLines = 100;
        // The number of data values per simulated line.
        int itemsPerLine = lowest.length;
        for(int i = 0; i < numLines; i++) {
            for(int j = 0; j < itemsPerLine; j++) {
                int value = lowest[j]+rand.nextInt(range[j]);
                line.append(value);
                line.append(' ');
            }
            // Convert the line to a LogEntry.
            LogEntry entry = new LogEntry(line.toString());
            data.add(entry);
            line.setLength(0);
        }
    }
}
Then comes the LoglineTokenizer.java class:
import java.util.StringTokenizer;

/**
 * Break up line from a web server log file into
 * its separate fields.
 * Currently, the log file is assumed to contain simply
 * integer date and time information.
 * 
 * @author David J. Barnes and Michael Kolling.
 * @version 2001.12.31
 */
public class LoglineTokenizer
{
    /**
     * Construct a LogLineAnalyzer
     */
    public LoglineTokenizer()
    {
    }

    /**
     * Tokenize a log line. Place the integer values from
     * it into an array. The length of the array must match
     * the number of tokens on the line.
     *
     * @param logline The line to be tokenized.
     * @param dataLine Where to store the values.
     */
    public void tokenize(String logline, int[] dataLine)
    {
        StringTokenizer tokens = new StringTokenizer(logline);
        if(tokens.countTokens() == dataLine.length) {
            String number = "";
            try{
                StringTokenizer tokenizer = new StringTokenizer(logline);
                int numTokens = tokenizer.countTokens();
                for(int i = 0; i < numTokens; i++) {
                    number = tokenizer.nextToken();
                    dataLine[i] = Integer.parseInt(number);
                }
            }
            catch(NumberFormatException e) {
                System.out.println("Badly formatted number: " + number);
                System.out.println("In log line: " + logline);
            }
        }
        else {
            System.out.println("Invalid log line: " + logline);
        }
    }
}
It appears that we need a file like this, too:
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 21
2002 5 31 23 27
2002 5 31 23 27
2002 5 31 23 33
2002 5 31 23 47
2002 5 31 23 55
2002 5 31 23 55
2002 5 31 23 55
2002 5 31 23 55
2002 5 31 23 55
2002 5 31 23 55
Call it weblog.txt and we can run the program:

frilled.cs.indiana.edu%cat LogExperiment.java
class LogExperiment {
    public static void main(String[] args) {
	LogAnalyzer a = new LogAnalyzer(); 
        a.analyzeHourlyData(); 
	a.printData(); 
        a.printHourlyCounts(); 
    }
}
frilled.cs.indiana.edu%
Here's the program in action:
frilled.cs.indiana.edu%java LogExperiment
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 21
2002 05 31 23 27
2002 05 31 23 27
2002 05 31 23 33
2002 05 31 23 47
2002 05 31 23 55
2002 05 31 23 55
2002 05 31 23 55
2002 05 31 23 55
2002 05 31 23 55
2002 05 31 23 55
Hr: Count
0: 0
1: 0
2: 0
3: 0
4: 0
5: 0
6: 0
7: 0
8: 0
9: 0
10: 0
11: 0
12: 0
13: 0
14: 0
15: 0
16: 0
17: 0
18: 0
19: 0
20: 0
21: 0
22: 0
23: 24
frilled.cs.indiana.edu%
The real interest in this program lies in the way it works.

To understand it completely we need to wait a few more lectures.

Meanwhile this would be a good time to go back to our regularly scheduled lab.


Updated by Adrian German for A201/A597 and I210