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:
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:
The wurbletInject (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:
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:
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 collapsedexpanded: the generated code will be expandednone: the generated code will get no fold-handle (default)
Example:
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/methodsmarks 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.) $classnameresolves 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.--missingoktolerates 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):
Variables can be used in comments or other variable definitions in two ways:
$<variable>: e.g.$tablename.mapwhere the dot automatically ends the variable name${<variable>}: e.g.${tablename}xxto add the stringxxto 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:
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