/*
--- CSU213 Spring 2005 Lecture Notes ---------
Copyright 2005 Viera K. Proulx

Lecture 15: How do we compare ...
*/

/* 
Goals:

 - learn to design a universal test for equality of lists of objects


Introduction:
We return to the problem first considered in Lecture 11:

Let us consider the following examples of classes that represent
images saved in a computer, items in a salad (tomaotes, peppers, 
mushrooms), or segments of a car trip between refueling, with 
information about the origin and destination, the miles traveled,
and the fuel information.

 +--------------+   +-------------+   +-------------+
 | Picture      |   | Item        |   | Segment     |
 +--------------+   +-------------+   +-------------+
 | String title |   | String name |   | String from |
 | int width    |   | int pieces  |   | String to   |
 | int height   |   | int price   |   | int miles   |
 +--------------+   +-------------+   | int gallons |
                                      | int price   |
                                      +-------------+

Here are some examples of the instances of these classes:

Picture snow  = new Picture("Snow.gif", 400, 200);
Picture beach = new Picture("Beach.gif", 200, 300);
Picture bluff = new Picture("Bluff.gif", 200, 600);

Item tom = new Item("tomato", 3, 50);
Item pep = new Item("pepper", 2, 60);
Item rad = new Item("raddish", 6, 10);

Segment b2n = new Segment("Boston", "NYC", 200, 10, 210);
Segment n2t = new Segment("NYC", "Trenton", 120, 5, 200);
Segment t2p = new Segment("Trenton", "Philly", 80, 2, 180);

We need a list of pictures to represent all pictures in a folder or 
a directory, we need a list of salad items to represent a
salas serving in a restaurant, and we need a list of trip 
segments, to compute the total distance traveled, or the cost
of the trip.

We defined a list of Object-s to represent any one of these lists:
   
       +--------+                
       | ALoObj |<------------+  
       +--------+             |   
          / \                 |  
          ---                 |  
           |                  |    
      --------------          |   
      |            |          | 
 +---------+ +--------------+ |
 | MTLoObj | | ConsLoObj    | | 
 +---------+ +--------------+ | 
             | Object first | | 
             | ALoObj rest  |-+  
             +--------------+     

We also made examples of these lists:

ALoObj objPictures = new ConsLoObj(snow, 
                       new ConsLoObj(beach,
                         new ConsLoObj(bluff, 
                           new MTLoObj())));

ALoObj objSalad = new ConsLoObj(tom, 
                    new ConsLoObj(pep,
                      new ConsLoObj(rad, 
                        new MTLoObj())));

ALoObj objTrip = new ConsLoObj(b2n, 
                   new ConsLoObj(n2t,
                     new ConsLoObj(t2p, 
                       new MTLoObj())));




We have then designed several methods that process the list of Object-s. 
--------------------------------------------------------------------------
***************************************************************************
We will consider the method 'contains'.

We start with recalling the examples:

pictures.contains(snow) --> should be true
pictures.contains(new Picture("Lake", 200, 200)) --> should be false

salad.contains(pep) --> should be true
salad.contains(new Item("Olive", 10, 10)) --> should be false

trip.contains(t2p) --> should be true
trip.contains(new Segment("DC", "Norfolk", 150, 8, 180)) --> should be false

In the class ALoObj we had the purpose and the header:
// does this list contain the given object
abstract boolean contains(Object obj);

In the class MTLoObj the method body was easy: 
boolean contains(Object obj){ return false; }

In the class ConsLoObj we defined the following:
boolean contains(Object o){
  return this.first.equals(o)
      || this.rest.contains(o);
}

However, we know, that the 'equals' method is not sufficient for our tests. 
We are concerned not only with the intensional equality of objects, 
but especially whether the contents and structure of the data is the same 
(extensional equality). For this we used our method 'same'. If we try to rewrite
the 'contains' method for the class ConsLoObj as follows:

boolean contains(Object o){
  return this.first.same(o)
      || this.rest.contains(o);
}

we run into a problem. Class Object does not define the method 'same'. In the
past, we have defined the method 'same' for different classes. So, we know 
how to define the method 'same' for the classes Picture, Item, and Segment:

// in the class Picture
// determine whether this picture is the same as the given one
boolean same(Picture p){
  return this.title.equals(p.title)
      && this.width == p.width
      && this.height == p.height;
}

// in the class Item
// determine whether this item is the same as the given one
boolean same(Item i){
  return this.name.equals(i.name)
      && this.pieces == i.pieces
      && this.price == i.price;
}

// in the class Segment
// determine whether this segment is the same as the given one
boolean same(Segment s){
  return this.from.equals(s.from)
      && this.to.equals(s.to)
      && this.miles == s.miles
      && this.gallons == s.gallons
      && this.price == s.price;
}

Unfortunately, each  of these methods consumes a different argument. We can 
abstract over the argument - replacing it with 'Object obj'. Of course, this
will then require that within the body of each method we 'cast' the given
obj to the appropriate type as follows:


// in the class Picture
// determine whether this picture is the same as the given object
boolean same(Object obj){
  return this.title.equals(((Picture)obj).title)
      && this.width == ((Picture)obj).width
      && this.height == ((Picture)obj).height;
}

// in the class Item
// determine whether this item is the same as the given object
boolean same(Object obj){
  return this.name.equals(((Item)obj).name)
      && this.pieces == ((Item)obj).pieces
      && this.price == ((Item)obj).price;
}

// in the class Segment
// determine whether this segment is the same as the given object
boolean same(Object obj){
  return this.from.equals(((Segment)obj).from)
      && this.to.equals(((Segment)obj).to)
      && this.miles == ((Segment)obj).miles
      && this.gallons == ((Segment)obj).gallons
      && this.price == ((Segment)obj).price;
}

Before we proceed, we should run some tests, to make sure the method works
the same was as it did before:

// test the method same in the class Picture:
boolean testSameP1 = snow.same(new Picture("Snow.gif", 400, 200)) == true;
boolean testSameP2 = beach.same(new Picture("Snow.gif", 400, 200)) == false;

// test the method same in the class Item:
boolean testSameI1 = tom.same(new Item("tomato", 3, 50)) == true;
boolean testSameI2 = tom.same(rad) == false;

// test the method same in the class Segment:
boolean testSameS1 = b2n.same(new Segment("Boston", "NYC", 200, 10, 210)) == true;
boolean testSameS2 = b2n.same(t2p) == false;

We have solved one part of our problem. Now every class whose objects we want to
combine into a list has the method 'same' with the same header. But the putside
world does not know about it. We could have an abstract class that contains an
abstract method with the signature 'boolean same(Object obj)', and have every
one of our classes extend it. But mixing salad items with pictures and trip
segments is certainly not very apetizing. The three classes have nothing in common,
only the fact that we want to compare two of their instances with each other.

The solution is to define an 'interface'. java interface is like an abstract class
with no fields, only methods. A class may extend only one super class, but may 
'implement' one or more interfaces. Interfaces define some common behavior that
typically transcends the specific properties of the classes that implement them.
The first such property we identified, is extensional equality. We define the 
interface and the three classes that implement the interface as follows:
*/

interface ISame{
  // determine whether this object is the same as the given one
   boolean same(Object obj);
}

// to represent a picture file
class Picture implements ISame {
  String title;
  int width;
  int height;

  Picture(String title, int width, int height) {
    this.title = title;
    this.width = width;
    this.height = height;
  }

  // determine whether this picture is the same as the given object
  boolean same(Object obj){
    return this.title.equals(((Picture)obj).title)
        && this.width == ((Picture)obj).width
        && this.height == ((Picture)obj).height;
  }
}

// to represent a salad item
class Item implements ISame {
  String name;
  int pieces;
  int price;

  Item(String name, int pieces, int price) {
    this.name = name;
    this.pieces = pieces;
    this.price = price;
  }

  // determine whether this item is the same as the given object
  boolean same(Object obj){
    return this.name.equals(((Item)obj).name)
        && this.pieces == ((Item)obj).pieces
        && this.price == ((Item)obj).price;
  }
}

// to represent a segment of a road trip
class Segment implements ISame {
  String from;
  String to;
  int miles;
  int gallons;
  int price;

  Segment(String from, String to, int miles, int gallons, int price) {
    this.from = from;
    this.to = to;
    this.miles = miles;
    this.gallons = gallons;
    this.price = price;
  }

  // determine whether this segment is the same as the given object
  boolean same(Object obj){
    return this.from.equals(((Segment)obj).from)
        && this.to.equals(((Segment)obj).to)
        && this.miles == ((Segment)obj).miles
        && this.gallons == ((Segment)obj).gallons
        && this.price == ((Segment)obj).price;
  }
}

/*

We can now run the earlier examples and make sure they work:
*/

class Examples_1{
  Examples_1() {}

  // picture files
  Picture snow  = new Picture("Snow.gif", 400, 200);
  Picture beach = new Picture("Beach.gif", 200, 300);
  Picture bluff = new Picture("Bluff.gif", 200, 600);

  // salad items
  Item tom = new Item("tomato", 3, 50);
  Item pep = new Item("pepper", 2, 60);
  Item rad = new Item("raddish", 6, 10);

  // trip segments
  Segment b2n = new Segment("Boston", "NYC", 200, 10, 210);
  Segment n2t = new Segment("NYC", "Trenton", 120, 5, 200);
  Segment t2p = new Segment("Trenton", "Philly", 80, 2, 180);


  // test the method same in the class Picture:
  boolean testSameP1 = snow.same(new Picture("Snow.gif", 400, 200)) == true;
  boolean testSameP2 = beach.same(new Picture("Snow.gif", 400, 200)) == false;

  // test the method same in the class Item:
  boolean testSameI1 = tom.same(new Item("tomato", 3, 50)) == true;
  boolean testSameI2 = tom.same(rad) == false;

  // test the method same in the class Segment:
  boolean testSameS1 = b2n.same(new Segment("Boston", "NYC", 200, 10, 210)) == true;
  boolean testSameS2 = b2n.same(t2p) == false;
}

/* We still have one problem to resolve. At this point the three class made 
a contract to implement the 'same' method. However, the 'contains' method in
the class ConsLoObj need to be informed that the objects in the list, specifically
the object known as 'this.first' is an instance of a class that implements
ISame interface, and so it will be able to invoke the method 'same' at the 
run time. We cast 'this.first' to type ISame:

  // determine whether this list contains the given object
  boolean contains(Object o){
    return ((ISame)this.first).same(o)
        || this.rest.contains(o);
  }

We can now finish the definition of these classes and run the examples:
*/

// to represent a list of objects
abstract class ALoObj {

  // determine whether this list contains the given object
  abstract boolean contains(Object o);
}

// to represent an empty list of objects
class MTLoObj extends ALoObj {
  MTLoObj() {}

  // determine whether this list contains the given object
  boolean contains(Object o){ return false; }
}

// to represent a nonempty list of objects
class ConsLoObj extends ALoObj {
  Object first;
  ALoObj rest;

  ConsLoObj(Object first, ALoObj rest) {
    this.first = first;
    this.rest = rest;
  }

  // determine whether this list contains the given object
  boolean contains(Object o){
    return ((ISame)this.first).same(o)
        || this.rest.contains(o);
  }
}


class Examples{
  Examples() {}

  // picture files
  Picture snow  = new Picture("Snow.gif", 400, 200);
  Picture beach = new Picture("Beach.gif", 200, 300);
  Picture bluff = new Picture("Bluff.gif", 200, 600);

  // salad items
  Item tom = new Item("tomato", 3, 50);
  Item pep = new Item("pepper", 2, 60);
  Item rad = new Item("raddish", 6, 10);

  // trip segments
  Segment b2n = new Segment("Boston", "NYC", 200, 10, 210);
  Segment n2t = new Segment("NYC", "Trenton", 120, 5, 200);
  Segment t2p = new Segment("Trenton", "Philly", 80, 2, 180);


  // test the method same in the class Picture:
  boolean testSameP1 = snow.same(new Picture("Snow.gif", 400, 200)) == true;
  boolean testSameP2 = beach.same(new Picture("Snow.gif", 400, 200)) == false;

  // test the method same in the class Item:
  boolean testSameI1 = tom.same(new Item("tomato", 3, 50)) == true;
  boolean testSameI2 = tom.same(rad) == false;

  // test the method same in the class Segment:
  boolean testSameS1 = b2n.same(new Segment("Boston", "NYC", 200, 10, 210)) == true;
  boolean testSameS2 = b2n.same(t2p) == false;

  // an empty list of anything...
  ALoObj mtlist = new MTLoObj();

  // list of pictures
  ALoObj objPictures = new ConsLoObj(snow, 
                         new ConsLoObj(beach,
                           new ConsLoObj(bluff, 
                             new MTLoObj())));

  // list of salad items
  ALoObj objSalad = new ConsLoObj(tom, 
                      new ConsLoObj(pep,
                        new ConsLoObj(rad, 
                          new MTLoObj())));

  // a list of trip segments
  ALoObj objTrip = new ConsLoObj(b2n, 
                     new ConsLoObj(n2t,
                       new ConsLoObj(t2p, 
                         new MTLoObj())));


  // test the method contains for the list of pictures
  boolean testContainsP1 = objPictures.contains(snow) == true;
  boolean testContainsP2 = objPictures.contains(new Picture("Lake", 200, 200)) == false;

  boolean testContainsI1 = objSalad.contains(pep) == true;
  boolean testContainsI2 = objSalad.contains(new Item("Olive", 10, 10)) == false;

  boolean testContainsS1 = objTrip.contains(t2p) == true;
  boolean testContainsS2 = objTrip.contains(new Segment("DC", "Norfolk", 150, 8, 180)) == false;

}

/*
*****************************************************************************************
Take a deep breath!
We are not done. We still have a problem.
What if our list is a list of lists?
That means, that each Object in the list is of the type ALoObj.
So, we have to make sure that ALoObj also implements the ISame interface.
At least, we know what we need to do: to design the method 'same' for the hierarchy
that represents a list of Object-s.

We can easily make examples from the data we already defined:

boolean testSameLP1 = objPictures.same(new ConsLoObj(snow, 
                                         new ConsLoObj(beach,
                                           new ConsLoObj(bluff, 
                                             new MTLoObj())))) == true;
boolean testSameLP2 = objPictures.same(new ConsLoObj(snow, new MTLoObj())) == false;

boolean testSameLI1 = objSalad.same(new ConsLoObj(tom, 
                                      new ConsLoObj(pep,
                                        new ConsLoObj(rad, 
                                          new MTLoObj())))) == true;
boolean testSameLI2 = objSalad.same(new ConsLoObj(tom, new MTLoObj())) == false;

boolean testSameLS1 = objTrip.same(new ConsLoObj(b2n, 
                                     new ConsLoObj(n2t,
                                       new ConsLoObj(t2p, 
                                         new MTLoObj())))) == true;
boolean testSameLS2 = objTrip.same(new ConsLoObj(b2n, new MTLoObj())) == false;

Life would be easier, if we at least knew that the Object we are comparing with
is indeed a list of Objects. Maybe a helper method could do the work.

We define the following in the class ALoObj:

abstract class ALoObj implements ISame{

  // determine whether this list of objects is the same as the given Object
  boolean same(Object obj){
    return this.sameLoObj((ALoObj) obj);
  }

  // determine whether this list of objects is the same as the given list of objects
  abstract boolean sameLoObj(ALoObj alist);
}
The rest is the same as what we did in Lecture 9:


Add the the abstract class two new methods:

  // determine whether this list of objects is the same as the given empty list of objects
  boolean sameMTLoObj(MTLoObj mtlist){ return false; }

  // determine whether this list of objects is the same as the given nonempty list of objects
  boolean sameConsLoObj(ConsLoObj conslist){ return false; }
  
In the class MTLoObj we add:

  // determine whether this empty list of objects is the same as the given list of objects
  boolean sameLoObj(ALoObj alist){
    return alist.sameMTLoObj(this);
  }

  // determine whether this empty list of objects is the same as the given empty list of objects
  boolean sameMTLoObj(MTLoObj mtlist){ return true; }

and in the class ConsLoObj we add:

  // determine whether this nonempty list of objects is the same as the given list of objects
  boolean sameLoObj(ALoObj alist){
    return alist.sameConsLoObj(this);
  }
  
  // determine whether this nonempty list of objects is the same 
  // as the given nonempty list of objects
  boolean sameConsLoObj(ConsLoObj conslist){
    return ((ISame)this.first).same(conslist.first)
        && this.rest.same(conslist.rest);
  }

Complete example of all te code from this lecture is in a separate file.

*/