Print
Extending Nuts

Nut class

Nuts is extensible because users can define their own tags by implementing Nut classes. There are certain rules to follow when defining Nut classes:

  1. All Nut classes should extend the Nut class.
  2. A Nut class is a Java Bean because it needs a public default constructor and setters to set its attributes.
  3. For each named "subelement", an "addSubelement" method with one parameter should be defined.
  4. The parameter type of the "addSubelement" method should be a Nut class too.
  5. The Nut class of the named sub-element is automatically loaded and doesn't have to be registered. Though explicitly loaded Nut class under the same name will override the parameter type.
  6. For an anonymous sub-element, whose tag name corresponds to a registered Nut class, an "add" method with one parameter should be defined.
  7. When more than one annonymous "add" method exist, the one with the most restrictive parameter type is chosen when a sub-element is added. i.e. it follows the Java method overloading mechanism.
  8. For a tag that expects one or more sub-elements who are themselves top-level tags, a "set(Object[])" method should be defined. The parameter type does not have to be Object[], it can be any array.
  9. A "eval()" method can optionally be defined. The return type can be anything. This method can be implemented to verify that the tag is properly configured, it is also used to return the final result of the tag.

There are many pre-defined helper classes to make the job of creating your own Nut class easier.

Writing my own

In the Overview section, we claimed that one can define his own "repeat" tag to repeatedly run a Component for x number of times. Here we'll just use this as an example to show the process of creating customized Nut class.

First, let's lay out the desired xml syntax. We hope we can do:

<repeat times="100">
  <ctor class="A" singleton="false"/>
</repeat>

This calls the constructor for 100 times.

We also want to allow using reference, such that:

<ctor id="a" class="A" singleton="false"/>
<repeat times="100" component="$a"/>

Secondly, let's analyze the syntax.

  • In order to support the "times" and "component" attribute, we need to have "setTimes(int)" and "setComponent(Component c)" methods.
  • Then, to support a sub-element, we should have a "add(Component)" method.
  • Finally, the tag describes yet another "component instantiation routine", which means it should evaluate to a Component.

Now let's start coding:

public class RepeatNut extends ComponentNut{
  private int times = -1;
  private Component action;
  public void setTimes(int times){
    this.times = times;
  }
  public void setComponent(Component action){
    this.action = action;
  }
  public void add(Component a){
    if(this.action!=null)
      raise("component already set");
    this.action = a;
  }
  public Component eval(){
    if(times<0)
      throw raise("attribute times should be set");
    if(action==null)
      throw raise("component should be set");
    return action.repeat(times);
  }
}

ComponentNut class extends from Nut. It can be inherited by any Nut class that evaluates to a Component.

The set(), setComponent(), setTimes() are straight-forward.

The eval() method calls Component.repeat() to run the components repeatedly.

As you may have noticed, there are many tediouos checkings to make sure things like "mandatory attributes should be set", "an attribute and a sub-element cannot be both set". Fortunately, there's a DelegatingNut super class that already handles most of these tediousness. By extending it, the RepeatNut class becomes:

public class RepeatNut extends DelegatingNut{
  private int times = -1;
  public void setTimes(int times){
    this.times = times;
  }
  public Component eval(){
    if(times<0)
      throw raise("attribute times should be set");
    final Component action = getMandatory();
    return action.repeat(times);
  }
}

Use it

after writing this RepeatNut class, (let's assume it is in com.ajoo package), inside the xml config file, we can load it using the <nut> tag:

<module name="my test">
<nut name="repeat" class="com.ajoo.RepeatNut"/>

<body>
  <repeat times="100">
    <ctor class="A"/>
  </repeat>
</body>

</module>

When this class is in a jar file that's not in the current class path, we can:

<module name="my test">
<nut name="repeat" class="com.ajoo.RepeatNut" classpath="/home/mynuts.jar"/>

<body>
  <repeat times="100">
    <ctor class="A"/>
  </repeat>
</body>

</module>

Life cycle for Nut objects

Here's how a Nut object is used:

  1. when the instantiation routine described by <repeat> is used, an object of RepeatNut is created by calling the default constructor.
  2. All the attribute values are evaluated and injected into the RepeatNut object using the setter methods.
  3. Sub-elements are evaluated sequentially.
  4. If a sub-element is another Nut class, Nut class is analyzed automatically. It does not need to be declared by the <nut> tag. An object of this Nut class is created following the same steps recursively.
  5. All sub-elements are added to the Nut object by calling the "addXXX" methods or "add" method if the tag name "tagName" doesn't correspond to a named "addTagName" method.
  6. If the Nut class supports a "set" method with one parameter whose type is array, all sub-elements are evaluated and grouped together as an array and then passed to this method.
  7. If a "eval()" method is present, it is invoked.
  8. If the return type of the method is void, the Nut object itself is considered as the evaluation result.
  9. For Nut object automatically loaded for sub-elements, the return value of the "eval()" method is ignored unless it returns object of the same Nut class.
  10. The Nut object is then discarded and subject to garbage-collection.

Summary

Extensible Nuts tag imakes a useful tool to deal with various dependency injection models in disparate systems. For example, we could implement the annotation based, Java EE 5 dependency injection model by creating a Ejb3Nut class that's aware of the Java EE 5 annotations. We then can override the default <bean> tag using this Ejb3Nut class. Without a single line of code changed in the core to be dependent on Java EE 5, Nuts is nicely extended to be Java EE 5 compliant.

The same technique is used in Nuts' Spring integration. Specific spring-aware Nut classes are built to handle Spring factory bean and various Spring's proprietary marker interfaces. These custom Nut classes are then registered to override the default <ctor>, <bean> tags and add Spring support.

Powered by Atlassian Confluence