On this page:
3.1 Adoption Protocol
3.2 Initialization and Connecting Clients
3.3 Interfaces
3.4 Testing Task
3.5 Delivery
8.9

3 — Adopting Pets

Due Thursday, 25 January 2024, 11:59:59pm

Purpose

The purpose of this milestone is to implement a portion of the primary functionality of the Husky Animal Lovers Park.

The first part of functionality allows hopeful pet-parents to view the animals at the shelter and (hopefully) adopt one. This interaction consists of several steps:
  1. A client enters the shelter;

  2. They walk through, viewing one animal at a time;

  3. If an animal seems like a potential match, the client can take the animal out of its enclosure to get to know them better, and return them after a time;

  4. Finally, when everything works out, they can request to adopt their new furry best friend.

Of course, the client may leave the shelter at any time without adopting a pet.

3.1 Adoption Protocol

To enable these potential interactions at HALP, you will create classes that implement a protocol between three parties: the client, the shelter, and an object representing a particular client’s progress through their visit.

The first part is the simplest: a client entering the shelter and beginning the process of viewing the animals:

Adoption Entry:

        Client                                 ShelterEntry

          |                                      |

          |                                      |

          |             viewAnimals()            |

          |------------------------------------->|

          |              PetViewer               |

          |<=====================================|

          |                                      |

          |                                      |

In this diagram, the dashed arrow (>) represents the party on the left calling a method of the object on the right, and the double-dashed (<==) represents the method’s return and its result type, if any.

The client then interacts with the PetViewer in the following manner:
  • They call the viewNext method to look at the next pet, if there are any remaining available that they have not seen yet.

  • If viewNext returns Optional.empty(), the client has viewed all available pets and the interaction is over. (If the client wants they can call viewAnimals again to get another PetViewer.)

  • If the result of viewNext is not empty, then the client may optionally call the tryPlay method to request a one-on-one interaction with the animal. Since it’s possible another client may have taken the animal out of its enclosure in the meantime, either by calling tryPlay or tryAdopt, the method returns a boolean. A true value indicates that the client succeed in taking the animal out for some play time.

  • If everything goes well, the client can request to adopt the current animal by calling the tryAdopt method. Like for tryPlay, the animal may no longer be available for adoption. The method returns true if the adoption succeeded and false otherwise.

  • If adoption succeeded, the interactions between the client and the PetViewer are finished.

  • Otherwise, the client may repeatedly call viewNext. If they had previously taken an animal to play with, a call to viewNext implies that they have returned that animal to its (temporary) home.

  • At any point, the client may call the finished method on the PetViewer, indicating that they are done looking at the animals (at least for now). Like above, a call to finished implies that they have returned that animal to its (temporary) home.

The following diagram represents the core of these interactions:

PetViewer Protocol:

        Client                                 PetViewer

          |                                      |

          |                                      |

       +->|             viewNext()               |

       |  |------------------------------------->|

       |  |        Optional<AdoptablePet>        |

       |  |<=====================================|

       |  |                                      |

       |  |        if result is not empty:       |

       |  |                                      |

       |  |                                      |

       |  |          (optionally)                |

       |  |                                      |

       |  |            tryPlay()                 |

       |  |------------------------------------->|

       |  |             boolean                  |

       |  |<=====================================|

       |  |                                      |

       |  |                                      |

       |  |          (optionally)                |

       |  |                                      |

       |  |            tryAdopt()                |

       |  |------------------------------------->|

       |  |             boolean                  |

       |  |<=====================================|

       |  |                                      |

       |  |                                      |

       +--|(unless adoption succeeded)           |

          |                                      |

The first task is to design and implement classes implementing the ShelterEntry and PetViewer interfaces. These interfaces are provided to you below (see Interfaces). Your implementation should take whatever steps necessary to ensure it is safe to use with numerous concurrent clients.

Your implementation should also ensure the following properties:
  • No client can view, play with, or adopt a pet while another client is playing with that animal.

  • Likewise, once an animal is adopted by one client, no other clients can interact with it.

  • Multiple clients may view the same animal at the same time (assuming none have taken it out of its enclosure.)

3.2 Initialization and Connecting Clients

Implement the class com.neu.halp.center.Main that has (at least) the public method:

public static ClientEntry initialize(Reader configReader) {

  ...

}

This method will be called with a Reader from which you can obtain the JSON Configuration.

The returned ClientEntry is an object that allows clients to connect to the system. It has one method:

void connectClient(Consumer<ShelterEntry> client);

The connectClient method simply passes a ShelterEntry to a client Consumer to interact with according to the above protocol. However, connectClient should create and run a Thread for each client.

This setup mimics the behavior we would use to allow clients to connect over a network but avoids a great deal of the ceremony and boilerplate doing so would require.

3.3 Interfaces

The attached interfaces for AdoptablePet, ClientEntry, PetViewer, and ShelterEntry correspond to the protocols described above. Also: the PetType enum. Include them in your project under the com.neu.halp.client package. Do not modify them.

3.4 Testing Task

To aid in testing, implement (at least one) potential client behavior. The client will eagerly try to adopt a pet. It should optain a PetViewer and try to meet then adopt a pet as quickly as it can. The test client will report a result, an Optional<AdoptablePet> describing the pet it managed to adopt (if any). Concretely, it should:

  1. Obtain a PetViewer;

  2. Call viewNext;

  3. If the result of viewNext is present, call tryPlay; otherwise stop with an Optional.empty result.

  4. If tryPlay succeeds, call tryAdopt;

  5. If tryAdopt succeeds, stop;

  6. Otherwise, go back to step 2;

Finally, the client will take a parameter describing its patiencehow many animals it can view without adopting before giving up and calling the finished method.

Implementation Task

Implement the com.neu.halp.test.EagerClientFactory class. The class must include the following public methods:

public EagerClientFactory() {

   ...

}

public TestClient newClient(int patience) {

    ...

}

Interface

The attached TestClient interface defines two methods:
  • getFinished, reporting a boolean that returns true if the client is finished running; and

  • getResult, returning an Optional<AdoptablePet> describing either the pet the client adopted, or is empty if no adoption attempt succeeded.

Include the interface in your project in the com.neu.halp.test package. Again, do not modify the interface.

3.5 Delivery

Your project should generate a JAR named Milestone2.jar Milestone3.jar (see Java, Maven; there is no need to specify a mainClass for this milestone) including the Main and EagerClientFactory classes described above. Building the project (such as with mvn package) should put the executable in a folder named target at the top level of your repo.