Adding support for Java 5 enums to Naked Objects – part 2

In the previous post we saw how to write a FacetFactory so that Naked Objects treats enums as value types. Let’s now complete the story by also showing how to provide drop-down lists for any properties or action parameters of that type.

We’ll start by defining a new ChoicesFacet, to represent the values of the enum. This will be attached to the enum’s NakedObjectSpecification:

package org.nakedobjects.metamodel.facets;

import org.nakedobjects.metamodel.adapter.NakedObject;

public interface ChoicesFacet extends Facet {

    /**
     * Gets a set of choices for this object.
     */
    public Object[] getChoices(NakedObject adapter);
}

Standard fare is to also provide an abstract adapter:

package org.nakedobjects.metamodel.facets;

public abstract class ChoicesFacetAbstract extends FacetAbstract implements ChoicesFacet {

    public static Class type() {
        return ChoicesFacet.class;
    }

    public ChoicesFacetAbstract(FacetHolder holder) {
        super(type(), holder, false);
    }
}

And finally, let’s have an implementation for enums:

package org.nakedobjects.metamodel.facets;

import org.nakedobjects.metamodel.adapter.NakedObject;

public class ChoicesFacetEnum extends ChoicesFacetAbstract {

    private Object[] choices;

    public ChoicesFacetEnum(final FacetHolder holder, final Object[] choices) {
        super(holder);
        this.choices = choices;
    }

    @Override
    public Object[] getChoices(NakedObject adapter) {
        return choices;
    }
}

Let’s now revisit EnumFacetFactory and have it install this facet:

package org.nakedobjects.metamodel.facets;

import org.nakedobjects.metamodel.value.ValueUsingValueSemanticsProviderFacetFactory;

public class EnumFacetFactory extends ValueUsingValueSemanticsProviderFacetFactory {

  public EnumFacetFactory() {
    super(ChoicesFacet.class);
  }

  @Override
  public boolean process(
      Class cls, MethodRemover methodRemover,
      FacetHolder holder) {

    if (!cls.isEnum()) {
      return false;
    }

    addFacets(new EnumValueSemanticsProvider(holder, cls, getConfiguration(), getSpecificationLoader(), getRuntimeContext()));
    FacetUtil.addFacet(new ChoicesFacetEnum(holder, cls.getEnumConstants()));
    return true;
  }
}

The change here is the penultimate line in process() which adds the new ChoicesFacetEnum.

To get the properties and action parameters to display these choices, we need a further FacetFactory. This one will install a PropertyChoicesFacet and an ActionParameterChoicesFacet onto the property and action parameters respectively. We start by defining new subclasses of these facets:

package org.nakedobjects.metamodel.facets;

import org.nakedobjects.metamodel.adapter.NakedObject;
import org.nakedobjects.metamodel.facets.properties.choices.PropertyChoicesFacetAbstract;
import org.nakedobjects.metamodel.spec.NakedObjectSpecification;
import org.nakedobjects.metamodel.specloader.internal.peer.NakedObjectAssociationPeer;

public class PropertyChoicesFacetDerivedFromChoicesFacet extends PropertyChoicesFacetAbstract {

    public PropertyChoicesFacetDerivedFromChoicesFacet(FacetHolder holder) {
        super(holder);
    }

    @Override
    public Object[] getChoices(NakedObject adapter) {
        FacetHolder facetHolder = getFacetHolder();
        NakedObjectAssociationPeer noap = (NakedObjectAssociationPeer) facetHolder;
        NakedObjectSpecification noSpec = noap.getSpecification();
        ChoicesFacet choicesFacet = noSpec.getFacet(ChoicesFacet.class);
        if (choicesFacet == null)
            return new Object[0];
        return choicesFacet.getChoices(adapter);
    }
}

for properties, and:

public class ActionParameterChoicesFacetDerivedFromChoicesFacet extends ActionParameterChoicesFacetAbstract {

    public ActionParameterChoicesFacetDerivedFromChoicesFacet(FacetHolder holder) {
        super(holder);
    }

    public Object[] getChoices(NakedObject adapter) {
        FacetHolder facetHolder = getFacetHolder();
        NakedObjectActionParamPeer noapp = (NakedObjectActionParamPeer) facetHolder;
        NakedObjectSpecification noSpec = noapp.getSpecification();
        ChoicesFacet choicesFacet = noSpec.getFacet(ChoicesFacet.class);
        if (choicesFacet == null)
            return new Object[0];
        return choicesFacet.getChoices(adapter);
    }
}

for action parameters. The implementation of these two facets is similar: they delegate to the ChoicesFacet of their corresponding type if it exists.

Finally, we need the new FacetFactory to install these facets:

package org.nakedobjects.metamodel.facets;

import java.lang.reflect.Method;

import org.nakedobjects.metamodel.spec.feature.NakedObjectFeatureType;

public class PropertyAndParameterChoicesFacetDerivedFromChoicesFacetFacetFactory extends
    FacetFactoryAbstract {

    public PropertyAndParameterChoicesFacetDerivedFromChoicesFacetFacetFactory() {
        super(NakedObjectFeatureType.PROPERTIES_AND_PARAMETERS);
    }

    @Override
    public boolean process(Class cls, Method method,
            MethodRemover methodRemover, FacetHolder holder) {

        Class returnType = method.getReturnType();

        if (!returnType.isEnum()) {
            return false;
        }

        FacetUtil.addFacet(new PropertyChoicesFacetDerivedFromChoicesFacet(holder));
        return true;
    }

    @Override
    public boolean processParams(Method method, int paramNum, FacetHolder holder) {
        Class paramType = method.getParameterTypes()[paramNum];

        if (!paramType.isEnum()) {
            return false;
        }

        FacetUtil.addFacet(new ActionParameterChoicesFacetDerivedFromChoicesFacet(holder));
        return true;
    }
}

Last but not least, we need to register this new FacetFactory alongside the one described in the previous post:

nakedobjects.reflector.facets.include=\
    org.nakedobjects.metamodel.facets.EnumFacetFactory,\
    org.nakedobjects.metamodel.facets.PropertyAndParameterChoicesFacetDerivedFromChoicesFacetFacetFactory

All that remains is to see what this looks like. Here in full is the Stock entity, where Stock has a StockType:

package com.mycompany.myapp.dom;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.annotation.Optional;

public class Stock extends AbstractDomainObject implements Loanable {

    public String title() {
        return getName();
    }

    // {{ Name
    private String name;
    @MemberOrder(sequence = "1")
    public String getName() {
      return name;
    }
    public void setName(final String name) {
      this.name = name;
    }
    // }}

    // {{ Quantity
    private int quantity;
    @MemberOrder(sequence = "2")
    public int getQuantity() {
      return quantity;
    }

    public void setQuantity(final int quantity) {
      this.quantity = quantity;
    }
    // }}

    // {{ StockType
    private StockType stockType;

    @Optional
    @MemberOrder(sequence = "2")
    public StockType getStockType() {
      return stockType;
    }

    public void setStockType(final StockType stockType) {
      this.stockType = stockType;
    }
    // }}
}

… and a StockRepository:

package com.mycompany.myapp.dom;

import java.util.List;

import org.nakedobjects.applib.AbstractFactoryAndRepository;
import org.nakedobjects.applib.annotation.MemberOrder;

public class StockRepository extends AbstractFactoryAndRepository {

  // {{ all
  @MemberOrder(sequence = "1")
  public List all() {
    return allInstances(Stock.class);
  }
  // }}

  // {{ create
  @MemberOrder(sequence = "2")
  public Stock create(StockType stockType) {
    Stock stock = newTransientInstance(Stock.class);
    stock.setStockType(stockType);
    return stock;
  }
  // }}
}

If we bring up the dialog for the repository’s create() action, we’ll see there is a drop-down list box:

action-param-enum-dropdown

and similarly, there’s a drop-down for the StockRepository:

property-enum-dropdown

We’ll be merging the above into facets within Naked Objects 4.1, but if you can’t wait until that is released, by all means copy and paste and add to your own projects in the meantime. Enjoy!

Advertisement

Posted on February 28, 2010, in apache isis. Bookmark the permalink. 7 Comments.

  1. I have to do all this just to have enum support? It isn’t the solution!

  2. From what I understand this is a proof of concept, they will add support for enums in the 4.1 release.

  3. @Alexander,
    This *is* the solution! Perhaps it isn’t clear from the post, but setting up those facet factories etc is a one-off, and will support any generic.

    @Giorgio,
    I wouldn’t call this a proof-of-concept, this is pretty much the code that will go into 4.1. I’ve posted it here early (a) for anyone who’s missing enums, and (b) to demonstrate how the NO programming model can be extended.

    Cheers
    Dan

  4. Alan X Franklin

    Not sure if is just an entry creation typo, or blog framework error, but working through the code, I see two copies of PropertyChoicesFacetDerivedFromChoicesFacet and no code for ActionParameterChoicesFacetDerivedFromChoicesFacet.

    Damm, I’m going to have to put my brain in gear and try to work it out…..

  1. Pingback: Adding support for Java 5 enums to Naked Objects | Domain Driven Design using Naked Objects

  2. Pingback: Simplifying inheritance hierarchies using powertypes and Java 5 enums | Domain Driven Design using Naked Objects

Leave a Reply

Fill in your details below or click an icon to log in:

Gravatar
WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 109 other followers