GWT

StrataCode Components

StrataCode has several features for making Java code more declarative, more concise, and easier to read. You may be familiar with component frameworks that implement an inversion of control pattern to wire together a graph of objects for later customization. With StrataCode, these features are all built into the language using the new object operator, the @Component annotation and simple references between objects.

With an inversion of control container, you are faced with the choice of configuring a property using XML, annotations, or just providing the values or expressions in Java. You balance several design considerations with that choice. When you need to switch approaches for configuring a property, it alters the API contract for anyone using that code. For properties configured in XML or annotations, error messages and resolution often show up at runtime and the tooling experience suffers.

With layers, you have the choice of putting the reference in the original code or configuring it in a new layer. You can change your mind at any point without affecting consumers of your published layers. Even if you put a reference into the original source code, it can be overridden with no cost in a subsequent layer.

The layered approach to component assembly provides customization without upfront planning, a solution that improves code reusability substantially.

StrataCode component features are implemented through code-processing - modifying the original source code to move code from the constructor to a 'preInit' method, rewrite fields and references to fields to use get/set methods as needed. It's still easy to debug.

Creating Object Instances

StrataCode adds to the Java syntax a new keyword: "object" that works a lot like Scala's object. It defines a new variable and creates an instance for that variable automatically when it's accessed or through custom rules defined at code-generation time. A framework developer has a great deal of control over how the 'object' keyword is converted to Java by customizing templates, and attaching them to a type, base-type or parent-type (where a type can be a class or object).

If the object is a top-level construct in the file:

object myInstance {
}
StrataCode by default generates code similar to the following:
class myInstance {
    private static myInstance myInstance;
    static myInstance getMyInstance() {
        if (myInstance == null)
            myInstance = new myInstance();
        return myInstance;
    }
}

StrataCode creates a class for the object with a static property which lazily creates the default instance. In your code, you can refer to the object with an ordinary variable e.g. 'myInstance'. At code-generation time, StrataCode will transform that variable into a method call: myInstance.getMyInstance().

Just as Java lets you define inner classes, you can have inner objects:

class MyClass {
   object innerObject {
   }
}

This defines an inner object, with a single instance created for each instance of MyClass. At code generation time, StrataCode generates an instance variable to hold the instance and a similar getInnerObject method inside of MyClass to lazily create and retrieve the instance. As an optimization, StrataCode may not generate a class for each inner object. Many inner objects are just configured instances of some other type and this makes the implementation more efficient.

Framework layers can use inner objects for the parent/child relationship by adding the "setParent" call or constructor parameters during code-generation.

Properties

Java programmers are advised to avoid exposing fields directly in APIs. Instead the convention uses getX and setX methods to implement a property called X. You explicitly call the getX or setX method instead of directly manipulating the field so that the implementation can adapt down the road without breaking your code. The result is functional and well tested but it's more code to write both to implement and use a property and worse the code is harder to read and use.

StrataCode simplifies your life by formalizing the Java convention. It generates getX and setX methods as needed and converts simple identifiers and assignments to getX and setX method calls as needed in your generated code. You can define properties and use them as you would Java fields. StrataCode handles any necessary code-generation and conversion for you automatically. Your code looks simpler, cleaner and is more efficient. Use fields knowing that you can write custom getX and setX methods, or have them generated for you later without breaking code.

Frameworks can customize the getX and setX code generated for properties to interact with framework code.

The data binding system detect when properties are used in data binding expressions and generates getX and setX methods with the proper code to implement the binding. The setX method will trigger a change event typically, or the getX method may force the lazy-evaluation of the binding. The details of how this happens are managed in framework layers so application programmers and business users only see properties that may be attached to data binding rules. They are provided efficient and powerful abstractions over properties which can adapt from framework to framework.

Recursive References using @Component

When you initialize fields in a Java class you can only refer to fully initialized objects. If the state of your object in any way refers to values which refer back to you, your code will either not compile or just not work. These are called recursive references. Component frameworks support them by using a multi-step initialization process. Objects are created and registered in the name space, then references are resolved, then initialization hooks are run.

Programmers try to avoid recursive references because they create less modular code, the multi-step init process is more complex at runtime. But when you need to add one, it can be very hard to refactor code to remove that need. In the real world, data sometimes flows upstream. To move these references from Java to the component framework involves rewriting code, configuration, and breaks code using the affected published APIs or configuration hooks.

StrataCode offers a nice alternative. Add the @Component annotation to your StrataCode class to change the generated Java code to allow that class to use recursive references. StrataCode transforms your Java code to use a multi-step initialization sequence. First it creates all instances in the reference graph and sets their member variables so the getX methods work. Then the instance variables are assigned.

TODO: Currently the constructor code is run before these complex instance variables are assigned. Should this be fixed? It would be nice if by the time constructor code is run, all instance variables are at least assigned.

You cannot guarantee that all referenced constructors have been run as there's no way to do that in a recursive reference graph. Instead, you can move code dependent on that into an init method that is called later. There's also a start method called after all referenced components have been initialized.

TODO: It would be nice to configure the list of stages required for a given component class (e.g. init, start, validate, etc.)

Customizing Object Lifecycle

A declarative framework that only supports static objects only goes so far. StrataCode makes the object lifecycle a customization "hinge point" so the same object operator can create instances with different lifecycles. Framework layers add code templates to customize the generated getX method, fields, and init-code for those objects.

Framework layers can control the lifecycle of the page objects by using the scope operator or the @Scope annotation.

For example, the HTML framework provides these scopes:

Framework developers have simple hooks for implementing a new scope. They can override the contents of the getX method generated for a particular base class, class marked with an annotation, or in a layer. This is controlled both by annotations set on the class, base-class or layer. Framework layers provide code templates for generating the right getX method for a given type. For a session scoped type, they find the object in the current session using a thread-local variables to find the current session.

TODO: Provide a scope to make it easy to inject "multi-tenant" behavior into application code. The tenant-id property can be pulled out of the context and used to ensure the proper version of that object is used in each situation.