OOP - exercise 1 – Family Tree Goals: to exercise, experience − Interfaces and abstract classes − Inheritance − Object-oriented design − Flexibility in code development − Iterator, Factory, Template method, Strategy and Decorator design patterns − Linked list and sorted list data structures. Submission Deadline: April 1, 2009 (no fooling) 20:00
1 Introduction
In this exercise you will implement a family tree in two stages. 1.You will create a generic reusable infrastructure. 2.You will instantiate that infrastructure and use it for the application of your choice (here it will be your family tree). The design below is a bit simplistic, and will not provide a complete application, but this is only an exercise. The root of the code will be in a package called oop.ex1. Most of the code will be in packages of the form oop.ex1.
, as described below.
2 Infrastructure (in package oop.ex1.collection) In this part, you are to define generic interfaces and classes for several data structures, as well as a class for comparing the efficiency of the different implementations. The main interface for the data structure provides the contract that the classes need to satisfy. The classes will provide two structures – a linked list and a sorted linked list. All the code (interfaces and classes) dealing with the data structures will be in the package oop.ex1.collection and the test in oop.ex1.test.collection. OOP - exercise 1 – Family Tree: page 1 of 14
Class hierarchy of our data structures: Sequence
Abstract Sequence
Simple Sequence
Sorted Sequence
The public interfaces and classes in this package will be visible outside the package; those that are defined without an access restriction (i.e., have package visibility) will be visible only inside the package. The above also applies to visibility of methods: when access restriction is not specified for interface methods, they are implicitly public, but object methods without an access restriction are “package protected”. Always define your classes and methods with minimum sufficient visibility. 2.1 Define Sequence interface Objects which sole purpose is to contain other objects are called in Java terminology “collections”. Java provides several collection implementations in java.util package, but in this exercise we will create and use our own implementations. One example of collection is a sequence – series of objects stored in a particular order.
Since we want to define what the sequence does, rather than how it's done, we will start with defining a Sequence interface which extends java.lang.Iterable and looks like this: public interface Sequence extends Iterable { Object first(); boolean isEmpty(); int size(); Object add(Object obj); Object get(Object obj); void addMany(Object... objects); void addAll(Iterable objects); }
Defining an interface includes adding appropriate documentation (Javadoc) – requirements described textually below must be written in the documentation. All methods in interfaces are implicitly public and abstract. Here's what the above methods should do: ● first() method return the first element of the sequence, ● isEmpty() tests whether sequence has any elements, ● size() returns the size of the sequence and zero if it is empty. ● The method add(Object) allows to add elements to the sequence, returning the added element (or its equivalent, more on that later), and rejects nulls throwing a runtime exception, ● get(Object) returns an object equal to the parameter, if it is found in the sequence, or null otherwise. If there are duplicate (equal to each other) objects in the sequence, the get method may return any one of them. The sequence, as we said, should not contain null elements. OOP - exercise 1 – Family Tree: page 2 of 14
●
The addMany and addAll methods are there for user convenience – they allow users to add multiple elements at once, the three dots (Object...) is called a vararg and it's an alternative syntax for Object[] that allows user to call this method as addMany(“a”, “b”, “c”) and get it translated by the compiler to addMany(new Object[] {“a”, “b”, “c”}).
Iterable interface will allow our sequences to be used in for loops, e.g.: Sequence sequence = ...; for (Object element : sequence) { //element variable, at each stage of iteration, represents next element in the sequence //do whatever you need with element variable in the loop body }
The iterator, which is also a design pattern, allows users of the collection to go over its elements, performing some action for each one. In Java, iterator is represented by java.util.Iterator interface, and a collection which allows iteration – java.lang.Iterable, defines java.util.Iterator iterator() method. You will need to implement iterator() method too, and the actual data structure will have to provide a corresponding Iterator class, a new instance of which this method will return – iterator instances are not shared between multiple clients. 2.2 AbstractSequence class You may notice that the convenience addMany/All methods can be implemented using the add(Object) and do not directly depend on the actual implementation of the sequence. Same can be said about size and first methods - which can be implemented using the iterator(). While those may not be optimal implementations, they provide a basis for sub-classes, who can override the default algorithms with better ones if needed. This is called Template Method design pattern widely used in Object-Oriented programs and we will exercise it by creating a package-protected abstract AbstractSequence class. The get method can also be implemented using iterator() but how should we compare objects for equality? We want to allow sub-classes to control this, and hence provide equal(Object,Object) which is intended to be overridden by sub-classes. It is defined abstract and has no body, since it is intended for internal use only we declare it as protected. public abstract class AbstractSequence implements Sequence { public int size() { int count = 0; for (Object element : this) count ++; return count; } public void addMany(Object... objects) { for (Object obj : objects) add(obj); } public Object get(Object obj) ... public Object first() ... public void addAll(Iterable objects) ... protected abstract boolean equal(Object obj1, Object obj2); }
The rest of the Sequence and Iterable methods (the ones not declared in AbstractSequence) are automatically provided by the JVM as abstract methods.
OOP - exercise 1 – Family Tree: page 3 of 14
Can you think of a reasonable default implementation of the equal method? What do you need to change to add a default implementation of equal(Object,Object) in AbstractSequence? Include your answers in the README file. 2.3 The SimpleSequence class – a linked list We have defined an interface, so let's now implement it. One way of implementing a sequence is based on a linked list. Linked list is a data structure which holds Node objects. The list stores a pointer to head node (first in the list), and every node stores the element, and the pointer to the next node. The last node's “next” pointer is null.
When we create a new linked list, it is originally empty – the head pointer points to null. New elements are added in the beginning of the list, using roughly the following algorithm: 1. Allocate a new node 2. Insert new element into the newly created node 3. Have new node point to old head 4. Update head to point to new node Create a public class named SimpleSequence which should extend AbstractSequence providing implementation for the missing methods. It should also override size() method with a more efficient implementation that returns the size in constant time, and override the first() method. The get method needs equality comparison, and here it is made using the Object.equals method. This data structure can contain duplicate elements. When overriding methods, we add @Override annotation before method modifiers (method modifiers are public, protected, private, abstract, static, etc.) - this is not mandatory, but highly recommended, since it allows the Java compiler to detect a misspelling or incorrect signature of overridden methods that otherwise will likely result in incorrect behavior of our program. (More info on annotations here.) Iterator: Usually data structures have an additional dedicated class for the iterator. Iterator has access to internals of the data structure and implements hasNext() and next() method accordingly. To find out more about iterators in Java, see this tutorial. In this exercise our data structures do not support removal of elements, therefore our Iterator needs not support the optional remove() method. The convention in Java is that not-implemented methods throw UnsupportedOperationException, so we implement the remove() method of the iterator like this: public void remove() { throw new UnsupportedOperationException(); }
More on exceptions: Exceptions allow us to write fail-fast code - the sooner an error is spotted, the easier it is to find its cause and the better are the chances to correct it. To learn more about exceptions, see this tutorial. In this part of the exercise we will use several predefined Java exceptions, such as UnsupportedOperationException, NullPointerException and java.util.NoSuchElementException. These OOP - exercise 1 – Family Tree: page 4 of 14
exceptions are “runtime”, so the compiler does not require try/catch clause around the code that may throw them. Use NullPointerException (also abbreviated NPE) to prevent null values from being added to sequences, and use NoSuchElementException to signal that no element is available, when one is expected, such as when returning first element of an empty sequence or in case of incorrect usage of the iterator. Generics: when looking through Javadoc and other documentation, you may notice things written within angle brackets, like Iterator<E>. Those are type parameters to classes and methods, also known as Generics. We will learn Generics later in the course, so in this exercise we won't use them yet. It means that if you see a method signature which uses a parameter that came from within angle brackets, but you didn't supply any angle-bracket parameters – the type you should use instead is Object. The compiler may issue warnings that you are making unchecked calls – this is also caused by ignoring Generics. To make the warnings go away, add @SuppressWarnings({"unchecked"}) annotation before method modifiers (it can coexist with @Override annotation, and the order of annotations does not matter). You are not allowed to use Generics in this exercise. “Whether the correctness of a piece of software can be guaranteed or not depends greatly on the structure of the thing made” Edsger W. Dijkstra So, remember: keep minimum required visibility of your code – any “secondary” classes which are used to store or access internal structure should not be accessible from outside world. 2.4 The SortedSequence class – linked list that is always sorted Sometimes we want to access the elements of the sequence in some order, which is determined by the values, not the order in which they were inserted. One particular usage is accessing the minimum value in a constant time. For that purpose we will create a variation of SimpleSequence, a public sub-class named SortedSequence. When instance of SortedSequence is created, it requires a parameter of the type java.util.Comparator interface. The comparator is used to compare the values which are being added to the sequence, to the existing sequence values. Comparator: is an object that encapsulates ordering, the interface consists of a single method: public interface Comparator { int compare(Object o1, Object o2); } The compare method compares its two arguments, returning a negative integer, 0, or a positive integer
depending on whether the first argument is less than, equal to, or greater than the second. If either of the arguments has an inappropriate type for the Comparator, the compare method throws a ClassCastException. Comparator can be used in the following way: int low, high ... //or Integer low, high Comparator comparator = ... int comparison = comparator.compare(low,high); if (comparison > 0) //low is greater than high System.out.println(“invalid range”); else if (comparison == 0) //low and high are equal System.out.println(“single value = ” + low); else //valid range System.out.println(“range [” + low + “,” + high + “]”);
You can find more details and examples in this tutorial. Comparator is an example of Strategy design pattern, it provides a “comparison strategy” to our sequence – any new value is placed in the OOP - exercise 1 – Family Tree: page 5 of 14
appropriate position in the sequence, according to ascending order of the values. The comparator cannot be null and is set only once (stored with final field modifier). The equality comparison here is made using the comparator. This data structure can not contain duplicates (elements considered equal by the comparator), so when user attempts adding a duplicate, it has no affect on the data structure and matching collection resident is returned by the add method. What methods of SimpleSequence do you need to override1? Include your answer in the README file briefly describing what the overridden method does. You may implement the algorithms using either recursion or regular loops. Recursive solution will likely be shorter and more readable, but non-recursive solution will likely run faster on the JVM. 2.6 Testing oop.ex1.collection Create a class named SequenceTester in oop.ex1.test.collection package, to test the sequences. This class should add elements, iterate, add more, search for some objects etc. You should be creative and test as many different scenarios as possible, to make sure that your data structures work correctly. Test both types of sequence implementations. You can use Integer objects as sequence elements in your tests, create a Comparator that uses compareTo method from java.util.Comparable interface (the result of Comparable.compareTo is often regarded as “natural order”, hence the name). The sources given with this exercise include oop.ex1.test.collection.NaturalOrderComparator, which you can use like Comparator naturalOrder = NaturalOrderComparator.INSTANCE;
If you need to generate random numbers, you can use nextInt method in java.util.Random like this: Random r = new Random(); //create instance of Random once per series final int max = 10; //numbers between 0 and 9 will be generated int[] randomNumbers = new int[100]; //create array of 100 numbers for (int i = 0; i < 100; i++) { randomNumbers[i] = r.nextInt(max); //set the value to a random number between 0 and 9 } More examples of using Random can be found here.
We provide you with a basic test for sequences – oop.ex1.test.collection.SequenceSmokeTest, alongside some utilities that are used in it, like NaturalOrderComparator, SequenceFactory, and others located under oop.ex1.test and oop.ex1.test.collection packages. After you finish the sequences implementation, SequenceSmokeTest has to run successfully, producing following output: Test success rate = 8/8, results: [simple empty: success, simple add 1: success, simple find 1: success, sorted empty: success, sorted add 1: success, sorted find 1: success, sorted duplicates: success, sorted sorting: success]
The test you write can, but does not have to, take advantage of any of the supplied utilities. 1 This assignment does not require improving basic implementation of get OOP - exercise 1 – Family Tree: page 6 of 14
3 Application (in package oop.ex1.family) The code for the application, in the package oop.ex1.family, includes the following classes. Family
Person
Marriage
Family Member
Person Object
Family Exception Missing Information Exception
Invalid Information Exception
Override the default toString method in Family, PersonObject, FamilyMember and Marriage to produce a sensible String representation of each of these objects 3.1 Person interface and PersonObject class First we will create a public interface that represents a person. The class should have following members: name, gender (male or female), birth date and death date. All properties have getter methods - methods of the form getXxx where xxx is the name of property. Name, gender, and birth date cannot be changed after object creation, while death date can be set using die method that takes a Date. No death date (null) means the person is still alive. Person interface should look like this: public interface Person { String getName(); Date getBirthDate(); Gender getGender(); Date getDeathDate(); void die(Date d); boolean isAlive(Date d); }
We will also implement the interface with a public PersonObject class. Name, gender and birth date are mandatory parameters for its constructor, they cannot be null. PersonObject will store the information in member fields, but not before performing some validations: that mandatory parameters are not null, birth dated precedes death date, and death isn't modified after it is set. 3.1.1 Gender enum Is defined like this: public enum Gender {MALE, FEMALE} and it's values can be referenced as Gender.MALE and Gender.FEMALE (or, with static import before the class, just as MALE and FEMALE) and Gender can be treated as a regular class name. MALE and FEMALE are the only instances of Gender, they can be used in if or switch statements for example: import static oop.ex1.family.Gender.*; ... Gender x = ...; if (x == MALE) ...
To find out more about Java enums check this tutorial. 3.1.2 Exceptions To deal with invalid inputs, we will create several exceptions dedicated to family affairs. The OOP - exercise 1 – Family Tree: page 7 of 14
root exception will be abstract class PersonalInformationException extends RuntimeException, which will copy all the super-class constructors, but declare them as protected ones, preserving the same signature. Each constructor needs to call the appropriate super constructor like this: PersonalInformationException(String msg) { super(msg); } PersonalInformationException should have two concrete sub-classes: InvalidInformationException and MissingInformationException, with same package protected
constructors. All exception classes should be public. 3.1.3 Working with Dates We will use java.util.Date class to represent dates. To simplify working with the date, use the DateUtil class that you can find in the appendix and on course web-site. Use the static methods in DateUtil to convert dates to strings and vice versa – dates should be in DD/MM/YYYY format, e.g. 18/06/1942, 01/02/2009. To learn more about dates in Java, refer here. 3.2 Family object We will model family with the help of public Family, FamilyMember and Marriage classes, and the sequences we created in part I of the exercise. Family head:FamilyMember
index: FamilyMember
This is an approximate schema of how the data should be organized
FamilyMember
Marriage
person: Person
wife: FamilyMember
startDate: Date
marriages: Marriage
husband: FamilyMember
endDate: Date
parents: Marriage
children: FamilyMember
divorced: boolean
family: Family
family: Family
FamilyMember will extend a Person and add more functionality to it, such as information on his/her relatives and the role the person plays in the family, but we'll describe FamilyMember in more detail in
the section 3.3. Let's start with the family: public class Family { public Family(Person head) { /* start with a person who is “head of the family” */ } public FamilyMember getHead() { ... } public int size() { /* number of family members */ } public Iterable getAllMembers() { /* list all family members */ } public FamilyMember find(Person p) { /* find a Person in the family or null*/ } //... }
3.2.1 Using Sequences Storing marriages in family member, and children in a marriage, is done using Sequence objects, and we wish to choose the right implementation for each case. For example in case of list of marriages, the last marriage is probably the most frequently accessed, so it makes sense to keep the marriages sorted in reverse chronological order. OOP - exercise 1 – Family Tree: page 8 of 14
Although we could traverse the entire data structure to find a person in the family, this would be wasteful. When listing family members, if the same person plays several roles in a family (someone's a son and a husband, etc.), it may create a difficulty. We will once again use a sequence from the first part of the exercise to serve as an index of family members – it will provide an additional storage of family members, being updated any time new member is accepted into the family. Choose a sequence that does not allow duplicates, as we don't want family member to be indexed twice. 3.2.2 Comparing Persons Family members, or more generally Persons, can be compared by name – we will assume for the purpose of this exercise that names are unique. To maintain order inside any family members sequence, we need to create a comparator that relies on the natural String ordering (String class already implements Comparable). Create a comparator that relies on natural order of Persons names (resembling to the way we defined one in section 2.6). So except for head of the family, how does a person become a family member? 3.2.3 Family - Factory of FamilyMembers One joins a family by marrying a family member or getting born to one. But no one gets into the family without approval! The family has to accept one to become its member, and create an association between the family and newly accepted member. Our Family maintains its integrity, like any good Object should. One of the things we want to ensure is that only one FamilyMember object within a family corresponds to a given person – if the person already is in the family, we will not create new FamilyMember instance, but instead return an existing one. Logic like that cannot be placed in a constructor, because constructor always creates new instances, that's why we designate another method for family member creation – we create an internal (package protected) method in the Family class that is dedicated to creating family members (Factory design pattern – family is a “factory” of its members). Every FamilyMember creation should go through this method, and only this method is allowed to call FamilyMember constructor. Here is how the method should look like: FamilyMember accept(Person p) { if (p == null) throw new MissingInformationException("can't accept null"); return (FamilyMember) _index.add(new FamilyMember(this, p)); }
3.3 FamilyMember object FamilyMember implements a Person. As we already said, interfaces define “what” but not “how” and indeed, FamilyMember will implement Person interface, but underneath it will be very different from PersonObject: instead of saving personal details in dedicated fields, FamilyMember will store a member of type Person, to which it will delegate all the methods. FamilyMember constructor takes a Family and Person parameters, stores the instance of Person as a member field, and implements all OOP - exercise 1 – Family Tree: page 9 of 14
Person methods by delegating the work to the corresponding methods of the Person instance it
contains, like this: public class FamilyMember implements Person { private final Person _person; //... FamilyMember(Family family, Person person) { _person = person; //... } public String getName() { return _person.getName(); } public Date getBirthDate() { return _person.getBirthDate(); } //more fields and more methods are needed - part of the exercise... }
In some cases you may need to add extra logic in the overridden methods. FamilyMember will also have additional methods, related to the “family” affairs, such as marrying, giving birth, retrieving information about relatives. These methods will internally use the Family instance to which this member was associated. This is an example of Decorator design pattern (FamilyMember decorates a PersonObject), that allows, among other things, to share the same Person instance between several families and have the changes in Person propagate to the families they are in. 3.4 Marriage object Marriage, like FamilyMember, can be accessed publicly,
but can not be publicly created. This object also points back at the family it belongs to, contains a pair of family members and a list of children, while each child is a family member. Marriage has a beginning and ending date and it can end in either divorce or death of one of the spouses. 3.4.1 MaritalStatus enum This enumeration should contain SINGLE, MARRIED, DIVORCED and WIDOWED states, and it should be defined similarly to what we did with Gender. 3.4.2 Comparing marriages To organize marriages in reverse chronological order inside marriages sequence, we need to create a comparator that returns the “opposite” value from the natural dates ordering (Date class already implements Comparable). Create a comparator that relies on natural order of marriage start dates (resembling the one in section 2.6), then use reverseOrder method in the class java.util.Collections to transform it into a reverse-order comparator. Even though comparators return numeric results, don't use them in arithmetic operations (plus, minus, negative), due to the danger of overflow. Use boolean operations only - greater, less, equal. OOP - exercise 1 – Family Tree: page 10 of 14
3.5 Operations Following operations should be supported in a family – copy this list to your solution README file and provide name of the class and name of the function you created for each one of them: ● retrieve parents of a family member; ● retrieve all marriages of a family member; ● retrieve all brothers and sisters of a family member, only children of a biological parent should be considered; ● family member should be able to marry another person, whether that person is already a family member or not; ● a marriage can be divorced, or end with the death of at least one of the spouses; ● children can be born into a marriage; ● given a date, the marital status of a family member on that date can be retrieved, it is either single, married, divorced or widowed; ● given a date, the marriage in which a family member was on that date can be retrieved; ● one can test whether family member is available for marriage on a given date (according to his/ her marital status); ● all children of a family member can be retrieved, either per marriage or from all marriages; ● husband and wife can be retrieved from a marriage; ● start and end date can be retrieved from a marriage, and whether the marriage ended in divorce; You need to implement all these operations, as well as provide string representations for your objects. Find the right place in the code for each method – keep computations close to the data they operate on and where the user would expect to find them. Don't repeat computations. 3.5.1 Assumptions and constraints2 These are the assumptions that you can make while coding and should enforce in your implementation: ● Person name is unique – there are no 2 people with the same name, a person with the same name is considered the same person. ● Wife, sister and mother imply that the person is female; father, brother and husband – male. ● A person needs to be alive to get married. ● Marriage contains one male and one female. ● Marriage can be divorced only once, marriage ends if one of the spouses dies. ● Children can only be born to “active” marriages (the couple was alive and not divorced). ● One cannot marry a first-hand relative (mother, father, sister, brother or child). ● A person can only be born once, to one couple, and die only once (and not before birth). Reflect these assumptions in the Javadoc documentation of your classes. You should not implement validations for things not listed above, for example: same person can be simultaneously married in 2 different families, or have different parents – it's beyond the scope of the current exercise to prevent that. 3.5.2 Security – control not just what comes in, but also what goes out While internally we will store elements in sequences, we will not return Sequence, because 2 The guidelines in this exercise are made up for the purpose of teaching object-oriented programming and Java. They do not represent political, social, or religious views of the OOP course staff or the Hebrew University. OOP - exercise 1 – Family Tree: page 11 of 14
user might add elements bypassing the container object. Since we only want users to read from our data structures, we will expose the minimum interface that allows to do that, in our case we will use java.util.Iterable. 3.6 Test your family We are almost done. Create a oop.ex1.test.family package and create a FamilyTest file there with a static main method that will creates a family with at least 10 members and prints them out – names, gender, birth dates and death dates, number of marriages and number of children. The print-out should be copied into the solution README. We provide you with a basic test for family functionality – oop.ex1.test.family.FamilySmokeTest, that reuses some utilities in oop.ex1.test package. After you finish the family implementation, FamilySmokeTest has to run successfully, producing following output: Test success rate = 7/7, results: [alive person: success, dead person: success, family: success, head of family: success, members of family: success, find in family: success, marriage: success]
The test you write can, but does not have to, make any use of the provided test.
4 Guidelines In this exercise you may use all interfaces and classes available in java.lang, but no others, except for classes introduced in class, like java.util.Iterator, java.util.Date, java.util.Comparator and java.util.Random. In particular, do not use any existing JDK (or other library) implementation of java.util.Collection. When importing classes, do it per individual class, do not use bulk import <package>.*. Static methods should also be imported individually, except in enums, where you are allowed to do static import <enum-class>.*. Also you are allowed to use java.util.Arrays.deepToString and java.util.Arrays.deepEquals utility methods. The submission guidelines require that you code runs w/o errors or warnings. Read the submission guidelines – they are binding! Submit a file named FamilyTree.jar, with the java files of your program (but NO class files), and a README file. Be sure to follow the submission and styling guidelines of the OOP course. Before submitting, pass your jar file through the filter ~oop/www/ex1/check_ex1 as follows: ~oop/www/ex1/check_ex1 ex1.jar. Using the code provided to you. Below is the list of packages and classes provided to you, some complete, some are templates for you to add implementation. Do not change the structure of the code: no renaming or moving of packages, public classes or public methods is allowed. Tests: you need to make sure that the tests which we supply to you pass. If they don't, it likely indicates a problem in your code. As for the tests that you have written – they will help you check your code, but are not going to be tested or run by the course staff, so you are free to implement them as you wish. Your grade will be affected only by the results of the provided test, and other tests that we (the staff) have written. OOP - exercise 1 – Family Tree: page 12 of 14
5 Getting help The goal of this exercise is not to deal with technical difficulties or struggle with problematic APIs. If you are stuck on something – ask. There will be two forums accessible from the course home page (http://www.cs.huji.ac.il/~oop), one for official announcements and one for exchanging information between students, where course staff will also participate. The announcements and clarifications in the official forum are binding, so make sure to check the forum frequently, but anything said in the students' forum should not be treated as an official response. Submission guidelines contain more details about forums, if you have a private question, submit it to [email protected]
6 Grading policies ●
Working according to Coding style guidelines - 20%
●
Design, README file - 30%
●
Correct implementation – 50%
7 References Data structures “Introduction to Algorithms, 2nd Edition” by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein (info) “Data Structures and Algorithms in Java, 4th Edition” by Michael T. Goodrich and Roberto Tamassia (info)
Design patterns “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides (info)
Java On-line documentation: http://java.sun.com/javase/reference/index.jsp Javadoc http://java.sun.com/j2se/javadoc/ Annotation http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html Iterator http://java.sun.com/docs/books/tutorial/collections/interfaces/collection.html#Iterator
OOP - exercise 1 – Family Tree: page 13 of 14
Exception http://java.sun.com/docs/books/tutorial/essential/exceptions/index.html Comparator http://java.sun.com/docs/books/tutorial/collections/interfaces/order.html Enum http://java.sun.com/docs/books/tutorial/java/javaOO/enum.html Date http://java.sun.com/docs/books/tutorial/i18n/format/dateintro.html Random http://java.sun.com/developer/TechTips/2000/tt1107.html#tip1
OOP - exercise 1 – Family Tree: page 14 of 14