This project has retired. For details please refer to its Attic page.
Transient Composites Tutorial
Zest™
Introduction
Tutorials
Javadoc
Samples
Core
Libraries
Extensions
Tools
Glossary 

Transient Composites Tutorial

Tip

Theses tutorials are based on actual code found in the tutorials/ directory of the Zest™ SDK sources. You should start your favorite editor and find the code related to this tutorial, run it and play with it.

Throughout this set of tutorials it will be shown how to create and work with Composites, which is the basic element in Zest™. We will refactor one HelloWorld class to take advantage of the various features in Zest™. These refactorings will make it easier to reuse parts of the class, and introduce new features without having to change existing code. We will also look at some of the existing classes, or Fragments, available in Zest™ that you can reuse so that you don’t have to write everything yourself.

Each tutorial step in this series starts with the result from the previous tutorial, so you can always look at the next tutorial step for guidance on what to do.

At the bottom of each tutorial step, the is Solutions section, which list the files you should have come to if you have followed the instructions.

If you want to reproduce what’s explained in this tutorial, remember to depend on the Core Runtime artifact that depends on Core API, Core SPI, Core Bootstrap and Core Functional & I/O APIs:

Table 5. Artifact

Group IDArtifact IDVersion

org.qi4j.core

org.qi4j.core.runtime

2.1


See the Depend on Zest™ in your build tutorial for details.

Step 1 - Interface Refactoring

Initial Hello World

This whole tutorial describes how to step-by-step modify a typical HelloWorld "application" into a full-fledged Zest™ Composite Oriented application. Here is the initial code of HelloWorld.

/**
 * Initial HelloWorld implementation. Everything is mixed up
 * into one class, and no interface is used.
 */
public class HelloWorld
{
    String phrase;
    String name;

    public String getPhrase()
    {
        return phrase;
    }

    public void setPhrase( String phrase )
        throws IllegalArgumentException
    {
        if( phrase == null )
        {
            throw new IllegalArgumentException( "Phrase may not be null " );
        }

        this.phrase = phrase;
    }

    public String getName()
    {
        return name;
    }

    public void setName( String name )
        throws IllegalArgumentException
    {
        if( name == null )
        {
            throw new IllegalArgumentException( "Name may not be null " );
        }

        this.name = name;
    }

    public String say()
    {
        return phrase + " " + name;
    }
}
Interface refactoring

In this step we start with a basic Java class, which when invoked will concatenate the two properties "phrase" and "name". If invoked with the properties set to "Hello" and "World" respectively it will hence return "Hello World".

Zest™ relies heavily on the use of interfaces. This makes it possible for an object to externally implement a number of interfaces which internally is backed by a number of Mixins, some of which you may have written yourself, and some of which may have been reused. This also makes it easy to introduce Modifiers (aka "interceptors", aka "advice"), which are Fragments which execute before and/or after the method on the Mixin is invoked.

The first task is therefore to refactor the code so that the method is implemented from an interface instead. We should then also separate the state into one interface and the behaviour into another. This will make things easier for us later when state and behaviour becomes implemented by separate Mixins.

Steps for this tutorial:

  • Refactor the class into interface and implementation.
  • Refactor the interface so that it extends one interface called HelloWorldBehaviour with behaviour and one called HelloWorldState with state (get/set methods).
Solution

If you have successfully completed the task, you should end up with the following artifacts;

HelloWorld.java

/**
 * This interface aggregates the behaviour and state
 * of the HelloWorld sub-interfaces. To a client
 * this is the same as before though, since it only
 * has to deal with this interface instead of the
 * two sub-interfaces.
 */
public interface HelloWorld
    extends HelloWorldBehaviour, HelloWorldState
{
}

HelloWorldState.java

/**
 * This interface contains only the state
 * of the HelloWorld object.
 * The exceptions will be thrown by Zest automatically if
 * null is sent in as values. The parameters would have to be declared
 * as @Optional if null is allowed.
 */
public interface HelloWorldState
{
    void setPhrase( String phrase )
        throws IllegalArgumentException;

    String getPhrase();

    void setName( String name )
        throws IllegalArgumentException;

    String getName();
}

HelloWorldBehaviour.java

/**
 * This interface contains only the behaviour
 * of the HelloWorld object.
 */
public interface HelloWorldBehaviour
{
    String say();
}

HelloWorldMixin.java

/**
 * This is the implementation of the HelloWorld
 * interface. The behaviour and state is mixed.
 */
public class HelloWorldMixin
    implements HelloWorld
{
    String phrase;
    String name;

    @Override
    public String say()
    {
        return getPhrase() + " " + getName();
    }

    @Override
    public void setPhrase( String phrase )
        throws IllegalArgumentException
    {
        if( phrase == null )
        {
            throw new IllegalArgumentException( "Phrase may not be null" );
        }

        this.phrase = phrase;
    }

    @Override
    public String getPhrase()
    {
        return phrase;
    }

    @Override
    public void setName( String name )
        throws IllegalArgumentException
    {
        if( name == null )
        {
            throw new IllegalArgumentException( "Name may not be null" );
        }

        this.name = name;
    }

    @Override
    public String getName()
    {
        return name;
    }
}

Next step is Step 2 - Creating a Transient Composite

Step 2 - Creating a Transient Composite

Previous step was Step 1 - Interface Refactoring.

In this step we will create a TransientComposite interface that ties all pieces together. The TransientComposite interface is a regular Java interface which extends the interfaces you want to expose from your domain model, and which uses various annotations to declare what Fragments to include. Fragments include Mixins, Concerns, SideEffects and Constraints. In this tutorial we will only use Mixins. When a TransientComposite is instantiated at runtime the framework will inspect the interface to determine what the TransientComposite instance should look like in terms of used Fragments.

In Zest™ all method parameters are considered mandatory unless marked as @Optional. Therefore you can remove the null checks in the Mixin. If a null value is passed in an exception will be thrown by Zest™.

Steps for this tutorial:

  • Create an interface that extends the domain interface HelloWorld and org.qi4j.api.composite.TransientComposite.
  • Add a @Mixins annotation to it with the name of the Mixin as argument.
Solution

These ones remain unchanged:

  • HelloWorld.java
  • HelloWorldBehaviour.java
  • HelloWorldState.java

HelloWorldComposite.java

/**
 * This Composite interface declares all the Fragments of the HelloWorld composite.
 * <p>
 * Currently it only declares one Mixin.
 * </p>
 */
@Mixins( HelloWorldMixin.class )
public interface HelloWorldComposite
    extends HelloWorld, TransientComposite
{
}

HelloWorldMixin.java

/**
 * This is the implementation of the HelloWorld
 * interface. The behaviour and state is mixed.
 */
public class HelloWorldMixin
    implements HelloWorld
{
    String phrase;
    String name;

    @Override
    public String say()
    {
        return getPhrase() + " " + getName();
    }

    @Override
    public void setPhrase( String phrase )
        throws IllegalArgumentException
    {
        if( phrase == null )
        {
            throw new IllegalArgumentException( "Phrase may not be null" );
        }

        this.phrase = phrase;
    }

    @Override
    public String getPhrase()
    {
        return phrase;
    }

    @Override
    public void setName( String name )
        throws IllegalArgumentException
    {
        if( name == null )
        {
            throw new IllegalArgumentException( "Name may not be null" );
        }

        this.name = name;
    }

    @Override
    public String getName()
    {
        return name;
    }
}

Next step is Step 3 - Mixins

Step 3 - Mixins

Previous step was Step 2 - Creating a Transient Composite.

In this step we refactor the Mixin from the previous steps into two, one which serves the behaviour interface and one which serves the state interface. This makes it possible to reuse the interfaces independently and also makes it easier to exchange one interface implementation with another. This also allows us to specify the new Mixins as default implementations of the interfaces by adding @Mixins annotations on them.

Steps for this tutorial:

  • Refactor the Mixin into one which implement the behaviour interface and one which implements the state interface. Use the @This injection annotation to allow the behaviour to access the state.
  • Add a @Mixins annotations on the behaviour and state interfaces which declare the Mixins as default implementations.
  • Remove the @Mixins annotation from the TransientComposite interface.
Solution

Only HelloWorld.java remains unchanged.

HelloWorldComposite.java

/**
 * This Composite interface declares all the Fragments
 * of the HelloWorld composite.
 * <p>
 * The Mixins annotation has been moved to the respective sub-interfaces.
 * The sub-interfaces therefore declare themselves what mixin implementation
 * is preferred. This interface could still have its own Mixins annotation
 * with overrides of those defaults however.
 * </p>
 */
public interface HelloWorldComposite
    extends HelloWorld, TransientComposite
{
}

HelloWorldBehaviour.java

/**
 * This interface contains only the behaviour of the HelloWorld object.
 * <p>
 * It declares what Mixin to use as default implementation.
 * </p>
 */
@Mixins( HelloWorldBehaviourMixin.class )
public interface HelloWorldBehaviour
{
    String say();
}

HelloWorldBehaviourMixin.java

/**
 * This is the implementation of the HelloWorld behaviour interface.
 * <p>
 * It uses a @This Dependency Injection
 * annotation to access the state of the Composite. The field
 * will be automatically injected when the Composite
 * is instantiated. Injections of resources or references
 * can be provided either to fields, constructor parameters or method parameters.
 * </p>
 */
public class HelloWorldBehaviourMixin
    implements HelloWorldBehaviour
{
    @This
    HelloWorldState state;

    @Override
    public String say()
    {
        return state.getPhrase() + " " + state.getName();
    }
}

HelloWorldState.java

/**
 * This interface contains only the state
 * of the HelloWorld object.
 * The exceptions will be thrown by Zest automatically if
 * null is sent in as values. The parameters would have to be declared
 * as @Optional if null is allowed.
 */
@Mixins( HelloWorldStateMixin.class )
public interface HelloWorldState
{
    void setPhrase( String phrase )
        throws IllegalArgumentException;

    String getPhrase();

    void setName( String name )
        throws IllegalArgumentException;

    String getName();
}

HelloWorldStateMixin.java

/**
 * This is the implementation of the HelloWorld
 * state interface.
 */
public class HelloWorldStateMixin
    implements HelloWorldState
{
    String phrase;
    String name;

    @Override
    public String getPhrase()
    {
        return phrase;
    }

    @Override
    public void setPhrase( String phrase )
    {
        this.phrase = phrase;
    }

    @Override
    public String getName()
    {
        return name;
    }

    @Override
    public void setName( String name )
    {
        this.name = name;
    }
}

Next step is Step 4 - Concerns

Step 4 - Concerns

Previous step was Step 3 - Mixins.

In this step we refactor the mixin from the previous steps so that the result of the say() method is modified to be prefixed with "Simon says:". To do this we need to implement a Concern for the say() method. Concerns are a type of Modifier which modify the behaviour of the methods in Mixins. They do this by intercepting the invocation of the TransientComposite. This allows them to change the invocation parameters, return their own values or throw their own exceptions, and do other things which directly affect the invocation of the method.

Concerns should not perform any side-effects, such as updating state in another TransientComposite, Mixin or similar. Any side-effects are done in SideEffects, which is another type of Modifier, which are allowed to perform side-effects but, in contrast to Concerns, cannot change the parameters or in any other way change the result of the invocation.

Concerns are implemented in one of two ways: either create a class which directly implements the interface whose methods should be modified, or create a generic Modifier by implementing the InvocationHandler interface (or subclass GenericConcern which does this for you). Add an @ConcernFor dependency injection, as a field, constructor parameter or method parameter, which has the same type as the interface the Concern implements. When the TransientComposite is invoked the Concern will be called, allowing it to perform it’s work. If the call should proceed, then invoke the method again on the injected object. The preferred way to do all of this is to subclass ConcernOf which does all of this for you.

Concerns are applied by adding an @Concerns annotation on the TransientComposite, the domain interface, or the Mixin implementation. Any of these works, and where to put it is a matter of design choice.

Steps for this tutorial:

  • Create a typed concern, implement the HelloWorldBehaviour and let it modify the result of the base method by prefix the result with "Simon says:".
  • Add an @Concerns annotation on the HelloWorldBehaviourMixin which references the Concern class.
Solution

If you have successfully completed the task, you should end up with the following artifacts;

These ones remain unchanged:

  • HelloWorld.java
  • HelloWorldBehavior.java
  • HelloWorldComposite.java
  • HelloWorldState.java
  • HelloWorldStateMixin.java

HelloWorldBehaviourMixin.java

/**
 * This is the implementation of the HelloWorld
 * behaviour interface.
 * <p>
 * It uses a @This Dependency Injection
 * annotation to access the state of the Composite. The field
 * will be automatically injected when the Composite
 * is instantiated. Injections of resources or references
 * can be provided either to fields, constructor parameters or method parameters.
 * </p>
 */
@Concerns( HelloWorldBehaviourConcern.class )
public class HelloWorldBehaviourMixin
    implements HelloWorldBehaviour
{
    @This
    HelloWorldState state;

    @Override
    public String say()
    {
        return state.getPhrase() + " " + state.getName();
    }
}

HelloWorldBehaviourConcern.java

/**
 * This is a concern that modifies the mixin behaviour.
 */
public class HelloWorldBehaviourConcern
    extends ConcernOf<HelloWorldBehaviour>
    implements HelloWorldBehaviour
{
    @Override
    public String say()
    {
        return "Simon says:" + next.say();
    }
}

Next step is Step 5 - Constraints

Step 5 - Constraints

Previous step was Step 4 - Concerns.

In this step we will look at how to use Constraints. When we pass parameters to methods in regular Java code the only restriction we can make is to denote a type. Any other constraints on the input value, such as whether the parameter is optional, integer ranges, string regular expressions, and so on, cannot be expressed, and so we have to put this into Javadoc, and then manually add these checks in the implementation class.

In Zest™ there is the option to use Constraints, which are further restrictions on the parameters. This is implemented by having an annotation that describes what the Constraint does, and then an implementation class that checks whether a specific value fulfills the Constraint or not.

Note

The previous steps had a dependency to the Core API only. The constraints you’ve used in this step, introduce a new dependency to the Constraints Library, where all the constraint related classes reside. So update your classpath settings accordingly.

There are a number of pre-written constraints in Zest™ which you can use. The null check of the original HelloWorld version is already handled by default since Zest™ considers method parameters to be mandatory if not explicitly marked with the @Optional annotation. So, instead of doing that check we will add other checks that are useful to make, such as ensuring that the passed in string is not empty.

The only thing you have to do is add the annotation @NotEmpty to the method parameters you want to constrain in this way. The annotation has a default implementation declared in it by using the @Constraints annotation. You can either just use this, which is the common case, or override it by declaring your own @Constraints annotation in the TransientComposite type.

You can add as many Constraint annotations you want to a parameter. All of them will be checked whenever a method is called.

Steps for this tutorial:

  • Add @NotEmpty to the state parameters.
Solution

If you have successfully completed the task, you should end up with the following artifacts;

These ones remain unchanged:

  • HelloWorld.java
  • HelloWorldComposite.java
  • HelloWorldStateMixin.java

HelloWorldBehaviour.java

/**
 * This interface contains only the behaviour
 * of the HelloWorld object.
 * <p>
 * It declares what Mixin to use as default implementation, and also the extra
 * concern to be applied.
 * </p>
 */
@Concerns( HelloWorldBehaviourConcern.class )
@Mixins( HelloWorldBehaviourMixin.class )
public interface HelloWorldBehaviour
{
    String say();
}

HelloWorldBehaviourMixin.java

/**
 * This is the implementation of the HelloWorld
 * behaviour interface.
 * <p>
 * It uses a @This DependencyModel Injection
 * annotation to access the state of the Composite. The field
 * will be automatically injected when the Composite
 * is instantiated. Injections of resources or references
 * can be provided either to fields, constructor parameters or method parameters.
 * </p>
 */
public class HelloWorldBehaviourMixin
    implements HelloWorldBehaviour
{
    @This
    HelloWorldState state;

    @Override
    public String say()
    {
        return state.getPhrase() + " " + state.getName();
    }
}

HelloWorldBehaviourConcern.java

/**
 * This Concern validates the parameters
 * to the HelloWorldState interface.
 */
public class HelloWorldBehaviourConcern
    extends ConcernOf<HelloWorldBehaviour>
    implements HelloWorldBehaviour
{
    @Override
    public String say()
    {
        return "Simon says:" + next.say();
    }
}

HelloWorldState.java

/**
 * This interface contains only the state
 * of the HelloWorld object.
 * <p>
 * The parameters are declared as @NotEmpty, so the client cannot pass in empty strings
 * as values.
 * </p>
 */
@Mixins( HelloWorldStateMixin.class )
public interface HelloWorldState
{
    void setPhrase( @NotEmpty String phrase )
        throws IllegalArgumentException;

    String getPhrase();

    void setName( @NotEmpty String name )
        throws IllegalArgumentException;

    String getName();
}

Next step is Step 6 - SideEffects

Step 6 - SideEffects

Previous step was Step 5 - Constraints.

The current say() method has a Concern that modifies its value. What if we instead want the value to be intact, but log that value to System.out? That would be considered a side-effect of the say() method, and should hence not be done in a Concern. It would be better to implement this in a SideEffect. SideEffects are executed after the Mixin and all Concerns for a method are done, which means that the final result has been computed. A SideEffect can access this result value, and then use that for further computation, but it should not change the value or throw an exception.

SideEffects can be either typed or generic, just like Concerns. In the typed case we are interested in specifying SideEffects for one or more particular methods, whereas in the generic case the SideEffect is not really relying on what method is being invoked. Both are useful in different scenarios.

The easiest way to implement a typed SideEffect is to subclass the SideEffectOf class. This gives you access to the result of the real method invocation by using the "next" field, which has the same type as the interface of the method you want the code to be a side-effect of. Note that calling "next" does not actually do anything, it only returns the value (or throws the exception, if one was thrown from the original method) that has already been computed. Similarly, since the method is already done, you can return anything from the SideEffect method. The framework will simply throw it away, and also ignore any exceptions that you throw in your code.

To declare that the SideEffect should be used you add the @SideEffects annotation to either the TransientComposite type, the Mixin type, or the Mixin implementation. Either works.

Steps for this tutorial:

  • Create the SideEffect class that logs the result of say() to System.out.
  • Add a @SideEffects annotation with the SideEffect to the HelloWorldComposite interface.
  • Remove the Concern from the previous step.
  • Move the HelloWorldStateMixin from the HelloWorldState to the HelloWorldComposite interface.
Solution

If you have successfully completed the task, you should end up with the following artifacts;

These ones remain unchanged:

  • HelloWorld.java
  • HelloWorldBehaviourMixin.java
  • HelloWorldStateMixin.java

HelloWorldBehaviour.java

/**
 * This interface contains only the behaviour
 * of the HelloWorld object.
 */
public interface HelloWorldBehaviour
{
    String say();
}

HelloWorldBehaviourSideEffect.java

/**
 * As a side-effect of calling say, output the result.
 */
public class HelloWorldBehaviourSideEffect
    extends SideEffectOf<HelloWorldBehaviour>
    implements HelloWorldBehaviour
{
    @Override
    public String say()
    {
        System.out.println( result.say() );
        return null;
    }
}

HelloWorldComposite.java

/**
 * This Composite interface declares transitively
 * all the Fragments of the HelloWorld composite.
 * <p>
 * It declares that the HelloWorldBehaviourSideEffect should be applied.
 * </p>
 */
@Mixins( { HelloWorldBehaviourMixin.class, HelloWorldStateMixin.class } )
@SideEffects( HelloWorldBehaviourSideEffect.class )
public interface HelloWorldComposite
    extends HelloWorld, TransientComposite
{
}

HelloWorldState.java

/**
 * This interface contains only the state
 * of the HelloWorld object.
 * <p>
 * The parameters are declared as @NotEmpty, so the client cannot pass in empty strings
 * as values.
 * </p>
 */
public interface HelloWorldState
{
    void setPhrase( @NotEmpty String phrase )
        throws IllegalArgumentException;

    String getPhrase();

    void setName( @NotEmpty String name )
        throws IllegalArgumentException;

    String getName();
}

Next step is Step 7 - Properties

Step 7 - Properties

Previous step was Step 6 - SideEffects.

One of the goals of Zest™ is to give you domain modeling tools that allow you to more concisely use domain concepts in code. One of the things we do rather often is model Properties of objects as getters and setters. But this is a very weak model, and does not give you any access to metadata about the property, and also makes common tasks like UI binding non-trivial. There is also a lot of repetition of code, which is unnecessary. Using JavaBeans conventions one typically have to have code in five places for one property, whereas in Zest™ the same thing can be achieved with one line of code.

But lets start out easy. To declare a property you have to make a method in a mixin type that returns a value of the type Property, and which does not take any parameters. Here’s a simple example:

Property<String> name();

This declares a Property of type String with the name "name". The Property interface has methods "get" and "set" to access and mutate the value, respectively.

For now you will be responsible for implementing these methods, but later on these will be handled automatically, thus reducing Properties to one-liners!

In the Mixin implementation of the interface with the Property declaration you should have an injection of the Property, which is created for you by Zest™. The injection can be done in a field like this:

@State Property<String> name;

The State dependency injection annotation means that Zest™ will inject the Property for you. The field has the name "name", which matches the name in the interface, and therefore that Property is injected. You can then implement the method trivially by just returning the "name" field.

Properties can have Constraints just like method parameters. Simply set them on the Property method instead, and they will be applied just as before when you call "set".

Steps for this tutorial:

  • Remove JavaBeans properties from HelloWorldState.
  • Remove HelloWorld and add the HelloWorldState and HelloWorldBehavior to the HelloWorldComposite interface.
  • Remove the HelloWorldBehaviourSideEffect.
  • Update the behaviour mixin to use the state interface accordingly.
  • Add Property methods with the correct type and the @NotEmpty annotation.
  • Update the state mixin to inject and return the properties as described above.
Solution

If you have successfully completed the task, you should end up with the following artifacts;

Only HelloWorldBehavior.java remains unchanged.

Theses ones are deleted:

  • HelloWorld.java
  • HelloWorldConcern.java

HelloWorldBehaviourMixin.java

/**
 * This is the implementation of the HelloWorld
 * behaviour interface.
 * <p>
 * This version access the state using Zest Properties.
 * </p>
 */
public class HelloWorldBehaviourMixin
    implements HelloWorldBehaviour
{
    @This
    HelloWorldState state;

    @Override
    public String say()
    {
        return state.phrase().get() + " " + state.name().get();
    }
}

HelloWorldComposite.java

/**
 * This Composite interface declares transitively
 * all the Fragments of the HelloWorld composite.
 */
@Mixins( { HelloWorldBehaviourMixin.class, HelloWorldStateMixin.class } )
public interface HelloWorldComposite
    extends HelloWorldBehaviour, HelloWorldState, TransientComposite
{
}

HelloWorldState.java

/**
 * This interface contains only the state
 * of the HelloWorld object.
 * <p>
 * The state is now declared using Properties. The @NotEmpty annotation is applied to the
 * method instead, and has the same meaning as before.
 * </p>
 */
public interface HelloWorldState
{
    @NotEmpty
    Property<String> phrase();

    @NotEmpty
    Property<String> name();
}

HelloWorldStateMixin.java

/**
 * This is the implementation of the HelloWorld
 * state interface.
 */
public class HelloWorldStateMixin
    implements HelloWorldState
{
    @State
    private Property<String> phrase;
    @State
    private Property<String> name;

    @Override
    public Property<String> phrase()
    {
        return phrase;
    }

    @Override
    public Property<String> name()
    {
        return name;
    }
}

Next step is Step 8 - Generic Mixins

Step 8 - Generic Mixins

Previous step was Step 7 - Properties.

In this step we will look at how to use generic Fragments. So far all Fragments, i.e. the Concerns, SideEffects, and Mixins, have directly implemented the domain interface. But sometimes it is useful to be able to provide a generic implementation of an interface. An example of this is the HelloWorldState interface. Since it only handles properties, and the old version used the JavaBean rules for naming getters and setters we could create a mixin that handles invocations of such methods automatically for us by storing the properties in a map and use the methods to look them up.

Implementing a generic Fragment is done by creating a class that implements the interface java.lang.proxy.InvocationHandler. This has a single "invoke" method which is passed the object that was invoked (the TransientComposite in this case), the method, and the arguments. The Fragment is then allowed to implement the method any way it wants.

Since interfaces with only Properties is such a common case Zest™ already has a generic Mixin that implements the Properties management described above, but for the builtin Property type instead of the getter/setter variant. The class is aptly named PropertyMixin.

While we could use it, for now we will implement it ourselves to get a feel for how generic Mixins work.

Steps for this tutorial:

  • Remove the HelloWorldStateMixin
  • Add a GenericPropertyMixin, and have it implement InvocationHandler
  • Inject "@State StateHolder state" in the mixin. The StateHolder interface will give you access to the Properties for the TransientComposite which Zest™ manages for you
  • On call to invoke(), delegate to the StateHolder interface to get the Property for the invoked method
  • Add an @AppliesTo annotation to the Mixin and implement the AppliesToFilter with a rule that matches only methods that return Property values.
  • Add the mixin to the TransientComposite.
Solution

If you have successfully completed the task, you should end up with the following artifacts;

These ones remain unchanged:

  • HelloWorldBehaviour.java
  • HelloWorldState.java

GenericPropertyMixin.java

@AppliesTo( { GenericPropertyMixin.PropertyFilter.class } )
public class GenericPropertyMixin
    implements InvocationHandler
{
    @State
    private StateHolder state;

    @Override
    public Object invoke( Object proxy, Method method, Object[] args )
        throws Throwable
    {
        return state.propertyFor( method );
    }

    public static class PropertyFilter
        implements AppliesToFilter
    {
        @Override
        public boolean appliesTo( Method method, Class<?> mixin, Class<?> compositeType, Class<?> modifierClass )
        {
            return Property.class.isAssignableFrom( method.getReturnType() );
        }
    }
}

HelloWorldBehaviourMixin.java

/**
 * This is the implementation of the HelloWorld
 * behaviour interface.
 */
public class HelloWorldBehaviourMixin
    implements HelloWorldBehaviour
{
    @This
    HelloWorldState state;

    @Override
    public String say()
    {
        return state.phrase().get() + " " + state.name().get();
    }
}

HelloWorldComposite.java

/**
 * This Composite interface declares transitively
 * all the Fragments of the HelloWorld composite.
 * <p>
 * All standard declarations have been moved to
 * the StandardAbstractEntityComposite so we don't have to repeat
 * them in all Composites.
 * </p>
 */
@Mixins( { HelloWorldBehaviourMixin.class, GenericPropertyMixin.class } )
public interface HelloWorldComposite
    extends HelloWorldBehaviour, HelloWorldState, TransientComposite
{
}

Next step is Step 9 - Private and Abstract Mixins

Step 9 - Private and Abstract Mixins

Previous step was Step 8 - Generic Mixins.

Now we’re going to turn around and see how we can reduce the code needed to implement the HelloWorld example. We will also look at how to hide the Properties from the client code, since Properties are often considered to be implementation details that should not be exposed to clients.

The first thing we will do is remove the behaviour interface, and move the say() method to the TransientComposite type. This forces the mixin to implement the TransientComposite type, which would normally mean that it would have to implement all methods, including those found in the TransientComposite interface. However, since we are only really interested in implementing the say() method we will mark this by declaring that the Mixin "implements" the TransientComposite type, but is also "abstract". This, using pure Java semantics, makes it possible to avoid having to implement all methods. Zest™ will during the initialization phase detect that the Mixin only handles the say() method, and therefore only map it to that specific method. In order to instantiate the Mixin it will generate a subclass which implements the remaining methods in the TransientComposite type, as no-ops. These will never be called however, and is there purely for the purpose of being able to instantiate the Mixin. The Mixin is considered to be an Abstract Fragment.

To hide the state from the client we need to use what is called Private Mixins. A Private Mixin is any mixin that is referenced by another Mixin by using the @This injection, but which is not included in the TransientComposite type. As long as there is a Mixin implementation declared for the interface specified by the @This injection it will work, since Zest™ can know how to implement the interface. But since it is not extended by the TransientComposite type there is no way for a user of the TransientComposite to access it. That Mixin becomes an implementation detail. This can be used either for encapsulation purposes, or for referencing utility mixins that help the rest of the code implement some interface, but which itself should not be exposed.

Since the state is now hidden the initialization of the TransientComposite is a bit more tricky. Instead of just instantiating it you have to create a TransientBuilder first, then set the state using .prototypeFor(), and then call newInstance(). This ensures that the state can be set during construction, but not at any later point, other than through publicly exposed methods.

Steps for this tutorial:

  • Move the say() method into the TransientComposite interface.
  • Remove the behaviour interface.
  • Remove the HelloWorldBehaviourMixin, create a HelloWorldMixin and let the HelloWorldMixin implement the TransientComposite directly.
  • Mark the HelloWorldMixin as abstract and implement only the say() method.
  • Remove the HelloWorldState from the TransientComposite "extends" declaration.
  • Remove the GenericPropertyMixin. The Property methods will be implemented by the standard PropertyMixin declared in the TransientComposite interface instead.
Solution

If you have successfully completed the task, you should end up with the following artifacts only;

HelloWorldComposite.java

/**
 * This Composite interface declares transitively all the Fragments of the HelloWorld composite.
 * <p>
 * The Fragments are all abstract, so it's ok to
 * put the domain methods here. Otherwise the Fragments
 * would have to implement all methods, including those in Composite.
 * </p>
 */
@Mixins( { HelloWorldMixin.class } )
public interface HelloWorldComposite
    extends TransientComposite
{
    String say();
}

HelloWorldMixin.java

/**
 * This is the implementation of the say() method. The mixin
 * is abstract so it doesn't have to implement all methods
 * from the Composite interface.
 */
public abstract class HelloWorldMixin
    implements HelloWorldComposite
{
    @This
    HelloWorldState state;

    @Override
    public String say()
    {
        return state.phrase().get() + " " + state.name().get();
    }
}

HelloWorldState.java

/**
 * This interface contains only the state
 * of the HelloWorld object.
 */
public interface HelloWorldState
{
    @NotEmpty
    Property<String> phrase();

    @NotEmpty
    Property<String> name();
}