Naked Objects
By Richard Pawson and Robert Matthews

Appendix B: Code example

The following is the complete code for the Booking class as defined for the ECS application. The complete code for the application can be downloaded from our website. This example shows how a naked object is defined within the framework and demonstrates most of the techniques covered in this book.

Booking.java
package ecs.delivery;

import org.nakedobjects.object.AbstractNakedObject;
import org.nakedobjects.object.Title;
import org.nakedobjects.object.control.About;
import org.nakedobjects.object.control.ActionAbout;
import org.nakedobjects.object.control.FieldAbout;
import org.nakedobjects.object.control.ProgrammableAbout;
import org.nakedobjects.object.value.Date;
import org.nakedobjects.object.value.TextString;
import org.nakedobjects.object.value.Time;


/* Every business object is created by subclassing AbstractNakedObject
 * and implementing the title method */
public class Booking extends AbstractNakedObject {
    /* The version number is required for serialization, used when
    * passing objects between the client and server. */
    private static final long serialVersionUID = 1L;

    /* The values we want the user to be able to change, via the
     * keyboard, are declared as NakedValue objects. */
    private final TextString reference;
    private final TextString status;
    private final Date date;
    private final Time time;

    /* All the associations between the booking and the other objects
     * are declared as other NakedObject objects. */
    private City city;
    private Customer customer;
    private Telephone contactTelephone;
    private Location pickUp;
    private Location dropOff;
    private PaymentMethod paymentMethod;

    /* The constructor is commonly used to set up the object, specifically
     * so all the value field objects are immediately avaialable. */
    public Booking() {
        /* Each value object is created via its default constructor. */
        reference = new TextString();
        /* The reference field is made read-only by assigning this FieldAbout. */
        reference.setAbout(FieldAbout.READ_ONLY);
        status = new TextString();
        status.setAbout(FieldAbout.READ_ONLY);
        date = new Date();
        time = new Time();
    }

    /* An aboutAction... methods control the action... method that matches it's
    * name. */
    public About aboutActionCheckAvailability() {
    	/* A ProgrammableAbout can be used to check a number of conditions. */
        ProgrammableAbout c = new ProgrammableAbout();

        /* Checks conditions and adjusts the About accordingly: if the
        * argument is false then the About is altered so that a call
        * to canUse returns a Veto. */
        c.makeAvailableOnCondition(!getStatus().isSameAs("Available"));
        c.makeAvailableOnCondition(!getDate().isEmpty());

        return c;
    }

    public About aboutActionConfirm() {
        ProgrammableAbout c = new ProgrammableAbout();

        /* This version of the makeAvailableOnCondition method also adds a
        * message to the Veto. */
        c.makeAvailableOnCondition(getStatus().isSameAs("Available"), 
                                   "Status must be 'Available'");

        return c;
    }

    public About aboutActionCopyBooking() {
        int sets = 0;

        sets += ((getCustomer() != null) ? 1 : 0);
        sets += ((getPickUp() != null) ? 1 : 0);
        sets += ((getDropOff() != null) ? 1 : 0);
        sets += ((getPaymentMethod() != null) ? 1 : 0);
        sets += ((getContactTelephone() != null) ? 1 : 0);

        /* An About can be conditionally created: if the argument is true then
         * the returned About enables the action; if false, it disables it. */
        return ActionAbout.enable(sets >= 3);
    }

    public About aboutActionReturnBooking() {
        ProgrammableAbout c = new ProgrammableAbout();

        /* A description can be added to the About to tell the user what
         * the action will do. */
        c.setDescription(
                "Creates a new Booking based on the current booking.  The new booking has the pick up amd drop off locations reversed.");
        c.makeAvailableOnCondition(getStatus().isSameAs("Confirmed"), 
                                   "Can only create a return based on a confirmed booking");

        /* If the About is still allowing the action then the action's
         * name will be changed. */
        c.changeNameIfAvailable("Return booking to " + getPickUp());

        return c;
    }

    public About aboutPickUp(Location newPickup) {
        ProgrammableAbout c = new ProgrammableAbout();

        c.setDescription("The location to pick up the customer from.");

        if ((newPickup != null) && (getCity() != null)) {
            if (newPickup.equals(getDropOff())) {
                c.makeUnavailable(
                        "Pick up must differ from the drop off location");
            } else {
                boolean sameCity = getCity().equals(newPickup.getCity());

                c.makeAvailableOnCondition(sameCity, 
                                           "Location must be in " + 
                                           getCity().title());
            }
        }

        return c;
    }

    /* Zero-parametered action methods are made available to the user via
    * the object's pop up menu.  */
    public void actionCheckAvailability() {
        /* The value field is accessed and its value changed. */
        getStatus().setValue("Available");
        objectChanged();
    }

    public void actionConfirm() {
        getStatus().setValue("Confirmed");

        /* The locations used are added to the customer's Locations field.
        * Note that the accessor methods are used to ensure that the objects
        * are loaded first. */
        getCustomer().associateLocations(getPickUp());
        getCustomer().associateLocations(getDropOff());

        if (getCustomer().getPreferredPaymentMethod() == null) {
            getCustomer().setPreferredPaymentMethod(getPaymentMethod());
        }
    }

    public Booking actionCopyBooking() {
        /* A new instance is created by calling the createInstance method.  This
        * ensures that the created method is always called and that the object
        * is made persistent. */
        Booking copiedBooking = (Booking) createInstance(Booking.class);

        copiedBooking.associateCustomer(getCustomer());
        copiedBooking.setPickUp(getPickUp());
        copiedBooking.setDropOff(getDropOff());
        copiedBooking.setPaymentMethod(getPaymentMethod());
        copiedBooking.setContactTelephone(getContactTelephone());

        /* By returning the object we ensure that the user gets it: it is 
        * displayed to the user in a new window. */
        return copiedBooking;
    }

    /* One-parametered action methods are also available to the user and are
    * invoked via drag and drop.  
    * 
    * When an action method is marked as static then it works for the
    * class rather than the object.  To invoke this method the user must
    * drop a customer onto the Booking class icon. */
    public static Booking actionNewBooking(Customer customer) {
        Booking newBooking = (Booking) createInstance(Booking.class);

        newBooking.setCustomer(customer);
        newBooking.setPaymentMethod(customer.getPreferredPaymentMethod());

        return newBooking;
    }

    /* The recommended ordering for the action methods can be specified
    * with the actionOrder method.   This will affect the order of the
    * menu items for this object. */
    public static String actionOrder() {
        return "Check Availability, Confirm, Copy Booking, Return Booking";
    }

    public Booking actionReturnBooking() {
        Booking returnBooking = (Booking) createInstance(Booking.class);

        returnBooking.associateCustomer(getCustomer());
        returnBooking.setPickUp(getDropOff());
        returnBooking.setDropOff(getPickUp());
        returnBooking.setPaymentMethod(getPaymentMethod());
        returnBooking.setContactTelephone(getContactTelephone());

        return returnBooking;
    }

    /* The associate method overrides the get/set and is called by the 
    * framework instead of the ordinary accessor methods.  They are used 
    * to set up complex or bidirectional associations.  This method delegates,
    * to the other class, the work to set up a bidirectional link.  */
    public void associateCustomer(Customer customer) {
        customer.associateBookings(this);
    }

    public void associateDropOff(Location newDropOff) {
        setDropOff(newDropOff);
        setCity(newDropOff.getCity());
    }

    public void associatePickUp(Location newPickUp) {
        setPickUp(newPickUp);
        setCity(newPickUp.getCity());
    }

    private long createBookingRef() {
        try {
            /* The object store provides the ability to create and maintain
            * numbered sequences, which are unique. */
            return getObjectStore().serialNumber("booking ref");
        } catch (org.nakedobjects.object.ObjectStoreException e) {
            return 0;
        }
    }

    /* The created method is called when the logical object is created, 
    * i.e. it is not called when an object is recreated from its persisted
    * data. */
    public void created() {
        status.setValue("New Booking");
        reference.setValue("#" + createBookingRef());
    }

    /* The dissociate method mirrors the associate method and is called 
    * when the user tries to remove a reference. */
    public void dissociateCustomer(Customer customer) {
        customer.dissociateBookings(this);
    }

    /* The recommended order for the fields to be presented to the user  
    * can be specified by the fieldOrder method. */
    public static String fieldOrder() {
        return "reference, status, customer, date, time, pick up, drop off, payment method";
    }

    /* Each association within an object requires a get and a set method.
     * The get method simply returns the associated object's reference after 
     * it has ensured that the object has been loaded into memory.
     */
    public City getCity() {
        resolve(city);

        return city;
    }

    public Telephone getContactTelephone() {
        resolve(contactTelephone);

        return contactTelephone;
    }

    public Customer getCustomer() {
        resolve(customer);

        return customer;
    }

    /* Each value field only has a get method, which returns the value's
     * reference.  No set is required as the value objects must be a integral
     * part of the naked object. */
    public final Date getDate() {
        return date;
    }

    public Location getDropOff() {
        resolve(dropOff);

        return dropOff;
    }

    public PaymentMethod getPaymentMethod() {
        resolve(paymentMethod);

        return paymentMethod;
    }

    public Location getPickUp() {
        resolve(pickUp);

        return pickUp;
    }

    public final TextString getReference() {
        return reference;
    }

    public final TextString getStatus() {
        return status;
    }

    public final Time getTime() {
        return time;
    }

    /* The association has a set method so a reference can be passed to 
     * the object to set up the association.  As the object has now changed 
     * it also must be notified.*/
    public void setCity(City newCity) {
        city = newCity;
        objectChanged();
    }

    public void setContactTelephone(Telephone newContactTelephone) {
        contactTelephone = newContactTelephone;
        objectChanged();
    }

    public void setCustomer(Customer newCustomer) {
        customer = newCustomer;
        objectChanged();
    }

    public void setDropOff(Location newDropOff) {
        dropOff = newDropOff;
        objectChanged();
    }

    public void setPaymentMethod(PaymentMethod newPaymentMethod) {
        paymentMethod = newPaymentMethod;
        objectChanged();
    }

    public void setPickUp(Location newPickUp) {
        pickUp = newPickUp;
        objectChanged();
    }

    /* The title method generates a title string for the object that will 
    * be used when the object is displayed.  This title should identify the 
    * object to the user. */
    public Title title() {
        /* A Title object is normally retrieved from one of the object's 
        * field (all Naked objects can return a Title).  All of the title's
        * methods return the same Title object. */
        return reference.title().append(status);
    }
}