Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

As if there weren't enough options already for constructing your own
builders in Groovy, along comes another: MetaBuilder. Quite literally,
MetaBuilder is a builder that builds builders. Through some simple
examples, this article will show you how you can put MetaBuilder
to work for you in just three easy steps. To follow along, simply visit
SourceForge to get the MetaBuilder distribution and include
groovytools-builder-x.x.x.jar in your classpath.

...

Tip
titleTip

You don't have to pass MetaBuilder a class loader, but
in some cases, especially in scripts that declare new
classes such as this one, it's necessary. This can be done by setting the classLoader
property or by passing the class loader in MetaBuilder's constructor,
as shown above.

Define Your Domain Specific Langauge (DSL)

MetaBuilder provides a DSL, implemented as a Groovy builder, for defining
your own builders. If you are already familiar with builders in Groovy,
it takes just one simple example to get you started. Continuing where
we left off above, let's start defining a customer class and customer
builder:

Code Block
borderStyledashed
titleStep 2
class Customer {
    def name
    def dateOfBirth
    def ssn
    def phone
}

mb.define {
    customer(factory: Customer) {
        properties {
            name()
            dateOfBirth()
            ssn()
        }
    }
}

In the previous snippet, define is used to tell MetaBuilder that we
are going to create some new definitions, or schema, for the objects that
our new builder can create. MetaBuilder keeps track of our definitions
and it's even possible to reuse and extend these definitions, as we'll
see later.

customer is the name of the schema and the factory attribute
tells MetaBuilder what object to create whenever a customer is to be
built. properties contains a list of property names.

Tip
titleTip

MetaBuilder will throw an excception if a build script
attempts to use unspecified or mispelled properties. So, for example,
even though phone is a member of Customer, MetaBuilder won't
allow you to use it unless you add it to your schema.

...

Building objects is now just a matter of telling MetaBuilder that is
what you want to do:

Code Block
borderStyledashed
titleStep 3
def aCustomer = mb.build {
    customer {
        name = 'J. Doe'
        dateOfBirth  = '1/1/1900'
        ssn  = '555-55-5555'
    }
}

// this is equivalent
aCustomer = mb.build {
    customer ( name: 'J. Doe', dateOfBirth: '1/1/1900', ssn: '555-55-5555')
}

// you can even mix up the styles:
aCustomer = mb.build {
    customer ( name: 'J. Doe', dateOfBirth: '1/1/1900') {
        ssn = '555-55-5555'
    }
}

Great! If you've been following along in your own IDE, you hopefully
now have the basics down and are ready to take a look at some advanced
techniques.

Tip
titleTip

MetaBuilder.build() returns the last object
constructed. If your build script creates multiple objects, use the
buildList method instead to return all of them.

...

What would happen if you used a property that was not in the schema or
mistyped a legitimate property name? For example:

...

Despite the fact that phone is a property of your class, MetaBuilder only goes by what's in your
schema. That kind of checking is nice to have and MetaBuilder can do even more with some additional information.

...

Let's take another look at the factory attribute used earlier. By
simply specifying the factory attribute in your schemas, you tell
MetaBuilder how to build the right object every time. MetaBuilder was
designed to accept as wide a variety of values as possible. For example,
you can specify the factory attribute as any of the following:

...

  • def: the default value or a Closure to produce the default value, as needed
  • req: if true, a value must be specfied (it could be null though)
  • property: an alternative property name or Closure to use to set the value
  • min: the minimum length for a property value
  • max: the maximum length for a property value
  • check: causes an exception if the value fails the following test:
    Code Block
    borderStyledashed
    switch(value) {
        case check: return true
    }
    return false
    
    Tip
    titleTip

    The check attribute accepts the following types
    of objects which makes enforcing constraints easy:

    • Closures
    • Patterns
    • Classes
    • Numbers
    • Strings
    • Ranges
    • Collections
    Here's an example showing combinations of some of the above:
    Code Block
    borderStyledashed
    titleMetaBuilder Property Attributes in Action
    mb.define {
    customer3(factory: Customer) {
        properties {
            name(req: true, min: 1)
            dob(property: 'dateOfBirth')
            ssn(check: ~/\d{3}-\d{2}-\d{4}/)
        }
    }
    
    def aCustomer3 = mb.build {
        customer4 {
            name = 'J. Doe'
            dob = '1/1/1900'
            ssn  = '555-55-5555'
        }
    }
    

...

Use the schema attribute to tell MetaBuilder that you want to reuse
a schema. In the next example, we'll create a new Phone class and
update our schema to use it:

...

So far, we've only looked at examples of properties, but MetaBuilder also
supports collections. You define collections like you define properties,
just provide a list of them and set attributes as needed.

...

The above definition extends Customer and adds a list of phone numbers
and a map of addresses. Like properties, collections are mapped to
properties of an object by the name.

Note how address is defined directly within the collection.
Nesting definitions can make the definitiona bit more brief, but comes
at the risk of creating definitions that aren't as easily reused.

Another thing to note is the use of the key attribute in the
addresses collection. Presenece of the key attribute tells
MetaBuilder that the parent-child relationship is indexed. In the
following example, you can see that customer5 has both a list of
phone and map of addresses using the address's type
and the key.

...

This whirlwind tour of MetaBuilder really only just scratched the surface
of its features and capabilities. If you get stuck, be sure to check out
the MetaBuilder Meta-Schema, which describes the entire MetaBuilder feature set.

...