How to set node attribute dynamically using NodeBuilder pattern in groovy
How do I set a dynamic node attribute based on a condition in groovy when using the NodeBuilder pattern?
Like the following
Preferably it would be nice to reference the current element in the conditional statement since the condition might appear deep down in the structure.
1 Answer 1
The easiest way to do this is to evaluate the condition for the dynamic attribute outside the node closure. For Example:
Alternatively, you can create a map of the attributes beforehand:
-
The Overflow Blog
Related
Hot Network Questions
Subscribe to RSS
To subscribe to this RSS feed, copy and paste this URL into your RSS reader.
Site design / logo © 2023 Stack Exchange Inc; user contributions licensed under CC BY-SA . rev 2023.10.19.43685
By clicking “Accept all cookies”, you agree Stack Exchange can store cookies on your device and disclose information in accordance with our Cookie Policy.
Русские Блоги
Groovy выдвигает чудесную и простость использования XML в крайнем. Интерфейс. Среди них, генерация сценариев Ant и Maven Build; даже распространяется на более широкое поле.
[list]
[*] Groovy.xml.markupbuilder — сериализуйте ваши объекты на XML или XHTML
[*] Groovy.xml.saxBuilder -С может использоваться для существующих саксофона.
[*] Groovy.xml.dombuileder -create и проанализируйте документацию DOM
[*] Groovy.Util.AntBuilder -Используется для создания файлов сборки муравья
[*] Groovy.swing.swingbuilder -Используется для создания качающего пользовательского интерфейса
[*] Groovy.util.nodebuilder — Создать дерево -подобную структуру общего объекта
[/list]
[B] Построить XML [/b]
Производственный контент заключается в следующем:
<langs type=’current’>
<language version=’1.6′ flavor=’static’>java</language>
<language version=’1.7′>groovy</language>
<language version=’4′>javascript</language>
</langs>
1. Структура по умолчанию MarkupBuilder выводится в консоль. Вы также можете принять другие параметры IndentPrinter, PrintWriter и Writer, которые соответственно указывают на различные выходные направления. Таким образом, вы можете управлять выходом на розетке или подождать на веб -странице Groovlet.
2. Имя метода MarkupBuilder воспринимается как должное.
3. Метод в закрытии становится суб -лабелой внешнего метода (метка). Например, язык () выше генерирует суб -label <language> sub -label <langs> Соответствующая метка <langs>.
[B] построить html [/b] через markupbuilder [/b]
MarkupBuilder очень подходит для одновременного построения простых документов XML. Для более продвинутого создания XML Gloovy предоставляет [B] StreamingMarkPbuilder [/b]. Через это вы можете добавить различные контенты XML, такие как инструкции по обработке, пространство имени и неподготовленный текст объекта справки MKP (очень подходящий для блока CDATA)
Domain-Specific Languages
Groovy lets you omit parentheses around the arguments of a method call for top-level statements. «command chain» feature extends this by allowing us to chain such parentheses-free method calls, requiring neither parentheses around arguments, nor dots between the chained calls. The general idea is that a call like a b c d will actually be equivalent to a(b).c(d) . This also works with multiple arguments, closure arguments, and even named arguments. Furthermore, such command chains can also appear on the right-hand side of assignments. Let’s have a look at some examples supported by this new syntax:
It is also possible to use methods in the chain which take no arguments, but in that case, the parentheses are needed:
If your command chain contains an odd number of elements, the chain will be composed of method / arguments, and will finish by a final property access:
This command chain approach opens up interesting possibilities in terms of the much wider range of DSLs which can now be written in Groovy.
The above examples illustrate using a command chain based DSL but not how to create one. There are various strategies that you can use, but to illustrate creating such a DSL, we will show a couple of examples — first using maps and Closures:
As a second example, consider how you might write a DSL for simplifying one of your existing APIs. Maybe you need to put this code in front of customers, business analysts or testers who might be not hard-core Java developers. We’ll use the Splitter from the Google Guava libraries project as it already has a nice Fluent API. Here is how we might use it out of the box:
It reads fairly well for a Java developer but if that is not your target audience or you have many such statements to write, it could be considered a little verbose. Again, there are many options for writing a DSL. We’ll keep it simple with Maps and Closures. We’ll first write a helper method:
now instead of this line from our original example:
we can write this:
2. Operator overloading
Various operators in Groovy are mapped onto regular method calls on objects.
This allows you to provide your own Java or Groovy objects which can take advantage of operator overloading. The following table describes the operators supported in Groovy and the methods they map to.
3. Script base classes
3.1. The Script class
Groovy scripts are always compiled to classes. For example, a script as simple as:
is compiled to a class extending the abstract groovy.lang.Script class. This class contains a single abstract method called run. When a script is compiled, then its body will become the run method, while the other methods found in the script are found in the implementing class. The Script class provides base support for integration with your application through the Binding object, as illustrated in this example:
| 1 | a binding is used to share data between the script and the calling class |
| 2 | a GroovyShell can be used with this binding |
| 3 | input variables are set from the calling class inside the binding |
| 4 | then the script is evaluated |
| 5 | and the z variable has been «exported» into the binding |
This is a very practical way to share data between the caller and the script, however it may be insufficient or not practical in some cases. For that purpose, Groovy allows you to set your own base script class. A base script class has to extend groovy.lang.Script and be a single abstract method type:
Then the custom script base class can be declared in the compiler configuration, for example:
| 1 | create a custom compiler configuration |
| 2 | set the base script class to our custom base script class |
| 3 | then create a GroovyShell using that configuration |
| 4 | the script will then extend the base script class, giving direct access to the name property and greet method |
3.2. The @BaseScript annotation
As an alternative, it is also possible to use the @BaseScript annotation directly into a script:
where @BaseScript should annotate a variable which type is the class of the base script. Alternatively, you can set the base script class as a member of the @BaseScript annotation itself:
3.3. Alternate abstract method
We have seen that the base script class is a single abstract method type that needs to implement the run method. The run method is executed by the script engine automatically. In some circumstances it may be interesting to have a base class which implements the run method, but provides an alternative abstract method to be used for the script body. For example, the base script run method might perform some initialization before the run method is executed. This is possible by doing this:
| 1 | the base script class should define one (and only one) abstract method |
| 2 | the run method can be overridden and perform a task before executing the script body |
| 3 | run calls the abstract scriptBody method which will delegate to the user script |
| 4 | then it can return something else than the value from the script |
If you execute this code:
Then you will see that the script is executed, but the result of the evaluation is 1 as returned by the run method of the base class. It is even clearer if you use parse instead of evaluate , because it would allow you to execute the run method several times on the same script instance:
4. Adding properties to numbers
In Groovy number types are considered equal to any other types. As such, it is possible to enhance numbers by adding properties or methods to them. This can be very handy when dealing with measurable quantities for example. Details about how existing classes can be enhanced in Groovy are found in the extension modules section or the categories section.
An illustration of this can be found in Groovy using the TimeCategory :
| 1 | using the TimeCategory , a property minute is added to the Integer class |
| 2 | similarly, the months method returns a groovy.time.DatumDependentDuration which can be used in calculus |
Categories are lexically bound, making them a great fit for internal DSLs.
5. @DelegatesTo
5.1. Explaining delegation strategy at compile time
@groovy.lang.DelegatesTo is a documentation and compile-time annotation aimed at:
documenting APIs that use closures as arguments
providing type information for the static type checker and compiler
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:
One way of implementing this is using the builder strategy, which implies a method, named email which accepts a closure as an argument. The method may delegate subsequent calls to an object that implements the from , to , subject and body methods. Again, body is a method which accepts a closure as an argument and that uses the builder strategy.
Implementing such a builder is usually done the following way:
the EmailSpec class implements the from , to , … methods. By calling rehydrate , we’re creating a copy of the closure for which we set the delegate , owner and thisObject values. Setting the owner and the this object is not very important here since we will use the DELEGATE_ONLY strategy which says that the method calls will be resolved only against the delegate of the closure.
The EmailSpec class has itself a body method accepting a closure that is cloned and executed. This is what we call the builder pattern in Groovy.
One of the problems with the code that we’ve shown is that the user of the email 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 developer by suggesting, once they are in the closure body, methods that exist on the email class.
Moreover, if the user calls a method in the closure which is not defined by the EmailSpec class, the IDE should at least issue a warning (because it’s very likely that it will break at runtime).
One more problem with the code above is that it is not compatible with static type checking. Type checking would let the user know if a method call is authorized at compile time instead of runtime, but if you try to perform type checking on this code:
Then the type checker will know that there’s an email method accepting a Closure , but it will complain about every method call inside the closure, because from , for example, is not a method which is defined in the class. Indeed, it’s defined in the EmailSpec class and it has absolutely no hint to help it knowing that the closure delegate will, at runtime, be of type EmailSpec :
will fail compilation with errors like this one:
5.2. @DelegatesTo
For those reasons, Groovy 2.1 introduced 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.
The idea is to annotate the Closure parameter of the email method:
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 email . But there is still a problem: the default 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:
Now, both the IDE and the type checker (if you are using @TypeChecked ) 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!
The following code will now pass compilation:
5.3. DelegatesTo modes
@DelegatesTo supports multiple modes that we will describe with examples in this section.
5.3.1. Simple delegation
In this mode, the only mandatory parameter is the value which says to which class we delegate calls. Nothing more. We’re telling the compiler that the type of the delegate will always be of the type documented by @DelegatesTo (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).
5.3.2. Delegation strategy
In this mode, you must specify both the delegate class and a delegation strategy. This must be used if the closure will not be called with the default delegation strategy, which is Closure.OWNER_FIRST .
5.3.3. Delegate to parameter
In this variant, we will tell the compiler that we are delegating to another parameter of the method. Take the following code:
Here, the delegate which will be used is not created inside the exec method. In fact, we take an argument of the method and delegate to it. Usage may look like this:
Each of the method calls are delegated to the email parameter. This is a widely used pattern which is also supported by @DelegatesTo using a companion annotation:
A closure is annotated with @DelegatesTo , but this time, without specifying any class. Instead, we’re annotating another parameter with @DelegatesTo.Target . 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 Object but this is not true. Take this code:
Remember that this works out of the box without having to annotate with @DelegatesTo . However, to make the IDE aware of the delegate type, or the type checker aware of it, we need to add @DelegatesTo . And in this case, it will know that the Greeter variable is of type Greeter , so it will not report errors on the sayHello method even if the exec method doesn’t explicitly define the target as of type Greeter. This is a very powerful feature, because it prevents you from writing multiple versions of the same exec method for different receiver types!
In this mode, the @DelegatesTo annotation also supports the strategy parameter that we’ve described upper.
5.3.4. Multiple closures
In the previous example, the exec method accepted only one closure, but you may have methods that take multiple closures:
Then nothing prevents you from annotating each closure with @DelegatesTo :
But more importantly, if you have multiple closures and multiple arguments, you can use several targets:
| 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. |
5.3.5. Delegating to a generic type
In some situations, it is interesting to instruct the IDE or the compiler that the delegate type will not be a parameter but a generic type. Imagine a configurator that runs on a list of elements:
Then this method can be called with any list like this:
To let the type checker and the IDE know that the configure method calls the closure on each element of the list, you need to use @DelegatesTo differently:
@DelegatesTo takes an optional genericTypeIndex argument that tells what is the index of the generic type that will be used as the delegate type. This must be used in conjunction with @DelegatesTo.Target and the index starts at 0. In the example above, that means that the delegate type is resolved against List<T> , and since the generic type at index 0 is T and inferred as a Realm , the type checker infers that the delegate type will be of type Realm .
| We’re using a genericTypeIndex instead of a placeholder ( T ) because of JVM limitations. |
5.3.6. Delegating to an arbitrary type
It is possible that none of the options above can represent the type you want to delegate to. For example, let’s define a mapper class which is parametrized with an object and defines a map method which returns an object of another type:
| 1 | The mapper class takes two generic type arguments: the source type and the target type |
| 2 | The source object is stored in a final field |
| 3 | The map method asks to convert the source object to a target object |
As you can see, the method signature from map does not give any information about what object will be manipulated by the closure. Reading the method body, we know that it will be the value which is of type T , but T is not found in the method signature, so we are facing a case where none of the available options for @DelegatesTo is suitable. For example, if we try to statically compile this code:
Then the compiler will fail with:
In that case, you can use the type member of the @DelegatesTo annotation to reference T as a type token:
| 1 | The @DelegatesTo annotation references a generic type which is not found in the method signature |
Note that you are not limited to generic type tokens. The type member can be used to represent complex types, such as List<T> or Map<T,List<U>> . The reason why you should use that in last resort is that the type is only checked when the type checker finds usage of @DelegatesTo , not when the annotated method itself is compiled. This means that type safety is only ensured at the call site. Additionally, compilation will be slower (though probably unnoticeable for most cases).
6. Compilation customizers
6.1. Introduction
Whether you are using groovyc to compile classes or a GroovyShell , for example, to execute scripts, under the hood, a compiler configuration is used. This configuration holds information like the source encoding or the classpath but it can also be used to perform more operations like adding imports by default, applying AST transformations transparently or disabling global AST transformations.
The goal of compilation customizers is to make those common tasks easy to implement. For that, the CompilerConfiguration class is the entry point. The general schema will always be based on the following code:
Compilation customizers must extend the org.codehaus.groovy.control.customizers.CompilationCustomizer class. A customizer works:
on a specific compilation phase
on every class node being compiled
You can implement your own compilation customizer but Groovy includes some of the most common operations.
6.2. Import customizer
Using this compilation customizer, your code will have imports added transparently. This is in particular useful for scripts implementing a DSL where you want to avoid users from having to write imports. The import customizer will let you add all the variants of imports the Groovy language allows, that is:
class imports, optionally aliased
static imports, optionally aliased
static star imports
A detailed description of all shortcuts can be found in org.codehaus.groovy.control.customizers.ImportCustomizer
6.3. AST transformation customizer
The AST transformation customizer is meant to apply AST transformations transparently. Unlike global AST transformations that apply on every class being compiled as long as the transform is found on classpath (which has drawbacks like increasing the compilation time or side effects due to transformations applied where they should not), the customizer will allow you to selectively apply a transform only for specific scripts or classes.
As an example, let’s say you want to be able to use @Log in a script. The problem is that @Log is normally applied on a class node and a script, by definition, doesn’t require one. But implementation wise, scripts are classes, it’s just that you cannot annotate this implicit class node with @Log . Using the AST customizer, you have a workaround to do it:
That’s all! Internally, the @Log AST transformation is applied to every class node in the compilation unit. This means that it will be applied to the script, but also to classes defined within the script.
If the AST transformation that you are using accepts parameters, you can use parameters in the constructor too:
As the AST transformation customizers works with objects instead of AST nodes, not all values can be converted to AST transformation parameters. For example, primitive types are converted to ConstantExpression (that is LOGGER is converted to new ConstantExpression(‘LOGGER’) , but if your AST transformation takes a closure as an argument, then you have to give it a ClosureExpression , like in the following example:
6.4. Secure AST customizer
This customizer will allow the developer of a DSL to restrict the grammar of the language, for example, to prevent users from using particular constructs. It is only «secure» in that one aspect, i.e. limiting the allowable constructs within a DSL. It does not replace a security manager which might additionally be needed as an orthogonal aspect of overall security. The only reason for it to exist is to limit the expressiveness of the language. This customizer only works at the AST (abstract syntax tree) level, not at runtime! It can be strange at first glance, but it makes much more sense if you think of Groovy as a platform to build DSLs. You may not want a user to have a complete language at hand. In the example below, we will demonstrate it using an example of language that only allows arithmetic operations, but this customizer allows you to:
allow/disallow creation of closures
allow/disallow package definition
allow/disallow definition of methods
restrict the receivers of method calls
restrict the kind of AST expressions a user can use
restrict the tokens (grammar-wise) a user can use
restrict the types of the constants that can be used in code
For all those features, the secure AST customizer works using either an allowed list (list of elements that are permitted) or a disallowed list (list of elements that are not permitted). For each type of feature (imports, tokens, …) you have the choice to use either an allowed or disallowed list, but you can mix dis/allowed lists for distinct features. Typically, you will choose allowed lists (which permits only the constructs listed and disallows all others).
| 1 | use for token types from org.codehaus.groovy.syntax.Types |
| 2 | you can use class literals here |
If what the secure AST customizer provides out of the box isn’t enough for your needs, before creating your own compilation customizer, you might be interested in the expression and statement checkers that the AST customizer supports. Basically, it allows you to add custom checks on the AST tree, on expressions (expression checkers) or statements (statement checkers). For this, you must implement org.codehaus.groovy.control.customizers.SecureASTCustomizer.StatementChecker or org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker .
Those interfaces define a single method called isAuthorized , returning a boolean, and taking a Statement (or Expression ) as a parameter. It allows you to perform complex logic over expressions or statements to tell if a user is allowed to do it or not.
For example, there’s no predefined configuration flag in the customizer which will let you prevent people from using an attribute expression. Using a custom checker, it is trivial:
Then we can make sure that this works by evaluating a simple script:
| 1 | will fail compilation |
6.5. Source aware customizer
This customizer may be used as a filter on other customizers. The filter, in that case, is the org.codehaus.groovy.control.SourceUnit . For this, the source aware customizer takes another customizer as a delegate, and it will apply customization of that delegate only and only if predicates on the source unit match.
SourceUnit gives you access to multiple things but in particular the file being compiled (if compiling from a file, of course). It gives you the potential to perform operation based on the file name, for example. Here is how you would create a source aware customizer:
Then you can use predicates on the source aware customizer:
6.6. Customizer builder
If you are using compilation customizers in Groovy code (like the examples above) then you can use an alternative syntax to customize compilation. A builder ( org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder ) simplifies the creation of customizers using a hierarchical DSL.
| 1 | static import of the builder method |
| 2 | configuration goes here |
The code sample above shows how to use the builder. A static method, withConfig, takes a closure corresponding to the builder code, and automatically registers compilation customizers to the configuration. Every compilation customizer available in the distribution can be configured this way:
6.6.1. Import customizer
6.6.2. AST transformation customizer
| 1 | apply @Log transparently |
| 2 | apply @Log with a different name for the logger |
6.6.3. Secure AST customizer
6.6.4. Source aware customizer
| 1 | apply CompileStatic AST annotation on .sgroovy files |
| 2 | apply CompileStatic AST annotation on .sgroovy or .sg files |
| 3 | apply CompileStatic AST annotation on files whose name is ‘foo’ |
| 4 | apply CompileStatic AST annotation on files whose name is ‘foo’ or ‘bar’ |
| 5 | apply CompileStatic AST annotation on files that do not contain a class named ‘Baz’ |
6.6.5. Inlining a customizer
Inlined customizer allows you to write a compilation customizer directly, without having to create a class for it.
| 1 | define an inlined customizer which will execute at the CONVERSION phase |
| 2 | prints the name of the class node being compiled |
6.6.6. Multiple customizers
Of course, the builder allows you to define multiple customizers at once:
6.7. The configscript commandline parameter
So far, we have described how you can customize compilation using a CompilationConfiguration class, but this is only possible if you embed Groovy and that you create your own instances of CompilerConfiguration (then use it to create a GroovyShell , GroovyScriptEngine , …).
If you want it to be applied on the classes you compile with the normal Groovy compiler (that is to say with groovyc , ant or gradle , for example), it is possible to use a commandline parameter named configscript that takes a Groovy configuration script as argument.
This script gives you access to the CompilerConfiguration instance before the files are compiled (exposed into the configuration script as a variable named configuration ), so that you can tweak it.
It also transparently integrates the compiler configuration builder above. As an example, let’s see how you would activate static compilation by default on all classes.
6.7.1. Configscript example: Static compilation by default
Normally, classes in Groovy are compiled with a dynamic runtime. You can activate static compilation by placing an annotation named @CompileStatic on any class. Some people would like to have this mode activated by default, that is to say not having to annotate (potentially many) classes. Using configscript , makes this possible. First of all, you need to create a file named config.groovy into say src/conf with the following contents:
| 1 | configuration references a CompilerConfiguration instance |
That is actually all you need. You don’t have to import the builder, it’s automatically exposed in the script. Then, compile your files using the following command line:
We strongly recommend you to separate configuration files from classes, hence why we suggest using the src/main and src/conf directories above.
6.7.2. Configscript example: Setting system properties
In a configuration script you can also set system properties, e.g.:
If you have numerous system properties to set, then using a configuration file will reduce the need to set a bunch of system properties with a long command line or appropriately defined environment variable. You can also share all the settings by simply sharing the config file.
6.8. AST transformations
runtime metaprogramming doesn’t allow you to do what you want
you need to improve the performance of the execution of your DSLs
you want to leverage the same syntax as Groovy but with different semantics
you want to improve support for type checking in your DSLs
Then AST transformations are the way to go. Unlike the techniques used so far, AST transformations are meant to change or generate code before it is compiled to bytecode. AST transformations are capable of adding new methods at compile time for example, or totally changing the body of a method based on your needs. They are a very powerful tool but also come at the price of not being easy to write. For more information about AST transformations, please take a look at the compile-time metaprogramming section of this manual.
7. Custom type checking extensions
It may be interesting, in some circumstances, to provide feedback about wrong code to the user as soon as possible, that is to say when the DSL script is compiled, rather than having to wait for the execution of the script. However, this is not often possible with dynamic code. Groovy actually provides a practical answer to this known as type checking extensions.
8. Builders
Many tasks require building things and the builder pattern is one technique used by developers to make building things easier, especially building of structures which are hierarchical in nature. This pattern is so ubiquitous that Groovy has special built-in support. Firstly, there are many built-in builders. Secondly, there are classes which make it easier to write your own builders.
8.1. Existing builders
Groovy comes with many built-in builders. Let’s look at some of them.
8.1.1. MarkupBuilder
8.1.2. StreamingMarkupBuilder
8.1.3. SaxBuilder
A builder for generating Simple API for XML (SAX) events.
If you have the following SAX handler:
You can use SaxBuilder to generate SAX events for the handler like this:
And then check that everything worked as expected:
8.1.4. StaxBuilder
A Groovy builder that works with Streaming API for XML (StAX) processors.
Here is a simple example using the StAX implementation of Java to generate XML:
An external library such as Jettison can be used as follows:
8.1.5. DOMBuilder
A builder for parsing HTML, XHTML and XML into a W3C DOM tree.
For example this XML String :
Can be parsed into a DOM tree with a DOMBuilder like this:
And then processed further e.g. by using DOMCategory:
8.1.6. NodeBuilder
NodeBuilder is used for creating nested trees of groovy.util.Node objects for handling arbitrary data. To create a simple user list you use a NodeBuilder like this:
Now you can process the data further, e.g. by using GPath expressions:
8.1.7. JsonBuilder
Groovy’s JsonBuilder makes it easy to create Json. For example to create this Json string:
you can use a JsonBuilder like this:
We use JsonUnit to check that the builder produced the expected result:
If you need to customize the generated output you can pass a JsonGenerator instance when creating a JsonBuilder :
8.1.8. StreamingJsonBuilder
Unlike JsonBuilder which creates a data structure in memory, which is handy in those situations where you want to alter the structure programmatically before output, StreamingJsonBuilder directly streams to a writer without any intermediate memory data structure. If you do not need to modify the structure and want a more memory-efficient approach, use StreamingJsonBuilder .
The usage of StreamingJsonBuilder is similar to JsonBuilder . In order to create this Json string:
you use a StreamingJsonBuilder like this:
We use JsonUnit to check the expected result:
If you need to customize the generated output you can pass a JsonGenerator instance when creating a StreamingJsonBuilder :
8.1.9. SwingBuilder
SwingBuilder allows you to create full-fledged Swing GUIs in a declarative and concise fashion. It accomplishes this by employing a common idiom in Groovy, builders. Builders handle the busywork of creating complex objects for you, such as instantiating children, calling Swing methods, and attaching these children to their parents. As a consequence, your code is much more readable and maintainable, while still allowing you to access to the full range of Swing components.
Here’s a simple example of using SwingBuilder :
Here is what it will look like:

This hierarchy of components would normally be created through a series of repetitive instantiations, setters, and finally attaching this child to its respective parent. Using SwingBuilder , however, allows you to define this hierarchy in its native form, which makes the interface design understandable simply by reading the code.
The flexibility shown here is made possible by leveraging the many programming features built-in to Groovy, such as closures, implicit constructor calling, import aliasing, and string interpolation. Of course, these do not have to be fully understood in order to use SwingBuilder ; as you can see from the code above, their uses are intuitive.
Here is a slightly more involved example, with an example of SwingBuilder code re-use via a closure.
Here’s another variation that relies on observable beans and binding:
@Bindable is one of the core AST Transformations. It generates all the required boilerplate code to turn a simple bean into an observable one. The bind() node creates appropriate PropertyChangeListeners that will update the interested parties whenever a PropertyChangeEvent is fired.
8.1.10. AntBuilder
| Here we describe AntBuilder which lets you write Ant build scripts in Groovy rather than XML. You may also be interested in using Groovy from Ant using the Groovy Ant task. |
Despite being primarily a build tool, Apache Ant is a very practical tool for manipulating files including zip files, copy, resource processing, and more. But if ever you’ve been working with a build.xml file or some Jelly script and found yourself a little restricted by all those pointy brackets, or found it a bit weird using XML as a scripting language and wanted something a little cleaner and more straight forward, then maybe Ant scripting with Groovy might be what you’re after.
Groovy has a helper class called AntBuilder which makes the scripting of Ant tasks really easy; allowing a real scripting language to be used for programming constructs (variables, methods, loops, logical branching, classes etc). It still looks like a neat concise version of Ant’s XML without all those pointy brackets; though you can mix and match this markup inside your script. Ant itself is a collection of jar files. By adding them to your classpath, you can easily use them within Groovy as is. We believe using AntBuilder leads to more concise and readily understood syntax.
AntBuilder exposes Ant tasks directly using the convenient builder notation that we are used to in Groovy. Here is the most basic example, which is printing a message on the standard output:
| 1 | creates an instance of AntBuilder |
| 2 | executes the echo task with the message in parameter |
Imagine that you need to create a ZIP file. It can be as simple as:
In the next example, we demonstrate the use of AntBuilder to copy a list of files using a classical Ant pattern directly in Groovy:
Another example would be iterating over a list of files matching a specific pattern:
Or executing a JUnit test:
We can even go further by compiling and executing a Java file directly from Groovy:
It is worth mentioning that AntBuilder is included in Gradle, so you can use it in Gradle just like you would in Groovy. Additional documentation can be found in the Gradle manual.
8.1.11. CliBuilder
CliBuilder provides a compact way to specify the available options for a commandline application and then automatically parse the application’s commandline parameters according to that specification. By convention, a distinction is made between option commandline parameters and any remaining parameters which are passed to an application as its arguments. Typically, several types of options might be supported such as -V or —tabsize=4 . CliBuilder removes the burden of developing lots of code for commandline processing. Instead, it supports a somewhat declarative approach to declaring your options and then provides a single call to parse the commandline parameters with a simple mechanism to interrogate the options (you can think of this as a simple model for your options).
Even though the details of each commandline you create could be quite different, the same main steps are followed each time. First, a CliBuilder instance is created. Then, allowed commandline options are defined. This can be done using a dynamic api style or an annotation style. The commandline parameters are then parsed according to the options specification resulting in a collection of options which are then interrogated.
Here is a simple example Greeter.groovy script illustrating usage:
| 1 | Earlier versions of Groovy had a CliBuilder in the groovy.util package and no import was necessary. In Groovy 2.5, this approach became deprecated: applications should instead choose the groovy.cli.picocli or groovy.cli.commons version. The groovy.util version in Groovy 2.5 points to the commons-cli version for backwards compatibility but has been removed in Groovy 3.0. |
| 2 | define a new CliBuilder instance specifying an optional usage string |
| 3 | specify a -a option taking a single argument with an optional long variant —audience |
| 4 | specify a -h option taking no arguments with an optional long variant —help |
| 5 | parse the commandline parameters supplied to the script |
| 6 | if the h option is found display a usage message |
| 7 | display a standard greeting or, if the a option is found, a customized greeting |
Running this script with no commandline parameters, i.e.:
results in the following output:
Running this script with -h as the single commandline parameter, i.e.:
results in the following output:
Running this script with —audience Groovologist as the commandline parameters, i.e.:
results in the following output:
When creating the CliBuilder instance in the above example, we set the optional usage property within the constructor call. This follows Groovy’s normal ability to set additional properties of the instance during construction. There are numerous other properties which can be set such as header and footer . For the complete set of available properties, see the available properties for the groovy.util.CliBuilder class.
When defining an allowed commandline option, both a short name (e.g. «h» for the help option shown previously) and a short description (e.g. «display usage» for the help option) must be supplied. In our example above, we also set some additional properties such as longOpt and args . The following additional properties are supported when specifying an allowed commandline option:
the name of the argument for this option used in output
the long representation or long name of the option
the number of argument values
int or String (1)
whether the argument value is optional
whether the option is mandatory
the type of this option
the character that is the value separator
a default value
converts the incoming String to the required type
(1) More details later
(2) Single character Strings are coerced to chars in special cases in Groovy
If you have an option with only a longOpt variant, you can use the special shortname of ‘_’ to specify the option, e.g. : cli._(longOpt: ‘verbose’, ‘enable verbose logging’) . Some of the remaining named parameters should be fairly self-explanatory while others deserve a bit more explanation. But before further explanations, let’s look at ways of using CliBuilder with annotations.
Using Annotations and an interface
Rather than making a series of method calls (albeit in a very declarative mini-DSL form) to specify the allowable options, you can provide an interface specification of the allowable options where annotations are used to indicate and provide details for those options and for how unprocessed parameters are handled. Two annotations are used: groovy.cli.Option and groovy.cli.Unparsed.
Here is how such a specification can be defined:
| 1 | Specify a Boolean option set using -h or —help |
| 2 | Specify a String option set using -a or —audience |
| 3 | Specify where any remaining parameters will be stored |
Note how the long name is automatically determined from the interface method name. You can use the longName annotation attribute to override that behavior and specify a custom long name if you wish or use a longName of ‘_’ to indicate that no long name is to be provided. You will need to specify a shortName in such a case.
Here is how you could use the interface specification:
| 1 | Create a CliBuilder instance as before with optional properties |
| 2 | Parse parameters using the interface specification |
| 3 | Interrogate options using the methods from the interface |
| 4 | Parse a different set of parameters |
| 5 | Interrogate the remaining parameters |
When parseFromSpec is called, CliBuilder automatically creates an instance implementing the interface and populates it. You simply call the interface methods to interrogate the option values.
Using Annotations and an instance
Alternatively, perhaps you already have a domain class containing the option information. You can simply annotate properties or setters from that class to enable CliBuilder to appropriately populate your domain object. Each annotation both describes that option’s properties through the annotation attributes and indicates the setter the CliBuilder will use to populate that option in your domain object.
Here is how such a specification can be defined:
| 1 | Indicate that a Boolean property is an option |
| 2 | Indicate that a String property (with explicit setter) is an option |
| 3 | Specify where any remaining args will be stored |
And here is how you could use the specification:
| 1 | Create a CliBuilder instance as before with optional parameters |
| 2 | Create an instance for CliBuilder to populate |
| 3 | Parse arguments populating the supplied instance |
| 4 | Interrogate the String option property |
| 5 | Interrogate the remaining arguments property |
When parseFromInstance is called, CliBuilder automatically populates your instance. You simply interrogate the instance properties (or whatever accessor methods you have provided in your domain object) to access the option values.
Using Annotations and a script
Finally, there are two additional convenience annotation aliases specifically for scripts. They simply combine the previously mentioned annotations and groovy.transform.Field. The groovydoc for those annotations reveals the details: groovy.cli.OptionField and groovy.cli.UnparsedField.
Here is an example using those annotations in a self-contained script that would be called with the same arguments as shown for the instance example earlier:
Options with arguments
We saw in our initial example that some options act like flags, e.g. Greeter -h but others take an argument, e.g. Greeter —audience Groovologist . The simplest cases involve options which act like flags or have a single (potentially optional) argument. Here is an example involving those cases:
| 1 | An option that is simply a flag — the default; setting args to 0 is allowed but not needed. |
| 2 | An option with exactly one argument |
| 3 | An option with an optional argument; it acts like a flag if the option is left out |
| 4 | An example using this spec where an argument is supplied to the ‘c’ option |
| 5 | An example using this spec where no argument is supplied to the ‘c’ option; it’s just a flag |
Note: when an option with an optional argument is encountered, it will (somewhat) greedily consume the next parameter from the supplied commandline parameters. If however, the next parameter matches a known long or short option (with leading single or double hyphens), that will take precedence, e.g. -b in the above example.
Option arguments may also be specified using the annotation style. Here is an interface option specification illustrating such a definition:
And here is how it is used:
This example makes use of an array-typed option specification. We cover this in more detail shortly when we discuss multiple arguments.
Specifying a type
Arguments on the commandline are by nature Strings (or arguably can be considered Booleans for flags) but can be converted to richer types automatically by supplying additional typing information. For the annotation-based argument definition style, these types are supplied using the field types for annotation properties or return types of annotated methods (or the setter argument type for setter methods). For the dynamic method style of argument definition a special ‘type’ property is supported which allows you to specify a Class name.
When an explicit type is defined, the args named-parameter is assumed to be 1 (except for Boolean-typed options where it is 0 by default). An explicit args parameter can still be provided if needed. Here is an example using types with the dynamic api argument definition style:
Primitives, numeric types, files, enums and arrays thereof, are supported (they are converted using org.codehaus.groovy.runtime.StringGroovyMethods#asType).
Custom parsing of the argument String
If the supported types aren’t sufficient, you can supply a closure to handle the String to rich type conversion for you. Here is a sample using the dynamic api style:
Alternatively, you can use the annotation style by supplying the conversion closure as an annotation parameter. Here is an example specification:
And an example using that specification:
Options with multiple arguments
Multiple arguments are also supported using an args value greater than 1. There is a special named parameter, valueSeparator , which can also be optionally used when processing multiple arguments. It allows some additional flexibility in the syntax supported when supplying such argument lists on the commandline. For example, supplying a value separator of ‘,’ allows a comma-delimited list of values to be passed on the commandline.
The args value is normally an integer. It can be optionally supplied as a String. There are two special String symbols: ` and `\*`. The `*` value means 0 or more. The ` value means 1 or more. The * value is the same as using + and also setting the optionalArg value to true.
Accessing the multiple arguments follows a special convention. Simply add an ‘s’ to the normal property you would use to access the argument option and you will retrieve all the supplied arguments as a list. So, for a short option named ‘a’, you access the first ‘a’ argument using options.a and the list of all arguments using options.as . It’s fine to have a shortname or longname ending in ‘s’ so long as you don’t also have the singular variant without the ‘s’. So, if name is one of your options with multiple arguments and guess is another with a single argument, there will be no confusion using options.names and options.guess .
Here is an excerpt highlighting the use of multiple arguments:
| 1 | Args value supplied as a String and comma value separator specified |
| 2 | One or more arguments are allowed |
| 3 | Two commandline parameters will be supplied as the ‘b’ option’s list of arguments |
| 4 | Access the ‘a’ option’s first argument |
| 5 | Access the ‘a’ option’s list of arguments |
| 6 | An alternative syntax for specifying two arguments for the ‘a’ option |
| 7 | The arguments to the ‘b’ option supplied as a comma-separated value |
As an alternative to accessing multiple arguments using the plural name approach, you can use an array-based type for the option. In this case, all options will always be returned via the array which is accessed via the normal singular name. We’ll see an example of this next when discussing types.
Multiple arguments are also supported using the annotation style of option definition by using an array type for the annotated class member (method or property) as this example shows:
Работа с XML в Groovy
Groovy предоставляет значительное количество методов, предназначенных для просмотра и управления содержимым XML.
В этом руководстве мы покажем, как добавлять, редактировать или удалять элементы из XML в Groovy, используя различные подходы. Мы также покажем, как создать структуру XML с нуля .
2. Определение модели
Давайте определим структуру XML в нашем каталоге ресурсов, которую мы будем использовать в наших примерах:
И прочитайте его в переменную InputStream :
3. XML-парсер
Начнем изучение этого потока с класса XmlParser .
3.1. Чтение
Чтение и анализ XML-файла, вероятно, является наиболее распространенной XML-операцией, которую приходится выполнять разработчику. XmlParser предоставляет очень простой интерфейс, предназначенный именно для этого:
На этом этапе мы можем получить доступ к атрибутам и значениям структуры XML, используя выражения GPath.
Давайте теперь реализуем простой тест, используя Spock , чтобы проверить правильность нашего объекта article:
Чтобы понять, как получить доступ к значениям XML и как использовать выражения GPath, давайте сосредоточимся на внутренней структуре результата операции XmlParser#parse .
Объект article является экземпляром groovy.util.Node . Каждый узел состоит из имени, карты атрибутов, значения и родителя (который может быть нулевым или другим узлом) .
В нашем случае значением article является экземпляр groovy.util.NodeList , который является классом-оболочкой для коллекции Node s. NodeList расширяет класс java.util.ArrayList , который обеспечивает извлечение элементов по индексу. Чтобы получить строковое значение узла, мы используем groovy.util.Node#text().
В приведенном выше примере мы ввели несколько выражений GPath:
- article.article[0].author.firstname — получить имя автора первой статьи — article.article [n] будет напрямую обращаться к n -й статье .
- '*' — получить список дочерних элементов статьи — это аналог groovy.util.Node#children()
- author.'@id' — получить атрибут id элемента author . author.'@attributeName' обращается к значению атрибута по его имени (эквиваленты: author['@id'] и author.@id )
3.2. Добавление узла
Как и в предыдущем примере, давайте сначала прочитаем содержимое XML в переменную. Это позволит нам определить новый узел и добавить его в наш список статей, используя groovy.util.Node#append.
Давайте теперь реализуем тест, который доказывает нашу точку зрения:
Как видно из приведенного выше примера, процесс довольно прост.
Заметим также, что мы использовали groovy.util.NodeBuilder, который является хорошей альтернативой использованию конструктора узла для нашего определения узла .
3.3. Изменение узла
Мы также можем изменить значения узлов с помощью XmlParser . Для этого давайте еще раз проанализируем содержимое XML-файла. Далее мы можем отредактировать узел содержимого, изменив поле значения объекта Node .
Давайте помнить, что хотя XmlParser использует выражения GPath, мы всегда извлекаем экземпляр NodeList, поэтому для изменения первого (и единственного) элемента мы должны получить к нему доступ, используя его индекс.
Давайте проверим наши предположения, написав быстрый тест:
В приведенном выше примере мы также использовали Groovy Collections API для обхода NodeList .
3.4. Замена узла
Далее давайте посмотрим, как заменить весь узел, а не просто изменить одно из его значений.
Аналогично добавлению нового элемента, мы будем использовать NodeBuilder для определения узла , а затем заменим в нем один из существующих узлов с помощью groovy.util.Node#replaceNode :
3.5. Удаление узла
Удаление узла с помощью XmlParser довольно сложно. Хотя класс Node предоставляет метод remove(Node child) , в большинстве случаев мы не стали бы использовать его сам по себе.
Вместо этого мы покажем, как удалить узел, значение которого удовлетворяет заданному условию.
По умолчанию доступ к вложенным элементам с помощью цепочки ссылок Node.NodeList возвращает копию соответствующих дочерних узлов. Из-за этого мы не можем использовать метод java.util.NodeList#removeAll непосредственно в нашей коллекции статей .
Чтобы удалить узел по предикату, мы должны сначала найти все узлы, соответствующие нашему условию, а затем пройтись по ним и каждый раз вызывать метод java.util.Node#remove для родителя .
Давайте реализуем тест, удаляющий все статьи, id автора которых отличается от 3 :
Как мы видим, в результате нашей операции удаления мы получили XML-структуру только с одной статьей, а ее id равен 3 .
4. XmlSlurper
Groovy также предоставляет еще один класс, предназначенный для работы с XML. В этом разделе мы покажем, как читать XML-структуру и управлять ею с помощью XmlSlurper.
4.1. Чтение
Как и в наших предыдущих примерах, начнем с разбора структуры XML из файла:
Как мы видим, интерфейс идентичен интерфейсу XmlParser . Однако структура вывода использует groovy.util.slurpersupport.GPathResult , который является классом-оболочкой для Node . GPathResult предоставляет упрощенные определения таких методов, как equals() и toString() , обертывая Node#text(). В результате мы можем читать поля и параметры напрямую, используя только их имена.
4.2. Добавление узла
Добавление узла также очень похоже на использование XmlParser . Однако в этом случае groovy.util.slurpersupport. GPathResult#appendNode предоставляет метод, который принимает экземпляр java.lang.Object в качестве аргумента. В результате мы можем упростить новые определения узлов , следуя тому же соглашению, введенному Node Builder :
Если нам нужно изменить структуру нашего XML с помощью XmlSlurper, мы должны повторно инициализировать наш объект article, чтобы увидеть результаты. Мы можем добиться этого, используя комбинацию методов groovy.util.XmlSlurper #parseText и groovy.xmlXmlUtil#serialize .
4.3. Изменение узла
Как мы упоминали ранее, GPathResult представляет упрощенный подход к манипулированию данными. При этом, в отличие от XmlSlurper, мы можем изменять значения напрямую, используя имя узла или имя параметра:
Заметим, что когда мы изменяем только значения объекта XML, нам не нужно снова анализировать всю структуру.
4.4. Замена узла
Теперь приступим к замене всего узла. И снова на помощь приходит GPathResult . Мы можем легко заменить узел, используя groovy.util.slurpersupport.NodeChild#replaceNode , который расширяет GPathResult и следует тому же соглашению об использовании значений Object в качестве аргументов:
Как и в случае добавления узла, мы модифицируем структуру XML, поэтому нам нужно снова его разобрать.
4.5. Удаление узла
Чтобы удалить узел с помощью XmlSlurper, мы можем повторно использовать метод groovy.util.slurpersupport.NodeChild#replaceNode , просто указав пустое определение узла :
Опять же, изменение структуры XML требует повторной инициализации нашего объекта article .
5. XmlParser против XmlSlurper
Как мы показали в наших примерах, использование XmlParser и XmlSlurper очень похоже. Мы можем более или менее достичь одинаковых результатов с обоими. Однако некоторые различия между ними могут склонить чашу весов в сторону одного или другого.
Во-первых, XmlParser всегда анализирует весь документ в виде DOM-структуры. Благодаря этому мы можем одновременно читать и писать в него . Мы не можем сделать то же самое с XmlSlurper , так как он оценивает пути более лениво. В результате XmlParser может потреблять больше памяти. «
С другой стороны, XmlSlurper использует более простые определения, что упрощает работу с ним. Мы также должны помнить, что любые структурные изменения, внесенные в XML с помощью XmlSlurper , требуют повторной инициализации, что может привести к неприемлемому снижению производительности в случае внесения множества изменений друг за другом.
Решение о том, какой инструмент использовать, должно приниматься с осторожностью и полностью зависит от варианта использования.
6. Разметчик
Помимо чтения XML-дерева и управления им, Groovy также предоставляет инструменты для создания XML-документа с нуля. Давайте теперь создадим документ, состоящий из первых двух статей из нашего первого примера, используя groovy.xml.MarkupBuilder :
В приведенном выше примере мы видим, что MarkupBuilder использует тот же подход для определений узлов , который мы использовали ранее с NodeBuilder и GPathResult .
Чтобы сравнить выходные данные MarkupBuilder с ожидаемой структурой XML, мы использовали метод groovy.xml.XmlUtil#serialize .
7. Заключение
В этой статье мы рассмотрели несколько способов управления XML-структурами с помощью Groovy.
Мы рассмотрели примеры разбора, добавления, редактирования, замены и удаления узлов с помощью двух классов, предоставляемых Groovy: XmlParser и XmlSlurper . Мы также обсудили различия между ними и показали, как можно с нуля построить дерево XML с помощью MarkupBuilder .
Как всегда, полный код, использованный в этой статье, доступен на GitHub .