-
Notifications
You must be signed in to change notification settings - Fork 11
Getting Started
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.
Methods have several ways of operating, based on their allowed invocations:
-
any()
- can be called any number of times -
atLeast(x)
- must be called at least x number of times -
atMost(x)
- can be called at most x number of times -
between(x,y)
- must be called at least x times and at most y times. -
once()
- alias for atMost(1) -
last()
- can be called exactly once, and will cause the block to return
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 builder. These combinations increase factorially, which means just a handful of methods can produce thousands of classes! (Future versions may allow 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.
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.
Descriptor builder =
DescriptorGenerator.create(new DescriptorHelperImpl())
.setPackage("unquietcode.tools.flapi.builder")
.setStartingMethodName("create")
.setDescriptorName("Descriptor")
.setReturnType(Descriptor.class)
.enableCondensedClassNames(false)
.addMethod("setPackage(String packageName)").between(1,1)
.addMethod("setDescriptorName(String descriptorName)").between(1,1)
.addMethod("setStartingMethodName(String methodName)").once()
.addMethod("setReturnType(Class returnType)").between(1,1)
.addMethod("enableCondensedClassNames(boolean value)").once()
.addMethod("build()").last()
.startBlock("Method", "addMethod(String methodSignature)").any()
.addMethod("once()").last()
.addMethod("any()").last()
.addMethod("last()").last()
.addMethod("atLeast(int num)").last()
.addMethod("atMost(int num)").last()
.addMethod("between(int atLeast, int atMost)").last()
.startBlock("BlockChain", "addBlockChain()").once()
.addMethod("addBlockReference(String blockName)").last()
.addBlockReference("Block", "startBlock(String blockName)").last()
.addBlockReference("BlockChain", "addBlockChain()").once()
.endBlock()
.endBlock()
.startBlock("Block", "startBlock(String blockName, String methodSignature)")
.addBlockChain()
.addBlockReference("Method")
.any()
.addMethod("addBlockReference(String blockName, String methodSignature)")
.addBlockChain().addBlockReference("Method")
.any()
.addBlockReference("Method", "addMethod(String methodSignature)").any()
.addBlockReference("Block", "startBlock(String blockName, String methodSignature)")
.addBlockChain().addBlockReference("Method")
.any()
.addMethod("endBlock()").last()
.endBlock()
.build()
;
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
- 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)
- can return to the same block, or return to the parent block
- 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
- atLeast checks a block when it exits through a last method (throws a