Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Sign Up
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<h1>Groovy, DSLs and builders</h1><h2>DSLs made easy</h2><p>The Groovy language is a platform of choice for building DSLs. Using closures, it's quite easy to create custom control structures, as well as it is simple to create builders. Imagine that you have the following code:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>email { from 'dsl-guru@mycompany.com' to 'john.doe@waitaminute.com' subject 'The pope has resigned!' body { p 'Really, the pope has resigned!' } }</pre></td></tr></table><p>One way of implementing this is using the builder strategy, which implies a method, named <em>email</em> which accepts a closure as an argument. The method may delegate subsequent calls to an object that implements the <em>from</em>, <em>to</em>, <em>subject</em> and <em>body</em> methods. Again, <em>body</em> is a method which accepts a closure as an argument and that uses the builder strategy.</p><p>Implementing such a builder is not complicated:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def email(Closure cl) { def email = new Email() def code = cl.rehydrate(email, this, this) code.resolveStrategy = DELEGATE_ONLY code() }</pre></td></tr></table><p>the Email class implements the <em>from</em>, <em>to</em>, ... methods. By calling <em>rehydrate</em>, we're creating a copy of the closure for which we set the <em>delegate</em>, <em>owner</em> and <em>thisObject</em> values. Setting the owner and the "this" object is not very important here since we will use the <em>DELEGATE_ONLY</em> strategy which says that the method calls will be resolved only against the delegate of the closure. Then, we're just calling the code and we're done!</p><h2>Documentation is important</h2><p>One of the problems with the code that we've shown is that the user of the <em>email</em> method doesn't have any information about the methods that he's allowed to call inside the closure. The only possible information is from the method documentation. There are two issues with this: first of all, documentation is not always written, and if it is, it's not always available (javadoc not downloaded, for example). Second, it doesn't help IDEs. What would be really interesting, here, is for IDEs to help the developper by suggesting, once they are in the closure body, methods that exist on the <em>Email</em> class.</p><p>Moreover, if the user calls a method in the closure which is not defined by the <em>Email </em>class, the IDE should at least issue a warning (because it's very likely that it will break at runtime).</p><h2>Type checking builders</h2><p>Another problem with the code that we've shown is that it is not compatible with static type checking. If you try to perform type checking on this code:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>email { from 'dsl-guru@mycompany.com' to 'john.doe@waitaminute.com' subject 'The pope has resigned!' body { p 'Really, the pope has resigned!' } }</pre></td></tr></table><p>Then the type checker will know that there's an <em>email</em> method accepting a <em>closure</em>, but it will complain for every method call <strong>inside</strong> the closure, because <em>from</em>, for example, is not a method which is defined in the class. Indeed, it's defined in the <em>Email</em> class and it has absolutely no hint to help it knowing that the closure delegate will, at runtime, be of type <em>Email</em>.</p><h2>@DelegatesTo</h2><p>For those reasons, Groovy 2.1 introduces a new annotation named @DelegatesTo. The goal of this annotation is to solve both the documentation issue, that will let your IDE know about the expected methods in the closure body, and it will also solve the type checking issue, by giving hints to the compiler about what are the potential receivers of method calls in the closure body.</p><p>The idea is to annotate the <em>Closure</em> parameter of the <em>email</em> method:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def email(@DelegatesTo(Email) cl) { ...}</pre></td></tr></table><p>What we've done here is telling the compiler (or the IDE) that when the method will be called with a closure, the delegate of this closure will be set to an object of type <em>Email</em>. But there is still a problem: the defaut delegation strategy is not the one which is used in our method. So we will give more information and tell the compiler (or the IDE) that the delegation strategy is also changed:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def email(@DelegatesTo(strategy=Closure.DELEGATE_ONLY, value=Email) cl) { ... }</pre></td></tr></table><p>Now, both the IDE and the type checker (if you are using <em>@TypeChecked</em>) will be aware of the delegate and the delegation strategy. This is very nice because it will both allow the IDE to provide smart completion, but it will also remove errors at compile time that exist only because the behaviour of the program is normally only known at runtime!</p><h1>DelegatesTo modes</h1><p>@DelegatesTo supports multiple modes that we will describe with examples in this section.</p><h2>Simple delegation</h2><p>In this mode, the only mandatory parameter is the <em>value</em> which says to which class we delegate calls. Nothing more. We're telling the compiler that the type of the delegate will <strong>always</strong> be of the type documented by <em>@DelegatesTo</em> (note that it can be a subclass, but if it is, the methods defined by the subclass will not be visible to the type checker).</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def build(@DelegatesTo(BuilderItem) cl) { ... }</pre></td></tr></table><h2>Delegation strategy</h2><p>In this mode, you must specify both the delegate class <strong>and</strong> a delegation strategy. This must be used if the closure will not be called with the default delegation strategy, which is <em>Closure.OWNER_FIRST</em>.</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def build(@DelegatesTo(strategy=Closure.DELEGATE_FIRST, value=BuilderItem) { ... }</pre></td></tr></table><h2>Delegate to parameter</h2><p>In this variant, we will tell the compiler that we are delegating to another parameter of the method. Take the following code:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def exec(Object target, Closure code) { def clone = code.rehydrate(target, this, this) clone() }</pre></td></tr></table><p>Here, the delegate which will be used is <strong>not</strong> created inside the <em>exec</em> method. In fact, we take an argument of the method and delegate to it. Usage may look like this:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def email = new Email() exec(email) { from '...' to '...' send() }</pre></td></tr></table><p>Each of the method calls are delegated to the <em>email</em> parameter. This is a widely used pattern which is also supported by <em>@DelegatesTo</em> using a companion annotation:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def exec(@DelegatesTo.Target target, @DelegatesTo code) { ... }</pre></td></tr></table><p>A closure is annotated with <em>@DelegatesTo</em>, but this time, without specifying any class. Instead, we're annotating another parameter with <em>@DelegatesTo.Target</em>. The type of the delegate is then determined at compile time. One could think that we are using the parameter type, which in this case is <em>Object</em> but this is not true<em>.</em> Take this code:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>class Greeter { void sayHello() { println 'Hello' } } def greeter = new Greeter() exec(greeter) { sayHello() }</pre></td></tr></table><p>Remember that this works out of the box <strong>without</strong> having to annotate with <em>@DelegatesTo</em>. However, to make the IDE aware of the delegate type, or the <strong>type checker</strong> aware of it, we need to add <em>@DelegatesTo</em>. And in this case, it will now that the <em>greeter</em> variable is of type <em>Greeter</em>, so it will not report errors on the <em>sayHello</em> method <strong>even if the exec method doesn't explicitely define the target as of type Greeter</strong>. This is a very powerful feature, because it prevents you from writing multiple versions of the same <em>exec</em> method for different receiver types!</p><p>In this mode, the <em>@DelegatesTo</em> annotation also supports the <em>strategy</em> parameter that we've described upper.</p><h2>Multiple closures</h2><p>In the previous example, the <em>exec</em> method accepted only one closure, but you may have methods that take multiple closures:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def guardWith(Closure guard, Closure code, Closure fallback) { ... }</pre></td></tr></table><p>Then nothing prevents you from annotating each closure with <em>@DelegatesTo</em>:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def guardWith(@DelegatesTo(Foo) Closure guard, @DelegatesTo(Bar) Closure code, @DelegatesTo(Baz) Closure fallback) { ... }</pre></td></tr></table><p>But more importantly, if you have multiple closures <strong>and</strong> multiple arguments, you can use several targets:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def guardWith(@DelegatesTo.Target('guarded') guarded, @DelegatesTo.Target('codeObject') codeObject, @DelegatesTo.Target('fallbackObject') fallbackObject, @DelegatesTo(target='guarded') Closure guard, @DelegatesTo(target='codeObject') Closure code, @DelegatesTo(target='fallbackObject') Closure fallback) { ... }</pre></td></tr></table><p>At this point, you may wonder why we don't use the parameter names as references. The reason is that the information (the parameter name) is not always available (it's a debug-only information), so it's a limitation of the JVM.</p><h1>Statically compiled builders</h1><h2>Leveraging @DelegatesTo</h2><p>So far, we've only talked about static type checking (<em>@TypeChecked</em>) and IDE completion, but not about static compilation (<em>@CompileStatic</em>). So the question is whether <em>@CompileStatic</em> support it too (and the answer is of course yes!). In this section, we will show you how you can create a type-safe, statically compiled builder leveraging <em>@DelegatesTo</em>. As an example, we will use an <em>HtmlBuilder</em>, which is pretty much like a MarkupBuilder, apart from the fact that we will only allow some tags (for the sake of the example). Also make sure that we are not saying this is the <strong>best</strong> way to implement such a builder, we're just showing you the capabilities of <em>@DelegatesTo</em> with regards to static compilation. Let's start with the top level class:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>import groovy.transform.CompileStatic @CompileStatic class HTMLBuilder { final StringBuilder sb = new StringBuilder() String getText() { sb.toString() } }</pre></td></tr></table><p>The usage would be:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>def builder = new HTMLBuilder() builder.html { ... }</pre></td></tr></table><p>So we miss the "html" method (other properties/methods removed for clarity):</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>@CompileStatic class HTMLBuilder { public static void delegateToTag(Class clazz, Closure body) { def tag = clazz.newInstance() def clone = body.rehydrate(tag, null, null) clone() } void html(@DelegatesTo(HTMLTag) Closure html) { delegateToTag(HTMLTag, html) } private static class HTMLTag {} }</pre></td></tr></table><p>Here, we defined two methods:</p><ul><li><em>delegateToTag</em> takes a class argument and a closure, then creates a new instance of this class. The closure is then cloned and it's delegate is set to the tag and the closure is called.</li><li><em>html</em> tells that the closure will delegate to an HTML tag, so we create an empty (for now) HTML class</li></ul><p>We have two problems to solve:</p><ul><li>make the tag builder aware of the stringbuilder so that we can generate our text</li><li>make the tag itself able to support sub-tags</li></ul><p>To solve the first issue, we will create an abstract base class for all tags:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>@CompileStatic abstract class Tag { protected final StringBuilder sb Tag(StringBuilder sb) { this.sb = sb } void openTag() {} void closeTag() {} public void delegateToTag(Class clazz, Closure body) { Tag tag = (Tag) clazz.newInstance(sb) def clone = body.rehydrate(tag, this, this) tag.openTag() clone() tag.closeTag() } } </pre></td></tr></table><p>Then our HTMLTag just needs to extends that class:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>@InheritConstructors private static class HTMLTag extends Tag { }</pre></td></tr></table><p>Note that we also moved the <em>delegateToTag</em> method and removed the static modifier, so that we can pass the stringbuilder accross several tag instances, and it now calls <em>openTag</em> before calling the closure, and <em>closeTag</em> after. So now, the main builder looks like this:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>@CompileStatic class HTMLBuilder extends Tag { HTMLBuilder() { super(new StringBuilder()) } void html(@DelegatesTo(HTMLTag) Closure html) { delegateToTag(HTMLTag, html) } @InheritConstructors private static class HTMLTag extends Tag {} }</pre></td></tr></table><p>The next step is to generate HTML:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> @InheritConstructors private static class HTMLTag extends Tag { void openTag() { sb.append '<html>' } void closeTag() { sb.append '</html>' } }</pre></td></tr></table><p>Now what we will do is add a <em>head</em> and a <em>body</em> tag:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> @InheritConstructors private static class HTMLTag extends Tag { void openTag() { sb.append '<html>' } void closeTag() { sb.append '</html>' } void body(@DelegatesTo(BodyTag) Closure body) { delegateToTag(BodyTag, body) } void head(@DelegatesTo(HeadTag) Closure head) { delegateToTag(HeadTag, head) } } @InheritConstructors private static class BodyTag extends Tag { void openTag() { sb.append '<body>' } void closeTag() { sb.append '</body>' } } @InheritConstructors private static class HeadTag extends Tag { void openTag() { sb.append '<head>' } void closeTag() { sb.append '</head>' } } </pre></td></tr></table><p>Last but not least, let's make the body tag support a <em>p</em> tag:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>void p(String body) { sb << "<p>$body</p>" }</pre></td></tr></table><p>Now let's see that we can statically compile the usage too:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>@CompileStatic String test() { def builder = new HTMLBuilder() builder.html { body { p 'Hello, static builder!' } } builder.text } test()</pre></td></tr></table><p>This prints:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre><html><body><p>Hello, static builder!</p></body></html></pre></td></tr></table><p>But one problem here is that our <em>p</em> tag doesn't support subtags... Can we make it support it too? Sure!</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>void p(@DelegatesTo(PTag) Closure p) { delegateToTag(PTag, p) } // ... @InheritConstructors static class PTag extends Tag { void openTag() { sb << "<p>" } void closeTag() { sb << "</p>" } }</pre></td></tr></table><p>And here's how to use it:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>builder.html { body { p 'Hello, static builder!' p { sb << 'Inside!' // sb works too! } } }</pre></td></tr></table><p>As you can see, here, inside the <em>p</em> tag, we're using <em>sb</em>. This is possible because <em>delegateToTag</em> sets the <em>owner</em> to <em>sb</em>. The static compiler recognizes it and is able to use it, without a single compilation error! So, let's put below the complete example:</p><table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre>import groovy.transform.CompileStatic import groovy.transform.InheritConstructors @CompileStatic abstract class Tag { protected final StringBuilder sb Tag(StringBuilder sb) { this.sb = sb } void openTag() {} void closeTag() {} public void delegateToTag(Class clazz, Closure body) { Tag tag = (Tag) clazz.newInstance(sb) def clone = body.rehydrate(tag, this, this) tag.openTag() clone() tag.closeTag() } } @CompileStatic class HTMLBuilder extends Tag { HTMLBuilder() { super(new StringBuilder()) } void html(@DelegatesTo(HTMLTag) Closure html) { delegateToTag(HTMLTag, html) } String getText() { sb } @InheritConstructors private static class HTMLTag extends Tag { void openTag() { sb.append '<html>' } void closeTag() { sb.append '</html>' } void body(@DelegatesTo(BodyTag) Closure body) { delegateToTag(BodyTag, body) } void head(@DelegatesTo(HeadTag) Closure head) { delegateToTag(HeadTag, head) } } @InheritConstructors private static class BodyTag extends Tag { void openTag() { sb.append '<body>' } void closeTag() { sb.append '</body>' } void p(String body) { sb << body } void p(@DelegatesTo(PTag) Closure p) { delegateToTag(PTag, p) } } @InheritConstructors private static class HeadTag extends Tag { void openTag() { sb.append '<head>' } void closeTag() { sb.append '</head>' } } @InheritConstructors static class PTag extends Tag { void openTag() { sb << "<p>" } void closeTag() { sb << "</p>" } } } @CompileStatic String test() { def builder = new HTMLBuilder() builder.html { body { p 'Hello, static builder!' p { sb << 'Inside!' } } } builder.text } test()</pre></td></tr></table><p>Congratulations, you've built your first statically compiled builder in Groovy!</p>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced