Skip to content

Source Level Syntax

Java comments, whether single line or block comments, are parsed by the wurbler for wurblet anchors, variables and here-documents. This applies to generated comments as well! Consequently, generated code may include new anchors, variables or documents that in turn are evaluated by the wurbler and so on. This is an important feature because it allows bootstrapping from a rudimentary template, similar to a software developer starting with a sketch (the finished result only makes full sense once you know how it came to be). The special significance of a character can be turned off by preceding it with a backslash. A line terminated by a backslash is concatenated with the following line (continuation line). The backslash itself is expressed by a double backslash.

Wurblet Anchor

All generated code, whether a single line or a large block, must be bound to a wurblet and optional variables or resources such as model information. The binding is done by a so-called wurblet anchor. There may be as many anchors in a comment block as desired. The wurblets are applied by the wurbler in the order of their anchors in the source file. The generated output is inserted into the source file right after the comment. Each output block is embraced by special comment lines that uniquely identify the block and allow the wurbler to associate it to the anchor in subsequent wurbelization passes. Once the code is generated, the anchor may be moved in the source file anywhere without losing the binding. Thus, the source file may be edited and anchors or the model information changed between wurbelization passes without getting out of sync.

An anchor is of the form:

@wurblet[(wurbler-directive)] <tag> <wurblet> [<arg1> [... <argN>]]

where:

  • @wurblet: starts an anchor
  • (wurbler-directive1 ... N): optional directives to the wurbler
  • <tag>: is a tag uniquely identifying the anchor within the source file
  • <wurblet>: the class name of the wurblet. This is either a fully qualified class name or a basename to which the wurbler prepends the package names provided by the wurbletPaths configuration.

  • <arg1>...: optional wurblet arguments. Usually these arguments carry model information, or point to where such information can be found.

Anchors that span more than one line must be concatenated by a trailing backslash at the end of the lines to be continued. Arguments are separated by whitespaces. If an argument contains whitespaces, it must be enclosed in double quotes. Such arguments may span more than one line as well. Arguments may contain $-signs denoting a variable. Variables may either be declared in a variable section, can be an ant property, a property from a wurb-file or an environment variable. The variable substitution process is recursive, i.e. variables may contain strings referring to other variables and so on.

Examples:

/**
 * Principals are cached preloaded with a second index ’code’.
 *
 * @wurblet cache PdoCache --preload code
 */

// @wurblet updateByObjectTypeSerial DbUpdateBy --model=$mapfile \
//          processed:=:null objectClassId objectId modType serial:<= | processed

Anchor and Guarded Block

After the first wurbel run, the generated code sits below the anchor, enclosed in the guarded block. The following is a verbatim excerpt of NumberPoolPersistence.java from the Tentackle framework, using the default netbeans guard type:

public interface NumberPoolPersistence extends PersistentObject<NumberPool> {

  // @wurblet fieldlengths ColumnLengths

  //<editor-fold defaultstate="collapsed" desc="code 'fieldlengths' generated by wurblet ColumnLengths">//GEN-BEGIN:fieldlengths


  /** maximum number of characters for 'name'. */
  int CL_NAME = 30;

  /** maximum number of characters for 'realm'. */
  int CL_REALM = 80;

  //</editor-fold>//GEN-END:fieldlengths

  // @wurblet membernames AttributeNames

  //<editor-fold defaultstate="collapsed" desc="code 'membernames' generated by wurblet AttributeNames">//GEN-BEGIN:membernames


  /** relation name for 'component list of NumberRange numberRangeList via NumberRange#numberPoolId'. */
  String RN_NUMBERRANGELIST = "numberRangeList";

  /** attribute name for database column 'name'. */
  String AN_NAME = "name";
  ...

Two things are worth noting. First, the tags fieldlengths and membernames are freely chosen by the developer and appear in the guard comments, which is what binds the block to its anchor across runs. Second, several anchors happily coexist in one file, each generating one aspect of it — the whole file is a mixture of generated and hand-written code, and it stays editable.

An entity in Tentackle typically carries a handful of such anchors, for example:

  // @wurblet classVariables ClassVariables
  // @wurblet fieldnames ColumnNames
  // @wurblet declare Declare
  // @wurblet methods MethodsImpl
  // @wurblet selectByUniqueDomainKey PdoSelectUnique name
  // @wurblet relations PdoRelations
  // @wurblet selectByNameRealm PdoSelectUnique name realm

The two PdoSelectUnique anchors show why the tag exists: the same wurblet is applied twice in the same file with different arguments, producing two different finder methods.

Unnamed Anchor

If the <tag> in the wurblet anchor does not start with a letter, it is considered as an unnamed wurblet anchor. Unnamed anchors are useful for so-called inline wurblets, which are commonly used to inject a small piece of code into the code block directly preceding or following the anchor. The code is injected between two consecutive empty block comments /**/. The first character of the tag determines whether the preceding or following code block is targeted by the wurblet.

  • The characters - or < refer to the preceding block
  • all others, including + or > refer to the following block

Instead of /**/, the empty block comment may also be written as /*@*/. Both forms are equivalent and are treated as code rather than as a comment. /*@*/ is the better choice in Java sources: since Java 23 the compiler warns about a documentation comment that is not attached to any declaration, and /**/ in front of a declaration can trip that warning.

Example:

@TableName(/*@*/"modlog"/*@*/)      // @wurblet < Inject --string $tablename
The wurblet Inject (which is a Tentackle wurblet, btw.) inserts the string "modlog" into the @TableName annotation. The value is defined by the variable $tablename.

Tentackle uses this pattern wherever a value from the model has to appear inside an annotation, where a guarded block would not fit. The class ModificationLog binds both its table name and its class ID this way:

@ClassId(/*@*/2/*@*/)               // @wurblet < Inject $classid
@TableName(/*@*/"modlog"/*@*/)      // @wurblet < Inject --string $tablename
@PreloadRemoteDelegate
public class ModificationLog extends AbstractDbObject<ModificationLog> {

Several injections may even be applied to a single annotation, one per line, each anchored to the code preceding it:

@TableName(value =/*@*/"md.orgunit"/*@*/, // @wurblet < Inject --string $tablename
           mapSchema =/*@*/false/*@*/,    // @wurblet < Inject $mapSchema
           prefix =/*@*/""/*@*/)          // @wurblet < Inject --string $tablePrefix

Note the difference between the two forms: --string wraps the value in double quotes, so $tablename becomes "md.orgunit", while $mapSchema is injected verbatim as the boolean literal false.

Unlike guarded blocks, inline sections are not guarded — the wurbler simply replaces whatever it finds between the two empty block comments, so manual edits there are silently overwritten by the next run.

Conditional Anchor

Wurblets may be invoked conditionally. This is achieved by the test-directive.

Example:

// @wurblet(test:$planb==yes) runPlanB Didoedeldu

Will invoke the wurblet Didoedeldu if the variable $planb equals the string yes. Otherwise the comment // condition not met will be generated. Currently, only the operators == and != are supported.

Extra Indentation

The generated code may be indented by extra spaces with the indent wurbler directive. 0 means no extra indent, a negative value is interpreted as smart indent, which is the default for Java sources. Smart indent aligns the generated code with the comment block containing the wurblet anchor.

Example:

 // @wurblet(indent=4) someMethod DoIt

Editor Fold

An optional editor fold type for the generated code may be specified. Not all guard types support a fold type. Currently, editor folds are only supported by the Netbeans-IDE. By default, no editor fold will be generated. The following fold types are supported:

  • collapsed: the generated code will be collapsed
  • expanded: the generated code will be expanded
  • none: the generated code will get no fold-handle (default)

Example:

// @wurblet(fold=expanded) modelComment ModelComment

This example is taken from Tentackle, where it is applied consistently to the ModelComment wurblet. The reasoning is instructive: the project sets the fold type to collapsed globally (via the foldtype wurblet property, see Maven Integration), because generated implementation code is noise most of the time. The model comment, however, documents how the entity relates to the rest of the model, which is exactly what a developer opening the file wants to read — so that one anchor overrides the default and stays expanded.

Notice that some IDEs automatically fold a guarded block (also known as custom code folding region) by default. IntelliJ does so, for example.

Here Documents

Here-Documents are an elegant way to provide model information to the wurblets or to create files in general. Whenever the wurbler detects a document section within a comment block, it creates a file either on heap or in the filesystem depending on the file’s name. Document sections start with @> and terminate with @<.

Example (from the Wurbelizer tutorial):

/*
 * @> .$filename
 * String    name       the name
 * long      id         the unique ID
 * boolean   enabled    whether enabled or not
 * @<
 */

Notice that the filename may be a variable again. If the filename starts with a dot, it will not be saved to the filesystem but instead kept in memory only. Furthermore, such heap files must be unique within the wurbler instance. If the wurbler is invoked from within maven (which usually is the case), the file is available during the whole wurbel lifecycle phase, to all wurblets in all files. This feature allows running wurblets in distinct phases, each phase creating some information for the next phase.

Example: the model next to the code

In Tentackle, here-documents carry the entity model. The class ModificationLog declares its whole persistent model in a comment at the top of the very file that implements it:

/*
 * @> $mapfile
 *
 * # modification table for async coupling
 * name := $classname
 * id := $classid
 * table := $tablename
 *
 * [remote]
 *
 * ## attributes
 * long                     objectId         objectid         object id
 * int                      objectClassId    classid          object class id
 * String(192)              objectClassName  classname        object classname (if classid == 0)
 * long                     txId             txid             transaction id (optional)
 * String(64)               txName           txname           transaction name (optional) [TRIMWRITE]
 * ModificationType<char>   modType          modtype          modification type
 * Timestamp                when             modtime          time of event
 * long                     userId           userid           user id
 * String                   message          message          optional informational or error message [NOMETHOD]
 * Timestamp                processed        processed        processing time [MAPNULL]
 *
 * ## indexes
 * index next := processed, id
 * index txid := txid
 * index object := objectid, classid, processed
 * index user := userid, processed
 *
 * @<
 */

The name $mapfile does not start with a dot, so the document is written to disk as a real .map file. That is deliberate: other tools in the build (the schema generator, for instance) read the same model files, and the wurblets of other source files reference them by name. The variables come from a .wurb file next to the source (see Variables), and $classname is supplied by the wurbler itself.

The payoff is that the model and the code it generates cannot drift apart — they are the same file, reviewed in the same diff.

Cross-file generation with heap files

Heap files come into their own when generated code has to be distributed over several source files. Tentackle's remote delegates are the canonical case.

When a persistence method is generated for an entity that is reachable remotely, three pieces of code are needed in three different files: the local implementation, a signature in the TRIP delegate interface, and a forwarding method in the delegate implementation. The wurblet writes the latter two into heap files using the @{to}@ directive:

    RemoteIncludes genInc = new RemoteIncludes(this);
    PrintStream implOut   = genInc.getImplementationStream();
    PrintStream remoteOut = genInc.getInterfaceStream();

RemoteIncludes creates these streams as heap files named after the delegate classes:

    interfaceStream = new HeapStream(container, remoteInterface + "/methods");
    implementationStream = new HeapStream(container, remoteImplementation + "/methods");

The delegate source files then pick their contents up with a single anchor:

public interface NumberPoolRemoteDelegate
       extends AbstractPersistentObjectRemoteDelegate<NumberPool,NumberPoolPersistenceImpl> {

  // @wurblet inclrmi Include --missingok .$classname/methods

  //<editor-fold defaultstate="collapsed" desc="code 'inclrmi' generated by wurblet Include">//GEN-BEGIN:inclrmi

  NumberPool selectByUniqueDomainKey(DomainContext context, String name);
  NumberPool selectByNameRealm(DomainContext context, String name, String realm);

  //</editor-fold>//GEN-END:inclrmi

}

Every detail of that anchor does some work:

  • the leading dot in .$classname/methods marks the reference as a heap file, so nothing is read from disk. (The heap file itself is registered without the dot; the dot is the convention on the reading side.)
  • $classname resolves to the name of the class being wurbeled, NumberPoolRemoteDelegate, which is precisely the name the producing wurblet used. So no explicit wiring between producer and consumer is needed — the naming convention is the contract.
  • --missingok tolerates an absent heap file, which is what happens when the entity has no remote methods at all.
  • the two methods in the block were contributed by two different anchors in a different source file, NumberPoolPersistenceImpl.java.

For this to work, the producing files must be wurbeled before the consuming ones. Tentackle enforces the order with two filesets in the plugin configuration rather than with wurblet phases, because the ordering is between files, not within one (see Maven Integration).

Variables

Maven (or Ant) properties and environment variables are automatically declared as variables by the wurbler. Additional variables may be declared either in extra property-files (extension .wurb) or directly at the source level within any comment block. Such a declaration section starts with @{ and ends with @}.

Example (from the Tentackle tutorial):

/*
 * @{
 * tablename = md.orgunit
 * mapping   = $model/$tablename.map
 * @}
 */

Variables can be used in comments or other variable definitions in two ways:

  1. $<variable>: e.g. $tablename.map where the dot automatically ends the variable name
  2. ${<variable>}: e.g. ${tablename}xx to add the string xx to the tablename's value

Notice the recursion in the example above: mapping is defined in terms of $model and $tablename, where $model is not declared in the source at all but comes from the build tool — Tentackle sets it as a wurblet property in the POM.

Example: variables in a .wurb file

The alternative to an inline @{ @} block is a property file with the extension .wurb, placed next to the source file. ModificationLog.java is accompanied by ModificationLog.wurb with just three lines:

tablename=modlog
classid=2
mapfile=$model/$tablename.map

These three variables then feed everything else in the file: $mapfile names the here-document holding the model, $tablename is injected into the @TableName annotation and $classid into @ClassId. Changing the table name is therefore a one-line edit that propagates through the annotations, the model file and every generated SQL statement on the next build.

Which of the two forms to prefer is mostly a matter of taste. An inline block keeps everything in one file; a .wurb file keeps the source free of build metadata and is easier to generate from a wizard or archetype.

Further reading

  • Guard Types — the patterns that delimit the generated blocks
  • Writing Wurblets — how a wurblet consumes anchors, here-documents and variables