Skip to content

Getting Started

Ben Fagin edited this page Oct 19, 2013 · 17 revisions

Consider reading the new annotated documentation first!

Then come back here if you want a different perspective. :)

Basic Concepts

Flapi builds what is called a descriptor, which it then uses to build the Java code model and generate the classes and interfaces of a builder. A Descriptor is comprised of blocks and methods. A block contains methods. Each block can in turn nest other blocks inside of it. Blocks can be reused by using their name later in a addBlockReference(...) method, which connects the blocks logically without requiring redefining them.

A method or a block can also include a block chain. These are used to attach new or existing blocks to a method (or block constructor), providing a simple chaining mechanism. A block chain must be passed through before the block itself is reached, or the method returns to its parent block.

Flapi also creates helper interfaces, which the user can implement to support the builders. As methods are called on the builders, they are called on the helpers.

Invocation Tracking

Methods have several ways of operating, based on their allowed invocations:

  • any() - can be called any number of times
  • exactly(int x) - must be called exactly x times
  • atLeast(int x) - must be called at least x number of times
  • atMost(int x) - can be called at most x number of times
  • between(int x, int y) - must be called at least x times and at most y times.
  • last() - can be called exactly once, and will cause the block to return
  • last(Class c) - can be called exactly once, and will return an object of type c

More about that last() method: every block must contain at least one of these. If not, an error will be thrown when the descriptor is built. This makes sense, as without a last() method your block would never be able to exit. Developers would be stuck in an infinite loop where all they could do was keep chaining more and more methods together!

There are consequences to setting upper and lower limits on method invocations. Tracking the maximum invocations affects the generated builder by generating extra combinations of methods (one for each invocation). So, with atMost(3) you're actually adding three separate methods to your block builder. These combinations increase factorially, which means just a handful of methods can produce hundreds of classes! Descriptor blocks are intended to handle only a limited number of state changes. If you're doing lots of invocation checking then consider splitting your blocks into several smaller blocks. (Future versions may allow for a more 'soft' checking of maximum invocations.)

Likewise, the atLeast calls trigger a verification when a block exits that a method has been called the appropriate number of times. Check out the EmailBuilder example to see this behavior in action.

Creating a Descriptor

Every descriptor starts with a call to Flapi.builder(). This starts a fluent chain of methods which can be used to create your builder. The following list of methods can be used:

  • setPackage(String) - required, sets the name of the package for the generated builder classes
  • setStartingMethodName(String) - sets the starting method name for the builder (defaults to 'create')
  • setDescriptorName(String) - required, sets the name of the top level descriptor block
  • setReturnType(Class) - sets the return type of the overall builder (defaults to Void.class)
  • enableCondensedClassNames() optional, forces generated classes to use shorter names
  • addMethod(...) - used to add a method to a block or descriptor
  • startBlock(...) - adds a block to the descriptor, or nested in an existing block
  • addBlockChain(...) - adds a block chain, which can be comprised of new or referenced blocks
  • addBlockReference(...) - adds a block to another block or block chain, referencing it by name

You can check out any of the Examples for more information about how to use Flapi descriptors. If you have, then you might have noticed that the DescriptorBuilder itself looks strangely familiar. That's because the builder is created by Flapi for its own use! (This is akin to the way a compiler is bootstrapped and eventually made to compile itself.) That builder is fairly complex, and showcases some of the features of a Flapi descriptor.

Summary

Phew, that was a mouthful! Let's summarize:

  • Descriptor
    • is itself a block
    • contains blocks and methods
  • Block
    • contains blocks and methods
    • has a method which is its constructor within the parent context
      • means that a block too can have invocation restrictions
    • can be referenced in the addBlockReference(...) methods later without having to redefine
  • BlockChain
    • block which must be passed through before entering the current block or method
    • can use an anonymous block or a BlockReference
  • BlockReference
    • a reference to a previously created block
    • the order does not matter, but the block must exist somewhere or else an error is thrown
  • Method
    • can return to the same block, or return to the parent block
      • can also register an invocation towards some maximum, which moves horizontally to a new interface
    • can add block chains to create a sequence of blocks to pass through before completing (or continuing to the block in the case of a block constructor)
  • Helpers
    • interfaces which are generated to support the builder's behavior
    • while the interfaces are generated, the implementations are your own
      • they can do anything you want, but you don't control how and when their methods are invoked
  • Invocation Tracking
    • atLeast checks a block when it exits through a last method (throws a MinimumInvocationsException)
    • atMost is built into the descriptor
    • you should limit the number of atMost/once/between methods in a block, as adding too many can create a massive number of generated classes