Java programming course: 17.1 More User Interface Developments


More User Interface Developments

This section continues the previous one to develop some additional interacting graphical components and panels.

In this section you will learn:

  • How to develop some additional types of interacting components and panels
  • How to prepare the core system for use by the graphical user interface
  • How to define a scrollable, selectable hierarchical tree of objects

Interacting components and panels for managing animals

This section continues the user interface development by creating the classes necessary to manage the addition, modification and removal of animals in the zoo. The classes will follow the same pattern as used in the previous section for zookeepers, although some different components will be used in certain places.

This part of the application will look like this:

Zoo Administrator frame

Preparing the core system

Just as was done for the zookeeper user interface components, the animal components will communicate with the core system through the ZooAdministrator class. You may recall that Animal objects are being stored inside Pen objects, and that ZooAdministrator has defined three of the latter named lionPen, monkeyPen and penguinPen. Although this course is, for simplicity, sticking to just these three types of animal, it makes sense to design the software to make it easier to enhance in the future for many more animal pens and types. To this end, rather than using three individually named Pen objects you will store a Collection of Pen objects.

In ZooAdministrator replace the following line:

private Pen lionPen, monkeyPen, penguinPen; 

With this:

private Collection<Pen> pens; 

In the constructor you need to instantiate an empty collection before the call to createExamplePens():

pens = new TreeSet<Pen>(); 
  • The collection is sorted so that the pens will be listed alphabetically.

You should define a method that returns the collection:

public Collection<Pen> getPens() {
    return Collections.unmodifiableCollection(pens);
}
 

For the methods that generate sample data, since the individual Pen objects are no longer defined you can pass a reference to them as arguments:

private void acquireLions(Pen lionPen) {
    acquireAnimal(new Lion("Leo", Gender.MALE, 2), lionPen);
    acquireAnimal(new Lion("Mr Fuzz", Gender.MALE, 2), lionPen);
    acquireAnimal(new Lion("Bazooka", Gender.FEMALE, 5), lionPen);
}
    
private void acquireMonkeys(Pen monkeyPen) {
    acquireAnimal(new Monkey("Bonzo", Gender.MALE, 5), monkeyPen);
    acquireAnimal(new Monkey("Norti", Gender.FEMALE, 5), monkeyPen);
    acquireAnimal(new Monkey("Hairball", Gender.MALE, 4), monkeyPen);
}
    
private void acquirePenguins(Pen penguinPen) {
    acquireAnimal(new Penguin("Sammy", Gender.FEMALE, 1), penguinPen);
    acquireAnimal(new Penguin("Oswald", Gender.MALE, 2), penguinPen);
    acquireAnimal(new Penguin("Percy", Gender.MALE), penguinPen);
    acquireAnimal(new Penguin("Petra", Gender.FEMALE, 2), penguinPen);
}
 

Now modify the createExamplePens() method to create the three required pens and add each to the collection:

private void createExamplePens() {
    Pen lionPen = new Pen("Lion Lane");
    acquireLions(lionPen);
    pens.add(lionPen);

    Pen monkeyPen = new Pen("Monkey Mews");
    acquireMonkeys(monkeyPen);
    pens.add(monkeyPen);

    Pen penguinPen = new Pen("Penguin Parade");
    acquirePenguins(penguinPen);
    pens.add(penguinPen);
}
 

In the feedingTime() method you need to replace these lines:

animals.addAll(lionPen.getAnimals());
animals.addAll(monkeyPen.getAnimals());
animals.addAll(penguinPen.getAnimals());
 

With these:

for (Pen pen : pens) {
    animals.addAll(pen.getAnimals());
}
 

Because the zoo is restricting itself to only three types of animal, it will prove helpful to define an enum for these types. Create a new enum inside the Animal class called Type:

public abstract class Animal implements Comparable<Animal> {
    
    // The allowed animal types
    public enum Type {LION, MONKEY, PENGUIN};

    ... rest of class omitted ...
 

It may be useful for client objects to easily find out what type a particular Animal object is, so define the following abstract getType() method in Animal:

// Return the animal's type
public abstract Animal.Type getType();
 

Now override this method in Lion to return the LION enum value:

@Override
public Animal.Type getType() {
    return Animal.Type.LION;
}
 

Now override this method in Monkey to return the MONKEY enum value:

@Override
public Animal.Type getType() {
    return Animal.Type.MONKEY;
}
 

Now override this method in Penguin to return the PENGUIN enum value:

@Override
public Animal.Type getType() {
    return Animal.Type.PENGUIN;
}
 

Factory method

A useful technique when various subtypes of a class can be created is to provide a static factory method that determines what needs to be created and returns an object of that type. In the Animal class declare this method:

// Factory method to create an animal of a specified type
static Animal create(Animal.Type type, String name,
                     Gender gender, int age) {
    Animal animal = null;
    if (type.equals(Animal.Type.LION)) {
        animal = new Lion(name, gender, age);

    } else if (type.equals(Animal.Type.MONKEY)) {
        animal = new Monkey(name, gender, age);

    } else {
        animal = new Penguin(name, gender, age);
    }
    return animal;
}
 

By defining the above method, it makes it much easier for client objects to create a particular type of object without having to code their own if...else... statements. The method is static because it doesn't operate on any existing Animal object. You will make use of this method later in this lesson.

It would be prudent here to beef up the validation of new animals. Currently, there is only a validation method for the age, so you will define new methods to validate the name and gender:

protected void validateName(String name) {
    if (name.isEmpty()) {
        throw new IllegalArgumentException("Name must be specified");
    }
}
    
protected void validateGender(Gender gender) {
    if (gender == null) {
        throw new IllegalArgumentException("Gender must be specified");
    }
}
 

Call the above methods from the individual setter methods:

void setName(String name) {
    validateName(name);
    this.name = name;
}

void setGender(Gender gender) {
    validateGender(gender);
    this.gender = gender;
}
 

Define a validate() method that invokes the individual validation methods:

protected void validate(String name, Gender gender, int age) {
    validateName(name);
    validateGender(gender);
    validateAge(age);
}
 

Call validate() from the constructor:

Animal(String name, Gender gender, int age) {
    validate(name, gender, age);
       
    this.name = name;
    this.gender = gender;
    this.age = age;
    dateAdmitted = new Date(); // today's date is assumed

    // Add this animal's age to the combined age total
    combinedAge += this.age;
}
 

Because certain classes will be interested whenever an animal is added, changed or removed you can define an AnimalEvent class and AnimalListener interface in the virtualzoo.core.event package.

First, the AnimalEvent class:

package virtualzoo.core.event;

import virtualzoo.core.*;
import java.util.*;

public class AnimalEvent extends EventObject {
    
    public AnimalEvent(Animal animal) {
        super(animal);
    }
    
    public Animal getAnimal() {
        return (Animal) getSource();
    }
    
}
 

The AnimalListener interface:

package virtualzoo.core.event;

import java.util.*;


public interface AnimalListener extends EventListener {
    
    public void animalCreated(AnimalEvent event);
    public void animalChanged(AnimalEvent event);
    public void animalRemoved(AnimalEvent event);
    
}
 

In ZooAdministrator you need an instance variable to store a collection of AnimalListener objects:

private Collection<AnimalListener> animalListeners; 

Instantiate the collection inside the constructor:

animalListeners = new ArrayList<AnimalListener>(); 

And methods to add or remove AnimalListener objects, and ones to fire the event changes:

public void addAnimalListener(AnimalListener listener) {
    animalListeners.add(listener);
}
    
public void removeAnimalListener(AnimalListener listener) {
    animalListeners.remove(listener);
}
    
private void fireAnimalCreated(Animal animal) {
    AnimalEvent event = new AnimalEvent(animal);
    for (AnimalListener listener : animalListeners) {
        listener.animalCreated(event);
    }
}
    
private void fireAnimalChanged(Animal animal) {
    AnimalEvent event = new AnimalEvent(animal);
    for (AnimalListener listener : animalListeners) {
        listener.animalChanged(event);
    }
}
    
private void fireAnimalRemoved(Animal animal) {
    AnimalEvent event = new AnimalEvent(animal);
    for (AnimalListener listener : animalListeners) {
        listener.animalRemoved(event);
   }
}
 

With the above in place you can define a createAnimal() method that adds a new animal into the specified pen:

public Animal createAnimal(Animal.Type animalType,
                           Pen pen,
                           String name,
                           Gender gender,
                           int age) throws ValidationException {
    try {
        Animal animal = Animal.create(animalType, name, gender, age);
        acquireAnimal(animal, pen);
        fireAnimalCreated(animal);
        return animal;
    } catch (IllegalArgumentException ex) {
        throw new ValidationException(ex.getMessage());
    }
}
 
  • Note the use of the static factory method create() you defined earlier, which will return either a Lion, Monkey or Penguin as appropriate

You need a changeAnimal() method to update an animal's details:

public void changeAnimal(Animal animal,
                         Pen pen,
                         String name,
                         Gender gender,
                         int age) throws ValidationException {
    try {
        animal.setName(name);
        animal.setGender(gender);
        animal.setAge(age);
        if (! animal.getPen().equals(pen)) {
            relinquishAnimal(animal);
            acquireAnimal(animal, pen);
        }
        fireAnimalChanged(animal);
    } catch (IllegalArgumentException ex) {
        throw new ValidationException(ex.getMessage());
    }
}
 
  • Note that there is no argument for the animal's type, since once set through the createAnimal() method it cannot be amended. (If you need to amend this because it was entered in error, then the user would need to remove and recreate the animal)
  • Note also the check to see whether the animal has been moved to a different pen; if so, the relinquishAnimal() method removes it from its current pen and acquireAnimal() puts it into the new one

You need a removeAnimal() method to remove an animal:

public void removeAnimal(Animal animal) {
    relinquishAnimal(animal);
    fireAnimalRemoved(animal);
}
 

You need a getAnimals() method that returns a collection of animals in a particular pen:

public Collection<Animal> getAnimals(Pen pen) {
    return Collections.unmodifiableCollection(pen.getAnimals());
}
 


Print
×
Stay Informed

When you subscribe, we will send you an e-mail whenever there are new updates on the site.

Related Posts

 

Comments

No comments made yet. Be the first to submit a comment
Monday, 27 October 2025

Captcha Image