| = Metaprogramming |
| |
| The Groovy language supports two flavors of metaprogramming: runtime metaprogramming and compile-time metaprogramming. |
| The first one allows altering the class model and the behavior of a program at runtime, while the second only occurs |
| at compile-time. Both have pros and cons, that we will detail in this section. |
| |
| == Runtime metaprogramming (TBD) |
| |
| === GroovyObject interface (TBD) |
| ==== invokeMethod (TBD) |
| ==== get/setProperty (TBD) |
| ==== get/setMetaClass (TBD) |
| |
| === get/setAttribute (TBD) |
| |
| === methodMissing |
| |
| Groovy supports the concept of `methodMissing`. This method differs from `invokeMethod` in that it |
| is only invoked in the case of a failed method dispatch, when no method can be found for the given name and/or the |
| given arguments. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/MethodPropertyMissingTest.groovy[tags=method_missing_simple,indent=0] |
| ---- |
| |
| Typically when using `methodMissing` the code will react in some way that makes it possible for the next time the same |
| method is called, that it goes through the regular Groovy method dispatch logic. |
| |
| For example consider dynamic finders in GORM. These are implemented in terms of `methodMissing`. The code resembles |
| something like this: |
| |
| [source,groovy] |
| ---- |
| class GORM { |
| def dynamicMethods = [...] // an array of dynamic methods that use regex |
| def methodMissing(String name, args) { |
| def method = dynamicMethods.find { it.match(name) } |
| if(method) { |
| GORM.metaClass."$name" = { Object[] varArgs -> |
| method.invoke(delegate, name, varArgs) |
| } |
| return method.invoke(delegate,name, args) |
| } |
| else throw new MissingMethodException(name, delegate, args) |
| } |
| } |
| ---- |
| |
| Notice how, if we find a method to invoke then we dynamically register a new method on the fly using `ExpandoMetaClass`. |
| This is so that the next time the same method is called it is more efficient. This way `methodMissing` doesn't have |
| the overhead of `invokeMethod` _and_ is not expensive for the second call. |
| |
| === propertyMissing |
| |
| Groovy supports the concept of `propertyMissing` for intercepting otherwise failing property resolution attempts. In the |
| case of a getter method, `propertyMissing` takes a single String argument resembling the property name: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/MethodPropertyMissingTest.groovy[tags=property_missing_getter,indent=0] |
| ---- |
| |
| The `propertyMissing(String)` method is only called when no getter method for the given property can be found by the Groovy |
| runtime. |
| |
| For a setter methods a second `propertyMissing` definition can be added that takes an additional value argument: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/MethodPropertyMissingTest.groovy[tags=property_missing_getter_setter,indent=0] |
| ---- |
| |
| As with `methodMissing` it is best practice to dynamically register new properties at runtime to improve the overall lookup |
| performance. |
| |
| [NOTE] |
| `methodMissing` and `propertyMissing` that deal with static methods and properties can be added via |
| the <<core-metaprogramming.adoc#metaprogramming_emc,ExpandoMetaClass>>. |
| |
| === GroovyInterceptable (TBD) |
| |
| [[categories]] |
| === Categories |
| |
| There are situations where it is useful if a class _not_ under control had additional methods. In order to enable this |
| capability, Groovy implements a feature borrowed from Objective-C, called _Categories_. |
| |
| Categories are implemented with so-called _category classes_. A category class is special in that it needs to meet certain |
| pre-defined rules for defining extension methods. |
| |
| There are a few categories that are included in the system for adding functionality to classes that make them more |
| usable within the Groovy environment: |
| |
| * http://groovy.codehaus.org/api/groovy/time/TimeCategory.html[groovy.time.TimeCategory] |
| * http://groovy.codehaus.org/api/groovy/servlet/ServletCategory.html[groovy.servlet.ServletCategory] |
| * http://groovy.codehaus.org/api/groovy/xml/dom/DOMCategory.html[groovy.xml.dom.DOMCategory] |
| |
| Category classes aren't enabled by default. To use the methods defined in a category class it is necessary to apply |
| the scoped `use` method that is provided by the GDK and available from inside every Groovy object instance: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/CategoryTest.groovy[tags=time_category,indent=0] |
| ---- |
| <1> `TimeCategory` adds methods to `Integer` |
| <2> `TimeCategory` adds methods to `Date` |
| |
| The `use` method takes the category class as its first parameter and a closure code block as second parameter. Inside the |
| `Closure` access to the category methods is available. As can be seen in the example above even JDK classes |
| like `java.lang.Integer` or `java.util.Date` can be enriched with user-defined methods. |
| |
| A category needs not to be directly exposed to the user code, the following will also do: |
| |
| [source,groovy] |
| ---------------------------------------------------------------------------------------------------------------------- |
| class JPACategory{ |
| // Let's enhance JPA EntityManager without getting into the JSR committee |
| static void persistAll(EntityManager em , Object[] entities) { //add an interface to save all |
| entities?.each { em.persist(it) } |
| } |
| } |
| |
| def transactionContext = { |
| EntityManager em, Closure c -> |
| def tx = em.transaction |
| try { |
| tx.begin() |
| use(JPACategory) { |
| c() |
| } |
| tx.commit() |
| } catch (e) { |
| tx.rollback() |
| } finally { |
| //cleanup your resource here |
| } |
| } |
| |
| // user code, they always forget to close resource in exception, some even forget to commit, let's not rely on them. |
| EntityManager em; //probably injected |
| transactionContext (em) { |
| em.persistAll(obj1, obj2, obj3) |
| // let's do some logics here to make the example sensible |
| em.persistAll(obj2, obj4, obj6) |
| } |
| ---------------------------------------------------------------------------------------------------------------------- |
| |
| When we have a look at the `groovy.time.TimeCategory` class we see that the extension methods are all declared as `static` |
| methods. In fact, this is one of the requirements that must be met by category classes for its methods to be successfully added to |
| a class inside the `use` code block: |
| |
| [source,groovy] |
| ---------------------------------------------------------------------------------------------------------------------- |
| public class TimeCategory { |
| |
| public static Date plus(final Date date, final BaseDuration duration) { |
| return duration.plus(date); |
| } |
| |
| public static Date minus(final Date date, final BaseDuration duration) { |
| final Calendar cal = Calendar.getInstance(); |
| |
| cal.setTime(date); |
| cal.add(Calendar.YEAR, -duration.getYears()); |
| cal.add(Calendar.MONTH, -duration.getMonths()); |
| cal.add(Calendar.DAY_OF_YEAR, -duration.getDays()); |
| cal.add(Calendar.HOUR_OF_DAY, -duration.getHours()); |
| cal.add(Calendar.MINUTE, -duration.getMinutes()); |
| cal.add(Calendar.SECOND, -duration.getSeconds()); |
| cal.add(Calendar.MILLISECOND, -duration.getMillis()); |
| |
| return cal.getTime(); |
| } |
| |
| // ... |
| ---------------------------------------------------------------------------------------------------------------------- |
| |
| Another requirement is the first argument of the static method must define the type the method is attached to once being activated. The |
| other arguments are the normal arguments the method will take as parameters. |
| |
| Because of the parameter and static method convention, category method definitions may be a bit less intuitive than |
| normal method definitions. As an alternative Groovy comes with a `@Category` annotation that transforms annotated classes |
| into category classes at compile-time. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/CategoryTest.groovy[tags=time_category_anno,indent=0] |
| ---- |
| |
| Applying the `@Category` annotation has the advantage of being able to use instance methods without the target type as a |
| first parameter. The target type class is given as an argument to the annotation instead. |
| |
| [NOTE] |
| There is a distinct section on `@Category` in the <<core-metaprogramming.adoc#xform-Category,compile-time metaprogramming section>>. |
| |
| === Metaclasses (TBD) |
| ==== Custom metaclasses (TBD) |
| ===== Delegating metaclass (TBD) |
| ===== Magic package (TBD) |
| ==== Per instance metaclass (TBD) |
| |
| [[metaprogramming_emc]] |
| ==== ExpandoMetaClass |
| |
| Groovy comes with a special `MetaClass` the so-called `ExpandoMetaClass`. It is special in that it allows for dynamically |
| adding or changing methods, constructors, properties and even static methods by using a neat closure syntax. |
| |
| Applying those modifications can be especially useful in mocking or stubbing scenarios as shown in the <<core-testing-guide.adoc#testing_guide_emc,Testing Guide>>. |
| |
| Every `java.lang.Class` is supplied by Groovy with a special `metaClass` property that will give you a reference to an |
| `ExpandoMetaClass` instance. This instance can then be used to add methods or change the behaviour of already existing |
| ones. |
| |
| [NOTE] |
| By default `ExpandoMetaClass` doesn't do inheritance. To enable this you must call `ExpandoMetaClass#enableGlobally()` |
| before your app starts such as in the main method or servlet bootstrap. |
| |
| The following sections go into detail on how `ExpandoMetaClass` can be used in various scenarios. |
| |
| ===== Methods |
| |
| Once the `ExpandoMetaClass` is accessed by calling the `metaClass` property, methods can added by using either the left shift |
| `<<` or the `=` operator. |
| |
| [NOTE] |
| Note that the left shift operator is used to _append_ a new method. If the method already exists |
| an exception will be thrown. If you want to _replace_ a method you can use the `=` operator. |
| |
| The operators are applied on a non-existent property of `metaClass` passing an instance of a `Closure` code block. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_method,indent=0] |
| ---- |
| |
| The example above shows how a new method can be added to a class by accessing the `metaClass` property and using the `<<` or |
| `=` operator to assign a `Closure` code block. The `Closure` parameters are interpreted as method parameters. Parameterless methods |
| can be added by using the `{-> ...}` syntax. |
| |
| ===== Properties |
| |
| `ExpandoMetaClass` supports two mechanisms for adding or overriding properties. |
| |
| Firstly, it has support for declaring a _mutable property_ by simply assigning a value to a property of `metaClass`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_property,indent=0] |
| ---- |
| |
| Another way is to add getter and/or setter methods by using the standard mechanisms for adding instance methods. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_getter,indent=0] |
| ---- |
| |
| In the source code example above the property is dictated by the closure and is a read-only property. It is feasible to add |
| an equivalent setter method but then the property value needs to be stored for later usage. This could be done as |
| shown in the following example. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_getter_setter,indent=0] |
| ---- |
| |
| This is not the only technique however. For example in a servlet container one way might be to store the values in |
| the currently executing request as request attributes (as is done in some cases in Grails). |
| |
| ===== Constructors |
| |
| Constructors can be added by using a special `constructor` property. Either the `<<` or `=` operator can be used |
| to assign a `Closure` code block. The `Closure` arguments will become the constructor arguments when the code is |
| executed at runtime. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_constructors,indent=0] |
| ---- |
| |
| [NOTE] |
| Be careful when adding constructors however, as it is very easy to get into stack overflow troubles. |
| |
| ===== Static Methods |
| |
| Static methods can be added using the same technique as instance methods with the addition of the `static` qualifier |
| before the method name. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_static,indent=0] |
| ---- |
| |
| ===== Borrowing Methods |
| |
| With `ExpandoMetaClass` it is possible to use Groovy's method pointer syntax to borrow methods from other classes. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_method_pointer,indent=0] |
| ---- |
| |
| ===== Dynamic Method Names |
| |
| Since Groovy allows you to use Strings as property names this in turns allows you to dynamically create method and |
| property names at runtime. To create a method with a dynamic name simply use the language feature of reference property |
| names as strings. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_dynamic_method_names,indent=0] |
| ---- |
| |
| The same concept can be applied to static methods and properties. |
| |
| One application of dynamic method names can be found in the Grails web application framework. The concept of "dynamic |
| codecs" is implemented by using dynamic method names. |
| |
| [source,groovy] |
| .`HTMLCodec` Class |
| ------------------------------------------------------ |
| class HTMLCodec { |
| static encode = { theTarget -> |
| HtmlUtils.htmlEscape(theTarget.toString()) |
| } |
| |
| static decode = { theTarget -> |
| HtmlUtils.htmlUnescape(theTarget.toString()) |
| } |
| } |
| ------------------------------------------------------ |
| |
| The example above shows a codec implementation. Grails comes with various codec implementations each defined in a single class. |
| At runtime there will be multiple codec classes in the application classpath. At application startup the framework adds |
| a `encodeXXX` and a `decodeXXX` method to certain meta-classes where `XXX` is the first part of the codec class name (e.g. |
| `encodeHTML`). This mechanism is in the following shown in some Groovy pseudo-code: |
| |
| [source,groovy] |
| ------------------------------------------------------ |
| def codecs = classes.findAll { it.name.endsWith('Codec') } |
| |
| codecs.each { codec -> |
| Object.metaClass."encodeAs${codec.name-'Codec'}" = { codec.newInstance().encode(delegate) } |
| Object.metaClass."decodeFrom${codec.name-'Codec'}" = { codec.newInstance().decode(delegate) } |
| } |
| |
| |
| def html = '<html><body>hello</body></html>' |
| |
| assert '<html><body>hello</body></html>' == html.encodeAsHTML() |
| ------------------------------------------------------ |
| |
| ===== Runtime Discovery |
| |
| At runtime it is often useful to know what other methods or properties exist at the time the method is executed. `ExpandoMetaClass` |
| provides the following methods as of this writing: |
| |
| * `getMetaMethod` |
| * `hasMetaMethod` |
| * `getMetaProperty` |
| * `hasMetaProperty` |
| |
| Why can't you just use reflection? Well because Groovy is different, it has the methods that are "real" methods and |
| methods that are available only at runtime. These are sometimes (but not always) represented as MetaMethods. The |
| MetaMethods tell you what methods are available at runtime, thus your code can adapt. |
| |
| This is of particular use when overriding `invokeMethod`, `getProperty` and/or `setProperty`. |
| |
| ===== GroovyObject Methods |
| |
| Another feature of `ExpandoMetaClass` is that it allows to override the methods `invokeMethod`, `getProperty` and |
| `setProperty`, all of them can be found in the `groovy.lang.GroovyObject` class. |
| |
| The following example shows how to override `invokeMethod`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_invoke_method,indent=0] |
| ---- |
| |
| The first step in the `Closure` code is to lookup the `MetaMethod` for the given name and arguments. If the method |
| can be found everything is fine and it is delegated to. If not, a dummy value is returned. |
| |
| [NOTE] |
| A `MetaMethod` is a method that is known to exist on the `MetaClass` whether added at runtime or at compile-time. |
| |
| The same logic can be used to override `setProperty` or `getProperty`. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_get_property,indent=0] |
| ---- |
| |
| The important thing to note here is that instead of a `MetaMethod` a `MetaProperty` instance is looked up. If that exists |
| the `getProperty` method of the `MetaProperty` is called, passing the delegate. |
| |
| ===== Overriding Static invokeMethod |
| |
| `ExpandoMetaClass` even allows for overriding static method with a special `invokeMethod` syntax. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_invoke_method_static,indent=0] |
| ---- |
| |
| The logic that is used for overriding the static method is the same as we've seen before for overriding instance methods. The |
| only difference is the access to the `metaClass.static` property and the call to `getStaticMethodName` for retrieving |
| the static `MetaMethod` instance. |
| |
| ===== Extending Interfaces |
| |
| It is possible to add methods onto interfaces with `ExpandoMetaClass`. To do this however, it *must* be enabled |
| globally using the `ExpandoMetaClass.enableGlobally()` method before application start-up. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/metaprogramming/ExpandoMetaClassTest.groovy[tags=emc_interface,indent=0] |
| ---- |
| |
| === Extension modules |
| ==== Extending existing classes |
| |
| An extension module allows you to add new methods to existing classes, including classes which are precompiled, like |
| classes from the JDK. Those new methods, unlike those defined through a metaclass or using a category, are available |
| globally. For example, when you write: |
| |
| [source,groovy] |
| .Standard extension method |
| ------------------------------------ |
| def file = new File(...) |
| def contents = file.getText('utf-8') |
| ------------------------------------ |
| |
| The `getText` method doesn’t exist on the `File` class. However, Groovy knows it because it is defined in a special |
| class, `ResourceGroovyMethods`: |
| |
| [source,java] |
| .ResourceGroovyMethods.java |
| ---------------------------------------------------------------------------- |
| public static String getText(File file, String charset) throws IOException { |
| return IOGroovyMethods.getText(newReader(file, charset)); |
| } |
| ---------------------------------------------------------------------------- |
| |
| You may notice that the extension method is defined using a static method in a ``helper'' class (where various extension |
| methods are defined). The first argument of the `getText` method corresponds to the receiver, while additional parameters |
| correspond to the arguments of the extension method. So here, we are defining a method called _getText_ on |
| the `File` class (because the first argument is of type `File`), which takes a single argument as a parameter (the encoding `String`). |
| |
| The process of creating an extension module is simple: |
| |
| * write an extension class like above |
| * write a module descriptor file |
| |
| Then you have to make the extension module visible to Groovy, which is as simple as having the extension module classes |
| and descriptor available on classpath. This means that you have the choice: |
| |
| * either provide the classes and module descriptor directly on classpath |
| * or bundle your extension module into a jar for reusability |
| |
| An extension module may add two kind of methods to a class: |
| |
| * instance methods (to be called on an instance of a class) |
| * static methods (to be called on the class itself) |
| |
| ==== Instance methods |
| |
| To add an instance method to an existing class, you need to create an extension class. For example, let's say you |
| want to add a `maxRetries` method on `Integer` which accepts a closure and executes it at most _n_ times until no |
| exception is thrown. To do that, you only need to write the following: |
| |
| [source,groovy] |
| .MaxRetriesExtension.groovy |
| ---- |
| include::{projectdir}/src/spec/test/support/MaxRetriesExtension.groovy[tags=instance_extension,indent=0] |
| ---- |
| <1> The extension class |
| <2> First argument of the static method corresponds to the receiver of the message, that is to say the extended instance |
| |
| Then, after <<module-descriptor,having declared your extension class>>, you can call it this way: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ExtensionModuleSpecTest.groovy[tags=instance_extension_assert,indent=0] |
| ---- |
| |
| ==== Static methods |
| |
| It is also possible to add static methods to a class. In that case, the static method needs to be defined in its *own* |
| file: |
| |
| [source,groovy] |
| .StaticStringExtension.groovy |
| ---- |
| include::{projectdir}/src/spec/test/support/StaticStringExtension.groovy[tags=static_extension,indent=0] |
| ---- |
| <1> The static extension class |
| <2> First argument of the static method corresponds to the class being extended and is *unused* |
| |
| In which case you can call it directly on the `String` class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ExtensionModuleSpecTest.groovy[tags=static_extension_assert,indent=0] |
| ---- |
| |
| [[module-descriptor]] |
| ==== Module descriptor |
| |
| For Groovy to be able to load your extension methods, you must declare |
| your extension helper classes. You must create a file named |
| `org.codehaus.groovy.runtime.ExtensionModule` into the |
| `META-INF/services` directory: |
| |
| .org.codehaus.groovy.runtime.ExtensionModule |
| -------------------------------------------------------- |
| include::{projectdir}/src/spec/test-resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModuleSpec[tags=extension_descriptor,indent=0] |
| -------------------------------------------------------- |
| |
| The module descriptor requires 4 keys: |
| |
| * _moduleName_ : the name of your module |
| * _moduleVersion_: the version of your module. Note that version number |
| is only used to check that you don’t load the same module in two |
| different versions. |
| * _extensionClasses_: the list of extension helper classes for instance |
| methods. You can provide several classes, given that they are comma |
| separated. |
| * _staticExtensionClasses_: the list of extension helper classes for |
| static methods. You can provide several classes, given that they are |
| comma separated. |
| |
| Note that it is not required for a module to define both static helpers |
| and instance helpers, and that you may add several classes to a single |
| module. You can also extend different classes in a single module without |
| problem. It is even possible to use different classes in a single |
| extension class, but it is recommended to group extension methods into |
| classes by feature set. |
| |
| ==== Extension modules and classpath |
| |
| It's worth noting that you can't use an extension which is compiled at the same time as code using it. That means that |
| to use an extension, it *has* to be available on classpath, as compiled classes, before the code using it gets compiled. |
| Usually, this means that you can't have the _test_ classes in the same source unit as the extension class itself. Since |
| in general, test sources are separated from normal sources and executed in another step of the build, this is not an issue. |
| |
| ==== Compatibility with type checking |
| |
| Unlike categories, extension modules are compatible with type checking: if they are found on classpath, then the type |
| checker is aware of the extension methods and will not complain when you call them. It is also compatible with static |
| compilation. |
| |
| == Compile-time metaprogramming |
| |
| Compile-time metaprogramming in Groovy allows code generation at compile-time. Those transformations are altering the |
| Abstract Syntax Tree (AST) of a program, which is why in Groovy we call it AST transformations. AST transformations |
| allow you to hook into the compilation process, modify the AST and continue the compilation process to generate regular |
| bytecode. Compared to runtime metaprogramming, this has the advantage of making the changes visible in the class file |
| itself (that is to say, in the bytecode). Making it visible in the bytecode is important for example if you want the |
| transformations to be part of the class contract (implementing interfaces, extending abstract classes, ...) or even |
| if you need your class to be callable from Java (or other JVM languages). For example, an AST transformation can add |
| methods to a class. If you do it with runtime metaprogramming, the new method would only be visible from Groovy. If you |
| do the same using compile-time metaprogramming, the method would be visible from Java too. Last but not least, performance |
| would likely be better with compile-time metaprogramming (because no initialization phase is required). |
| |
| In this section, we will start with explaining the various compile-time transformations that are bundled with the Groovy |
| distribution. In a subsequent section, we will describe how you can <<developing-ast-xforms,implement your own AST transformations>> |
| and what are the disadvantages of this technique. |
| |
| === Available AST transformations |
| |
| Groovy comes with various AST transformations covering different needs: reducing boilerplate (code generation), implementing |
| design patterns (delegation, ...), logging, declarative concurrency, cloning, safer scripting, tweaking the compilation, |
| implementing Swing patterns, testing and eventually managing dependencies. If none of those AST transformations cover |
| your needs, you can still implement your own, as show in section <<developing-ast-xforms,Developing your own AST |
| transformations>>. |
| |
| AST transformations can be separated into two categories: |
| |
| * global AST transformations are applied transparently, globally, as soon as they are found on compile classpath |
| * local AST transformations are applied by annotating the source code with markers. Unlike global AST transformations, |
| local AST transformations may support parameters. |
| |
| Groovy doesn't ship with any global AST transformation, but you can find a list of local AST transformations |
| available for you to use in your code here: |
| |
| ==== Code generation transformations |
| |
| This category of transformation includes AST transformations which help removing boilerplate code. This is typically |
| code that you have to write but that does not carry any useful information. By autogenerating this boilerplate code, |
| the code you have to write is left clean and concise and the chance of introducing an error by getting such |
| boilerplate code incorrect is reduced. |
| |
| [[xform-ToString]] |
| ===== @groovy.transform.ToString |
| The `@ToString` AST transformation generates a human readable `toString` representation of the class. For example, |
| annotating the `Person` class like below will automatically generate the `toString` method for you: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_import,indent=0] |
| |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_simple,indent=0] |
| ---- |
| |
| With this definition, then the following assertion passes, meaning that a `toString` method taking the field valuess from |
| the class and printing them out has been generated: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_simple_assert,indent=0] |
| ---- |
| |
| The `@ToString` annotation accepts several parameters which are summarized in the following table: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |includeNames|false|Whether to include names of properties in generated toString.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_includeNames,indent=0] |
| ---- |
| |excludes|Empty list|List of properties to exclude from toString| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_excludes,indent=0] |
| ---- |
| |includes|Empty list|List of fields to include in toString| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_includes,indent=0] |
| ---- |
| |includeSuper|False|Should superclass be included in toString| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_includeSuper,indent=0] |
| ---- |
| |includeFields|False|Should fields be included in toString, in addition to properties| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_includeFields,indent=0] |
| ---- |
| |ignoreNulls|False|Should properties/fields with null value be displayed| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_ignoreNulls,indent=0] |
| ---- |
| |includePackage|False|Use fully qualified class name instead of simple name in toString| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_includePackage,indent=0] |
| ---- |
| |cache|False|Cache the toString string. Should only be set to true if the class is immutable.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_cache,indent=0] |
| ---- |
| |
| |======================================================================= |
| |
| [[xform-EqualsAndHashCode]] |
| ===== @groovy.transform.EqualsAndHashCode |
| |
| The `@EqualsAndHashCode` AST transformation aims at generating `equals` and `hashCode` methods for you. The generated |
| hashcode follows the best practices as described in _Effective Java_ by _Josh Bloch_: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=equalshashcode,indent=0] |
| ---- |
| |
| There are several options available to tweak the behavior of `@EqualsAndHashCode`: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |excludes|Empty list|List of properties to exclude from equals/hashCode| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=equalshashcode_example_excludes,indent=0] |
| ---- |
| |includes|Empty list|List of fields to include in equals/hashCode| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=equalshashcode_example_includes,indent=0] |
| ---- |
| |callSuper|False|Whether to include super in equals and hashCode calculations| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=equalshashcode_example_super,indent=0] |
| ---- |
| |includeFields|False|Should fields be included in equals/hashCode, in addition to properties| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_includeFields,indent=0] |
| ---- |
| |cache|False|Cache the hashCode computation. Should only be set to true if the class is immutable.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tostring_example_cache,indent=0] |
| ---- |
| |useCanEqual|True|Should equals call canEqual helper method.|See http://www.artima.com/lejava/articles/equality.html |
| |======================================================================= |
| |
| [[xform-TupleConstructor]] |
| ===== @groovy.transform.TupleConstructor |
| |
| The `@TupleConstructor` annotation aims at eliminating boilerplate code by generating constructors for you. A tuple |
| constructor is created for each property, with default values (using the Java default values). For example, the |
| following code will generate 3 constructors: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_simple,indent=0] |
| ---- |
| |
| The first constructor is a no-arg constructor which allows the traditional map-style construction. It is worth noting |
| that if the first property (or field) has type LinkedHashMap or if there is a single Map, AbstractMap or HashMap |
| property (or field), then the map-style mapping is not available. |
| |
| The other constructors are generated by taking the properties in the order they are defined. Groovy will generate as |
| many constructors as they are properties (or fields, depending on the options). |
| |
| The `@TupleConstructor` AST transformation accepts several configuration options: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |excludes|Empty list|List of properties to exclude from tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_excludes,indent=0] |
| ---- |
| |includes|Empty list|List of fields to include in tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_includes,indent=0] |
| ---- |
| |includeFields|False|Should fields be included in tuple constructor generation, in addition to properties| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_includeFields,indent=0] |
| ---- |
| |includeProperties|True|Should properties be included in tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_includeProperties,indent=0] |
| ---- |
| |includeSuperFields|False|Should fields from super classes be included in tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_includeSuperFields,indent=0] |
| ---- |
| |includeSuperProperties|True|Should properties from super classes be included in tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_includeSuperProperties,indent=0] |
| ---- |
| |callSuper|False|Should super properties be called within a call to the parent constructor rather than set as properties| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=tupleconstructor_example_callSuper,indent=0] |
| ---- |
| |force|False|By default, the transformation will do nothing if a constructor is already defined. Setting this property |
| to true, the constructor will be generated and it's your responsability to ensure that no duplicate constructor |
| is defined|See javadocs |
| |======================================================================= |
| |
| [[xform-Canonical]] |
| ===== @groovy.transform.Canonical |
| |
| The `@Canonical` AST transformation combines the effects of the <<xform-ToString,@ToString>>, |
| <<xform-EqualsAndHashCode,@EqualsAndHashCode>> and <<xform-TupleConstructor,@TupleConstructor>> |
| annotations: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=canonical_simple,indent=0] |
| ---- |
| |
| A similar immutable class can be generated using the <<xform-Immutable,@Immutable>> AST transformation instead. |
| The `@Canonical` AST transformation supports several configuration options: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |excludes|Empty list|List of properties to exclude from tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=canonical_example_excludes,indent=0] |
| ---- |
| |includes|Empty list|List of fields to include in tuple constructor generation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=canonical_example_includes,indent=0] |
| ---- |
| |======================================================================= |
| |
| [[xform-InheritConstructors]] |
| ===== @groovy.transform.InheritConstructors |
| |
| The `@InheritConstructor` AST transformation aims at generating constructors matching super constructors for you. This |
| is in particular useful when overridding exception classes: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=inheritconstructors_simple,indent=0] |
| ---- |
| |
| [[xform-Category]] |
| ===== @groovy.lang.Category |
| |
| The `@Category` AST transformation simplifies the creation of Groovy categories. Historically, a Groovy category was |
| written like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=oldstyle_category,indent=0] |
| ---- |
| |
| The `@Category` transformation lets you write the same using an instance-style class, rather than a static class style. |
| This removes the need for having the first argument of each method being the receiver. The category can be written like |
| this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=newstyle_category,indent=0] |
| ---- |
| |
| Note that the mixed in class can be referenced using `this` instead. It's also worth noting that using instance fields |
| in a category class is inherently unsafe: categories are not stateful (like traits). |
| |
| [[xform-IndexedProperty]] |
| ===== @groovy.transform.IndexedProperty |
| |
| The `@IndexedProperty` annotation aims at generating indexed getters/setters for properties of list/array types. |
| This is in particular useful if you want to use a Groovy class from Java. While Groovy supports GPath to access properties, |
| this is not available from Java. The `@IndexedProperty` annotation will generate indexed properties of the following |
| form: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=indexedproperty_simple,indent=0] |
| ---- |
| |
| [[xform-Lazy]] |
| ===== @groovy.lang.Lazy |
| |
| The `@Lazy` AST transformation implements lazy initialization of fields. For example, the following code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=lazy_simple,indent=0] |
| ---- |
| |
| will produce the following code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=lazy_simple_generated,indent=0] |
| ---- |
| |
| The default value which is used to initialize the field is the default constructor of the declaration type. It is possible |
| to define a default value by using a closure on the right hand side of the property assignment, as in the following |
| example: |
| |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=lazy_default,indent=0] |
| ---- |
| |
| In that case, the generated code looks like the following: |
| |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=lazy_default_generated,indent=0] |
| ---- |
| |
| If the field is declared volatile then initialization will be synchronized using the |
| http://en.wikipedia.org/wiki/Double-checked_locking[double-checked locking] pattern. |
| |
| Using the `soft=true` parameter, the helper field will use a `SoftReference` instead, providing a simple way to |
| implement caching. In that case, if the garbage collector decides to collect the reference, initialization will occur |
| the next time the field is accessed. |
| |
| [[xform-Newify]] |
| ===== @groovy.lang.Newify |
| |
| The `@Newify` AST transformation is used to bring alternative syntaxes to construct objects: |
| |
| * Using the `Python` style: |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=newify_python,indent=0] |
| ---- |
| * or using the `Ruby` style: |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=newify_ruby,indent=0] |
| ---- |
| The `Ruby` version can be disabled by setting the `auto` flag to `false`. |
| |
| [[xform-Sortable]] |
| ===== @groovy.lang.Sortable |
| |
| The `@Sortable` AST transformation is used to help write classes that are `Comparable` and easily sorted by |
| numerous properties. It is easy to use as shown in the following example where we annotate the `Person` class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_simple,indent=0] |
| ---- |
| |
| The generated class has the following properties: |
| |
| * it implements the `Comparable` interface |
| * it contains a `compareTo` method with an implementation based on the natural ordering of the `first`, `last` and `born` properties |
| * it has three methods returning comparators: `comparatorByFirst`, `comparatorByLast` and `comparatorByBorn`. |
| |
| The generated `compareTo` method will look like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_simple_generated_compareTo,indent=0] |
| ---- |
| As an example of the generated comparators, the `comparatorByFirst` comparator will have a `compare` method that looks like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_simple_generated_comparatorByFirst,indent=0] |
| ---- |
| |
| The `Person` class can be used wherever a `Comparable` is expected and the generated comparators |
| wherever a `Comparator` is expected as shown by these examples: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_simple_usage,indent=0] |
| ---- |
| |
| Normally, all properties are used in the generated `compareTo` method in the priority order in which they are defined. |
| You can include or exclude certain properties from the generated `compareTo` method by giving a list of property names |
| in the `includes` or `excludes` annotation attributes. If using `includes`, the order of the property names given will |
| determine the priority of properties when comparing. To illustrate, consider the following `Person` class definition: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_custom,indent=0] |
| ---- |
| |
| It will have two comparator methods `comparatorByFirst` and `comparatorByBorn` and the generated `compareTo` method will look like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_custom_generated_compareTo,indent=0] |
| ---- |
| |
| This `Person` class can be used as follows: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=sortable_custom_usage,indent=0] |
| ---- |
| |
| [[xform-Builder]] |
| ===== @groovy.transform.builder.Builder |
| |
| The `@Builder` AST transformation is used to help write classes that can be created using _fluent_ api calls. |
| The transform supports multiple building strategies to cover a range of cases and there are a number |
| of configuration options to customize the building process. If you're an AST hacker, you can also define your own |
| strategy class. The following table lists the available strategies that are bundled with Groovy and the |
| configuration options each strategy supports. |
| |
| |================================ |
| | Strategy | Description | builderClassName | builderMethodName |buildMethodName | prefix | includes/excludes |
| | `SimpleStrategy` | chained setters | n/a | n/a | n/a | yes, default "set" | yes |
| | `ExternalStrategy` | explicit builder class, class being built untouched | n/a | n/a | yes, default "build" | yes, default "" | yes |
| | `DefaultStrategy` | creates a nested helper class | yes, default __<TypeName>__Builder | yes, default "builder" | yes, default "build" | yes, default "" | yes |
| | `InitializerStrategy` | creates a nested helper class providing type-safe fluent creation | yes, default __<TypeName>__Initializer | yes, default "createInitializer" | yes, default "create" but usually only used internally | yes, default "" | yes |
| |================================ |
| |
| .SimpleStrategy |
| To use the `SimpleStrategy`, annotate your Groovy class using the `@Builder` annotation, and specify the strategy as shown in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_simple,indent=0] |
| ---- |
| |
| Then, just call the setters in a chained fashion as shown here: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_simple_usage,indent=0] |
| ---- |
| |
| For each property, a generated setter will be created which looks like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_simple_generated_setter,indent=0] |
| ---- |
| |
| You can specify a prefix as shown in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_simple_prefix,indent=0] |
| ---- |
| |
| And calling the chained setters would look like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_simple_prefix_usage,indent=0] |
| ---- |
| |
| You can use the `SimpleStrategy` in conjunction with `@Canonical`. If your `@Builder` annotation doesn't have |
| explicit `includes` or `excludes` annotation attributes but your `@Canonical` annotation does, the ones |
| from `@Canonical` will be re-used for `@Builder`. |
| |
| The annotation attributes `builderClassName`, `buildMethodName`, `builderMethodName` and `forClass` are not supported for this strategy. |
| |
| NOTE: Groovy already has built-in building mechanisms. Don't rush to using `@Builder` if the built-in mechanisms meet your needs. Some examples: |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_simple_alternatives,indent=0] |
| ---- |
| |
| .ExternalStrategy |
| To use the `ExternalStrategy`, create and annotate a Groovy builder class using the `@Builder` annotation, specify the |
| class the builder is for using `forClass` and indicate use of the `ExternalStrategy`. |
| Suppose you have the following class you would like a builder for: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_external_buildee,indent=0] |
| ---- |
| |
| you explicitly create and use your builder class as follows: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_external,indent=0] |
| ---- |
| |
| Note that the (normally empty) builder class you provide will be filled in with appropriate setters and a build method. |
| The generated build method will look something like: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_external_generated_build,indent=0] |
| ---- |
| |
| The class you are creating the builder for can be any Java or Groovy class following the normal JavaBean conventions, |
| e.g. a no-arg constructor and setters for the properties. Here is an example using a Java class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_external_java,indent=0] |
| ---- |
| |
| The generated builder can be customised using the `prefix`, `includes`, `excludes` and `buildMethodName` annotation attributes. |
| Here is an example illustrating various customisations: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_external_custom,indent=0] |
| ---- |
| |
| The `builderMethodName` and `builderClassName` annotation attributes for `@Builder` aren't applicable for this strategy. |
| |
| You can use the `ExternalStrategy` in conjunction with `@Canonical`. If your `@Builder` annotation doesn't have |
| explicit `includes` or `excludes` annotation attributes but the `@Canonical` annotation of the class you are creating |
| the builder for does, the ones from `@Canonical` will be re-used for `@Builder`. |
| |
| .DefaultStrategy |
| To use the `DefaultStrategy`, annotate your Groovy class using the `@Builder` annotation as shown in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_default,indent=0] |
| ---- |
| |
| If you want, you can customize various aspects of the building process |
| using the `builderClassName`, `buildMethodName`, `builderMethodName`, `prefix`, `includes` and `excludes` annotation attributes, |
| some of which are used in the example here: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_default_custom,indent=0] |
| ---- |
| |
| This strategy also supports annotating static methods and constructors. In this case, the static method or constructor |
| parameters become the properties to use for building purposes and in the case of static methods, the return type |
| of the method becomes the target class being built. If you have more than one `@Builder` annotation used within |
| a class (at either the class, method or constructor positions) then it is up to you to ensure that the generated |
| helper classes and factory methods have unique names (i.e. no more than one can use the default name values). |
| Here is an example highlighting method and constructor usage (and also illustrating the renaming required for unique names). |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_default_methods,indent=0] |
| ---- |
| |
| The `forClass` annotation attribute is not supported for this strategy. |
| |
| .InitializerStrategy |
| To use the `InitializerStrategy`, annotate your Groovy class using the `@Builder` annotation, and specify the strategy as shown in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_initializer,indent=0] |
| ---- |
| |
| Your class will be locked down to have a single public constructor taking a "fully set" initializer. |
| It will also have a factory method to create the initializer. These are used as follows: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_initializer_usage,indent=0] |
| ---- |
| |
| Any attempt to use the initializer which doesn't involve setting all the properties (though order is not important) will result in |
| a compilation error. If you don't need this level of strictness, you don't need to use `@CompileStatic`. |
| |
| You can use the `InitializerStrategy` in conjunction with `@Canonical` and `@Immutable`. If your `@Builder` annotation |
| doesn't have explicit `includes` or `excludes` annotation attributes but your `@Canonical` annotation does, the ones |
| from `@Canonical` will be re-used for `@Builder`. Here is an example using `@Builder` with `@Immutable`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=builder_initializer_immutable,indent=0] |
| ---- |
| |
| The annotation attribute `forClass` is not supported for this strategy. |
| |
| ==== Class design annotations |
| |
| This category of annotations are aimed at simplifying the implementation of well-known design patterns (delegation, |
| singleton, ...) by using a declarative style. |
| |
| [[xform-Delegate]] |
| ===== @groovy.lang.Delegate |
| |
| The `@Delegate` AST transformation aims at implementing the delegation design pattern. In the following class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegating_class,indent=0] |
| ---- |
| |
| The `when` field is annotated with `@Delegate`, meaning that the `Event` class will delegate calls to `Date` methods |
| to the `when` field. In this case, the generated code looks like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegating_class_generated,indent=0] |
| ---- |
| |
| Then you can call the `before` method, for example, directly on the `Event` class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegation_assert,indent=0] |
| ---- |
| |
| The behavior of the `@Delegate` AST transformation can be changed using the following parameters: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |interfaces|True|Should the interfaces implemented by the field be implemented by the class too| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_interfaces,indent=0] |
| ---- |
| |deprecated|false|If true, also delegates methods annotated with @Deprecated| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_deprecated_header,indent=0] |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_deprecated_footer,indent=0] |
| ---- |
| |methodAnnotations|False|Whether to carry over annotations from the methods of the delegate to your delegating method.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_annotations,indent=0] |
| ---- |
| |parameterAnnotations|False|Whether to carry over annotations from the method parameters of the delegate to your delegating method.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_parameter_annotations,indent=0] |
| ---- |
| |excludes|Empty array|A list of methods to be excluded from delegation. For more fine-grained control, see also `excludeTypes`.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_excludes_header,indent=0] |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_excludes_footer,indent=0] |
| ---- |
| |includes|Empty array|A list of methods to be included in delegation. For more fine-grained control, see also `includeTypes`.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_includes_header,indent=0] |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_includes_footer,indent=0] |
| ---- |
| |excludeTypes|Empty array|A list of interfaces containing method signatures to be excluded from delegation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_excludeTypes,indent=0] |
| ---- |
| |includeTypes|Empty array|A list of interfaces containing method signatures to be included in delegation| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_includeTypes_header,indent=0] |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=delegate_example_includeTypes_footer,indent=0] |
| ---- |
| |======================================================================= |
| |
| [[xform-Immutable]] |
| ===== @groovy.transform.Immutable |
| |
| The `@Immutable` AST transformation simplifies the creation of immutable classes, that is to say classes for which |
| members are deemed immutable. For that, all you have to do is annotating the class like in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_simple,indent=0] |
| ---- |
| |
| Immutable classes generated with `@Immutable` are automatically made final. For a class to be immutable, you have to |
| make sure that properties are of an immutable type (primitive or boxed types), of a known-immutable type or another |
| class annotated with `@Immutable`. The effect of applying `@Immutable` to a class are pretty similar to those of |
| applying the <<xform-Canonical,@Canonical>> AST transformation, but with an immutable class: automatic generation of |
| `toString`, `equals` and `hashCode` methods for example, but trying to modify a property would throw a `ReadOnlyPropertyException` |
| in that case. |
| |
| Since `@Immutable` relies on a predefined list of known immutable classes (like `java.net.URI` or `java.lang.String` |
| and fails if you use a type which is not in that list, you are allowed to instruct the transformation that some types |
| are deemed immutable thanks to the following parameters: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |knownImmutableClasses|Empty list|A list of classes which are deemed immutable.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_example_knownimmutableclasses,indent=0] |
| ---- |
| |knownImmutables|Empty list|A list of property names which are deemed immutable.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_example_knownimmutables,indent=0] |
| ---- |
| ---- |
| |copyWith|false|A boolean whether to generate a `copyWith( Map )` method.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=immutable_example_copyWith,indent=0] |
| ---- |
| |======================================================================= |
| |
| [[xform-Memoized]] |
| ===== @groovy.transform.Memoized |
| |
| The `@Memoized` AST transformations simplifies the implementation of caching by allowing the result of method calls |
| to be cached just by annotating the method with `@Memoized`. Let's imagine the following method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=memoized_long_computation,indent=0] |
| ---- |
| |
| This emulates a long computation, based on the actual parameters of the method. Without `@Memoized`, each method call |
| would take several seconds plus it would return a random result: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=memoized_long_computation_asserts,indent=0] |
| ---- |
| |
| Adding `@Memoized` changes the semantics of the method by adding caching, based on the parameters: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=memoized_long_computation_cached,indent=0] |
| ---- |
| |
| The size of the cache can be configured using two optional parameters: |
| |
| * _protectedCacheSize_: the number of results which are guaranteed not to be cleared after garbage collection |
| * _maxCacheSize_: the maximum number of results that can be kept in memory |
| |
| By default, the size of the cache is unlimited and no cache result is protected from garbage collection. Setting a |
| _protectedCacheSize>0_ would create an unlimited cache with some results protected. Setting _maxCacheSize>0_ would |
| create a limited cache but without any protection from garbage protection. Setting both would create a limited, |
| protected cache. |
| |
| [[xform-Singleton]] |
| ===== @groovy.lang.Singleton |
| |
| The `@Singleton` annotation can be used to implement the singleton design pattern on a class. The singleton instance |
| is defined eagerly by default, using class initialization, or lazily, in which case the field is initialized using |
| double checked locking. |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=singleton_simple,indent=0] |
| ---- |
| |
| By default, the singleton is created eagerly when the class is initialized and available through the `instance` property. |
| It is possible to change the name of the singleton using the `property` parameter: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=singleton_example_property,indent=0] |
| ---- |
| |
| And it is also possible to make initialization lazy using the `lazy` parameter: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/ClassDesignASTTransformsTest.groovy[tags=singleton_example_lazy,indent=0] |
| ---- |
| |
| In this example, we also set the `strict` parameter to false, which allows us to define our own constructor. |
| |
| [[xform-Mixin]] |
| ===== @groovy.transform.Mixin |
| |
| Deprecated. Consider using traits instead. |
| |
| ==== Logging improvements |
| |
| Groovy provides AST transformation that helps integrating with the most widely used logging frameworks. It's worth noting |
| that annotating a class with one of those annotations doesn't prevent you from adding the appropriate logging framework |
| on classpath. |
| |
| All transformations work in a similar way: |
| |
| * add static final `log` field corresponding to the logger |
| * wrap all calls to `log.level()` into the appropriate `log.isLevelEnabled` guard, depending on the underlying framework |
| |
| Those transformations support two parameters: |
| |
| * `value` (default `log`) corresponds to the name of the logger field |
| * `category` (defaults to the class name) is the name of the logger category |
| |
| [[xform-Log]] |
| ===== @groovy.util.logging.Log |
| |
| The first logging AST transformation available is the `@Log` annotation which relies on the JDK logging framework. Writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=log_spec,indent=0] |
| ---- |
| |
| is equivalent to writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=log_equiv,indent=0] |
| ---- |
| |
| [[xform-Commons]] |
| ===== @groovy.util.logging.Commons |
| |
| Groovy supports the http://commons.apache.org/proper/commons-logging/[Apache Commons Logging] framework using to the |
| `@Commons` annotation. Writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=commons_spec,indent=0] |
| ---- |
| |
| is equivalent to writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=commons_equiv,indent=0] |
| ---- |
| |
| [[xform-Log4j]] |
| ===== @groovy.util.logging.Log4j |
| |
| Groovy supports the http://logging.apache.org/log4j/1.2/[Apache Log4j 1.x] framework using to the |
| `@Log4j` annotation. Writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=log4j_spec,indent=0] |
| ---- |
| |
| is equivalent to writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=log4j_equiv,indent=0] |
| ---- |
| |
| [[xform-Log4j2]] |
| ===== @groovy.util.logging.Log4j2 |
| |
| Groovy supports the http://logging.apache.org/log4j/2.x/[Apache Log4j 2.x] framework using to the |
| `@Log4j2` annotation. Writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=log4j2_spec,indent=0] |
| ---- |
| |
| is equivalent to writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=log4j2_equiv,indent=0] |
| ---- |
| |
| [[xform-Slf4j]] |
| ===== @groovy.util.logging.Slf4j |
| |
| Groovy supports the http://www.slf4j.org/[Simple Logging Facade for Java (SLF4J)] framework using to the |
| `@Slf4j` annotation. Writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=slf4j_spec,indent=0] |
| ---- |
| |
| is equivalent to writing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/LogImprovementsASTTransformsTest.groovy[tags=slf4j_equiv,indent=0] |
| ---- |
| |
| ==== Declarative concurrency |
| |
| The Groovy language provides a set of annotations aimed at simplifying common concurrency patterns in a declarative |
| approach. |
| |
| [[xform-Synchronized]] |
| ===== @groovy.transform.Synchronized |
| |
| The `@Synchronized` AST transformations works in a similar way to the `synchronized` keyword but locks on different |
| objects for safer concurrency. It can be applied on any method or static method: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DeclarativeConcurrencyASTTransformsTest.groovy[tags=example_synchronized,indent=0] |
| ---- |
| |
| Writing this is equivalent to creating a lock object and wrapping the whole method into a synchronized block: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DeclarativeConcurrencyASTTransformsTest.groovy[tags=example_synchronized_equiv,indent=0] |
| ---- |
| |
| By default, `@Synchronized` creates a field named `$lock` (or `$LOCK` for a static method) but you can make it use any |
| field you want by specifying the value attribute, like in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DeclarativeConcurrencyASTTransformsTest.groovy[tags=example_synchronized_customlock,indent=0] |
| ---- |
| |
| [[xform-WithReadLock]] |
| ===== @groovy.transform.WithReadLock and @groovy.transform.WithWriteLock |
| |
| The `@WithReadLock` AST transformation works in conjunction with the `@WithWriteLock` transformation |
| to provide read/write synchronization using the `ReentrantReadWriteLock` facility that the JDK provides. The annotation |
| can be added to a method or a static method. It will transparently create a `$reentrantLock` final field (or |
| `$REENTRANTLOCK` for a static method) and proper synchronization code will be added. For example, the following code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DeclarativeConcurrencyASTTransformsTest.groovy[tags=example_rwlock,indent=0] |
| ---- |
| |
| is equivalent to this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DeclarativeConcurrencyASTTransformsTest.groovy[tags=example_rwlock_equiv,indent=0] |
| ---- |
| |
| Both `@WithReadLock` and `@WithWriteLock` support specifying an alternative lock object. In that case, the referenced |
| field must be declared by the user, like in the following alternative: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DeclarativeConcurrencyASTTransformsTest.groovy[tags=example_rwlock_alter,indent=0] |
| ---- |
| |
| For details |
| |
| * See Javadoc for gapi::groovy.transform.WithReadLock |
| * See Javadoc for gapi::groovy.transform.WithWriteLock |
| |
| ==== Easier cloning and externalizing |
| |
| Groovy provides two annotations aimed at facilitating the implementation of `Clonable` and `Externalizable` interfaces, |
| respectively named `@AutoClone` and `@AutoExternalize`. |
| |
| [[xform-AutoClone]] |
| ===== @groovy.transform.AutoClone |
| |
| The `@AutoClone` annotation is aimed at implementing the `@java.lang.Cloneable` interface using various strategies, thanks to the `style` parameter: |
| |
| * the default `AutoCloneStyle.CLONE` strategy calls `super.clone()` first then `clone()` on each cloneable property |
| * the `AutoCloneStyle.SIMPLE` strategy uses a regular constructor call and copies properties from the source to the clone |
| * the `AutoCloneStyle.COPY_CONSTRUCTOR` strategy creates and uses a copy constructor |
| * the `AutoCloneStyle.SERIALIZATION` strategy uses serialization (or externalization) to clone the object |
| |
| Each of those strategies have pros and cons which are discussed in the Javadoc for gapi::groovy.transform.AutoClone and gapi::groovy.transform.AutoCloneStyle . |
| |
| For example, the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoclone,indent=0] |
| ---- |
| |
| is equivalent to this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoclone_equiv,indent=0] |
| ---- |
| |
| Note that the String properties aren't explicitly handled because Strings are immutable and the `clone()` method from `Object` will copy the String references. The same would apply to primitive fields and most of the concrete subclasses of `java.lang.Number`. |
| |
| In addition to cloning styles, `@AutoClone` supports multiple options: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |excludes|Empty list|A list of property or field names that need to be excluded from cloning. A string consisting of a comma-separated field/property names is also allowed. |
| See gapi::groovy.transform.AutoClone#excludes for details| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoclone_excludes,indent=0] |
| ---- |
| |includeFields|false|By default, only properties are cloned. Setting this flag to true will also clone fields.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoclone_includeFields,indent=0] |
| ---- |
| |======================================================================= |
| |
| [[xform-AutoExternalize]] |
| ===== @groovy.transform.AutoExternalize |
| |
| The `@AutoExternalize` AST transformation will assist in the creation of `java.io.Externalizable` classes. It will |
| automatically add the interface to the class and generate the `writeExternal` and `readExternal` methods. For example, this |
| code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoext,indent=0] |
| ---- |
| |
| will be converted into: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoext_equiv,indent=0] |
| ---- |
| |
| The `@AutoExternalize` annotation supports two parameters which will let you slightly customize its behavior: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |excludes|Empty list|A list of property or field names that need to be excluded from externalizing. A string consisting of a comma-separated field/property names is also allowed. |
| See gapi::groovy.transform.AutoExternalize#excludes for details| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoext_excludes,indent=0] |
| ---- |
| |includeFields|false|By default, only properties are externalized. Setting this flag to true will also clone fields.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CloningASTTransformsTest.groovy[tags=example_autoext_includeFields,indent=0] |
| ---- |
| |======================================================================= |
| |
| |
| ==== Safer scripting |
| |
| The Groovy language makes it easy to execute user scripts at runtime (for example using gapi::groovy.lang.GroovyShell), |
| but how do you make sure that a script won't eat all CPU (infinite loops) or that concurrent scripts won't slowly consume |
| all available threads of a thread pool? Groovy provides several annotations which are aimed towards safer scripting, |
| generating code which will for example allow you to interrupt execution automatically. |
| |
| [[xform-ThreadInterrupt]] |
| ===== @groovy.transform.ThreadInterrupt |
| |
| One complicated situation in the JVM world is when a thread can't be stopped. The `Thread#stop` method exists but is |
| deprecated (and isn't reliable) so your only chance relies in `Thread#interrupt`. Calling the latter will set the |
| `interrupt` flag on the thread, but it will *not* stop the execution of the thread. This is problematic because it's the |
| responsibility of the code executing in the thread to check the interrupt flag and properly exit. This makes sense when |
| you, as a developer, know that the code you are executing is meant to be run in an independent thread, but in general, |
| you don't know it. It's even worse with user scripts, who might not even know what a thread is (think of DSLs). |
| |
| `@ThreadInterrupt` simplifies this by adding thread interruption checks at critical places in the code: |
| |
| * loops (for, while) |
| * first instruction of a method |
| * first instruction of a closure body |
| |
| Let's imagine the following user script: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=threadinterrupt_infiniteloop,indent=0] |
| ---- |
| |
| This is an obvious infinite loop. If this code executes in its own thread, interrupting wouldn't help: if you `join` on |
| the thread, then the calling code would be able to continue, but the thread would still be alive, running in background |
| without any ability for you to stop it, slowly causing thread starvation. |
| |
| One possibility to work around this is to set up your shell this way: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=threadinterrupt_shell_setup,indent=0] |
| ---- |
| |
| The shell is then configured to automatically apply the `@ThreadInterrupt` AST transformations on all scripts. This allows |
| you to execute user scripts this way: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=threadinterrupt_control,indent=0] |
| ---- |
| |
| The transformation automatically modified user code like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=threadinterrupt_infiniteloop_equiv,indent=0] |
| ---- |
| |
| The check which is introduced inside the loop guarantees that if the `interrupt` flag is set on the current thread, an |
| exception will be thrown, interrupting the execution of the thread. |
| |
| `@ThreadInterrupt` supports multiple options that will let you further customize the behavior of the transformation: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |thrown|`java.lang.InterruptedException`|Specifies the type of exception which is thrown if the thread is interrupted.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=threadinterrupt_thrown,indent=0] |
| ---- |
| |checkOnMethodStart|true|Should an interruption check be inserted at the beginning of each method body. See gapi::groovy.transform.ThreadInterrupt for details.| |
| [source,groovy] |
| ---- |
| @ThreadInterrupt(checkOnMethodStart=false) |
| ---- |
| |applyToAllClasses|true|Should the transformation be applied on all classes of the same source unit (in the same source file). See gapi::groovy.transform.ThreadInterrupt for details.| |
| [source,groovy] |
| ---- |
| @ThreadInterrupt(applyToAllClasses=false) |
| class A { ... } // interrupt checks added |
| class B { ... } // no interrupt checks |
| ---- |
| |applyToAllMembers|true|Should the transformation be applied on all members of class. See gapi::groovy.transform.ThreadInterrupt for details.| |
| [source,groovy] |
| ---- |
| class A { |
| @ThreadInterrupt(applyToAllMembers=false) |
| void method1() { ... } // interrupt checked added |
| void method2() { ... } // no interrupt checks |
| } |
| ---- |
| |======================================================================= |
| |
| [[xform-TimedInterrupt]] |
| ===== @groovy.transform.TimedInterrupt |
| |
| The `@TimedInterrupt` AST transformation tries to solve a slightly different problem from <<xform-ThreadInterrupt>>: instead of checking the `interrupt` flag of the thread, it will automatically |
| throw an exception if the thread has been running for too long. |
| |
| NOTE: This annotation does *not* spawn a monitoring thread. Instead, it works in a similar manner as `@ThreadInterrupt` by placing checks at appropriate places in the code. This means that if you |
| have a thread blocked by I/O, it will *not* be interrupted. |
| |
| Imagine the following user code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=timedinterrupt_fib,indent=0] |
| ---- |
| |
| The implementation of the famous Fibonacci number computation here is far from optimized. If it is called with a high `n` value, it can take minutes to answer. With `@TimedInterrupt`, you can |
| choose how long a script is allowed to run. The following setup code will allow the user script to run for 1 second at max: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=timedinterrupt_shell_setup,indent=0] |
| ---- |
| |
| This code is equivalent to annotating a class with `@TimedInterrupt` like this: |
| |
| [source,groovy] |
| ---- |
| @TimedInterrup(value=1, unit=TimeUnit.SECONDS) |
| class MyClass { |
| def fib(int n) { |
| n<2?n:fib(n-1)+fib(n-2) |
| } |
| } |
| ---- |
| |
| `@TimedInterrupt` supports multiple options that will let you further customize the behavior of the transformation: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |value|Long.MAX_VALUE|Used in combination with `unit` to specify after how long execution times out.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=timedinterrupt_duration,indent=0] |
| ---- |
| |unit|TimeUnit.SECONDS|Used in combination with `value` to specify after how long execution times out.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=timedinterrupt_duration,indent=0] |
| ---- |
| |thrown|`java.util.concurrent.TimeoutException`|Specifies the type of exception which is thrown if timeout is reached.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=timedinterrupt_thrown,indent=0] |
| ---- |
| |checkOnMethodStart|true|Should an interruption check be inserted at the beginning of each method body. See gapi::groovy.transform.TimedInterrupt for details.| |
| [source,groovy] |
| ---- |
| @TimedInterrupt(checkOnMethodStart=false) |
| ---- |
| |applyToAllClasses|true|Should the transformation be applied on all classes of the same source unit (in the same source file). See gapi::groovy.transform.TimedInterrupt for details.| |
| [source,groovy] |
| ---- |
| @TimedInterrupt(applyToAllClasses=false) |
| class A { ... } // interrupt checks added |
| class B { ... } // no interrupt checks |
| ---- |
| |applyToAllMembers|true|Should the transformation be applied on all members of class. See gapi::groovy.transform.TimedInterrupt for details.| |
| [source,groovy] |
| ---- |
| class A { |
| @TimedInterrupt(applyToAllMembers=false) |
| void method1() { ... } // interrupt checked added |
| void method2() { ... } // no interrupt checks |
| } |
| ---- |
| |======================================================================= |
| |
| WARNING: `@TimedInterrupt` is currently not compatible with static methods! |
| |
| [[xform-ConditionalInterrupt]] |
| ===== @groovy.transform.ConditionalInterrupt |
| |
| The last annotation for safer scripting is the base annotation when you want to interrupt a script using a custom strategy. In particular, this is the annotation of choice if you |
| want to use resource management (limit the number of calls to an API, ...). In the following example, user code is using an infinite loop, but `@ConditionalInterrupt` will allow us |
| to check a quota manager and interrupt automatically the script: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=conditionalinterrupt,indent=0] |
| ---- |
| |
| The quota checking is very basic here, but it can be any code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=conditionalinterrupt_quotaclass,indent=0] |
| ---- |
| |
| We can make sure `@ConditionalInterrupt` works properly using this test code: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=conditionalinterrupt_assert,indent=0] |
| ---- |
| |
| Of course, in practice, it is unlikely that `@ConditionalInterrupt` will be itself added by hand on user code. It can be injected in a similar manner as the example shown in the |
| <<xform-ThreadInterrupt,ThreadInterrupt>> section, using the gapi::org.codehaus.groovy.control.customizers.ASTTransformationCustomizer : |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=conditionalinterrupt_injected,indent=0] |
| ---- |
| |
| `@ConditionalInterrupt` supports multiple options that will let you further customize the behavior of the transformation: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |value||The closure which will be called to check if execution is allowed. If the closure returns false, execution is allowed. If it returns true, then an exception will be thrown.| |
| [source,groovy] |
| ---- |
| @ConditionalInterrupt({ ... }) |
| ---- |
| |thrown|`java.lang.InterruptedException`|Specifies the type of exception which is thrown if execution should be aborted.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SaferScriptingASTTransformsTest.groovy[tags=conditionalinterrupt_thrown,indent=0] |
| ---- |
| |checkOnMethodStart|true|Should an interruption check be inserted at the beginning of each method body. See gapi::groovy.transform.ConditionalInterrupt for details.| |
| [source,groovy] |
| ---- |
| @ConditionalInterrupt(checkOnMethodStart=false) |
| ---- |
| |applyToAllClasses|true|Should the transformation be applied on all classes of the same source unit (in the same source file). See gapi::groovy.transform.ConditionalInterrupt for details.| |
| [source,groovy] |
| ---- |
| @ConditionalInterrupt(applyToAllClasses=false) |
| class A { ... } // interrupt checks added |
| class B { ... } // no interrupt checks |
| ---- |
| |applyToAllMembers|true|Should the transformation be applied on all members of class. See gapi::groovy.transform.ConditionalInterrupt for details.| |
| [source,groovy] |
| ---- |
| class A { |
| @ConditionalInterrupt(applyToAllMembers=false) |
| void method1() { ... } // interrupt checked added |
| void method2() { ... } // no interrupt checks |
| } |
| ---- |
| |======================================================================= |
| |
| ==== Compiler directives |
| |
| This category of AST transformations groups annotations which have a direct impact on the semantics of the code, rather |
| than focusing on code generation. With that regards, they can be seen as compiler directives that either change the |
| behavior of a program at compile time or runtime. |
| |
| [[xform-Field]] |
| ===== @groovy.transform.Field |
| |
| The `@Field` annotation only makes sense in the context of a script and aims at solving a common scoping error with |
| scripts. The following example will for example fail at runtime: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CompilerDirectivesASTTransformsTest.groovy[tags=field_missing_property,indent=0] |
| ---- |
| |
| The error that is thrown may be difficult to interpret: +groovy.lang.MissingPropertyException: No such property: x+. The reason is that scripts are compiled |
| to classes and the script body is itself compiled as a single _run()_ method. Methods which are defined in the scripts are independent, so the code above is |
| equivalent to this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CompilerDirectivesASTTransformsTest.groovy[tags=field_missing_property_equiv,indent=0] |
| ---- |
| |
| So `def x` is effectiveley interpreted as a local variable, outside of the scope of the `line` method. The `@Field` AST transformation aims at fixing this |
| by changing the scope of the variable to a field of the enclosing script: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CompilerDirectivesASTTransformsTest.groovy[tags=field_fixed,indent=0] |
| ---- |
| |
| The resulting, equivalent, code is now: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CompilerDirectivesASTTransformsTest.groovy[tags=field_fixed_equiv,indent=0] |
| ---- |
| |
| [[xform-PackageScope]] |
| ===== @groovy.transform.PackageScope |
| |
| By default, Groovy visibility rules imply that if you create a field without specifying a modifier, then the field is interpreted as a property: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CompilerDirectivesASTTransformsTest.groovy[tags=packagescope_property,indent=0] |
| ---- |
| |
| Should you want to create a package private field instead of a property (private field+getter/setter), then annotate your field with `@PackageScope`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/CompilerDirectivesASTTransformsTest.groovy[tags=packagescope_property_javalike,indent=0] |
| ---- |
| |
| [[xform-AnnotationCollector]] |
| ===== @groovy.transform.AnnotationCollector |
| |
| `@AnnotationCollector` allows the creation of meta-annotation, which are described in a <<meta-annotations,dedicated section>>. |
| |
| [[xform-TypeChecked]] |
| ===== @groovy.transform.TypeChecked |
| |
| `@TypeChecked` activates compile-time type checking on your Groovy code. See <<section-typechecked,section on type checking>> for details. |
| |
| [[xform-CompileStatic]] |
| ===== @groovy.transform.CompileStatic |
| |
| `@CompileStatic` activates static compilation on your Groovy code. See <<section-typechecked,section on type checking>> for details. |
| |
| [[xform-CompileDynamic]] |
| ===== @groovy.transform.CompileDynamic |
| |
| `@CompileDynamic` disables static compilation on parts of your Groovy code. See <<section-typechecked,section on type checking>> for details. |
| |
| [[xform-DelegatesTo]] |
| ===== @groovy.lang.DelegatesTo |
| |
| `@DelegatesTo` is not, technically speaking, an AST transformation. It is aimed at documenting code and helping the compiler in case you are |
| using <<xform-TypeChecked,type checking>> or <<xform-CompileStatic, static compilation>>. The annotation is described thoroughly in the |
| <<section-delegatesto,DSL section>> of this guide. |
| |
| ==== Swing patterns |
| |
| [[xform-Bindable]] |
| ===== @groovy.beans.Bindable |
| |
| `@Bindable` is an AST transformation that transforms a regular property into a bound property (according to the http://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/[JavaBeans specification]). |
| The `@Bindable` annotation can be placed on a property or a class. To convert all properties of a class into bound properties, on can annotate the class like in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=bindable_on_class,indent=0] |
| ---- |
| |
| This is equivalent to writing this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=bindable_on_class_equiv,indent=0] |
| ---- |
| |
| `@Bindable` therefore removes a lot of boilerplate from your class, dramatically increasing readability. If the annotation is put on a single property, only that property is bound: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=bindable_single_property,indent=0] |
| ---- |
| |
| [[xform-ListenerList]] |
| ===== @groovy.beans.ListenerList |
| |
| The `@ListenerList` AST transformation generates code for adding, removing and getting the list of listeners to a class, just by annotating a collection property: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=listenerlist_simple,indent=0] |
| ---- |
| |
| The transform will generate the appropriate add/remove methods based on the generic type of the list. In addition, it will also create `fireXXX` methods based on the public methods declared on the class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=listenerlist_simple_equiv,indent=0] |
| ---- |
| |
| `@Bindable` supports multiple options that will let you further customize the behavior of the transformation: |
| |
| [cols="1,1,2,3a",options="header"] |
| |======================================================================= |
| |Attribute|Default value|Description|Example |
| |name|Generic type name|By default, the suffix which will be appended to add/remove/... methods is the simple class name of the generic type of the list.| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=listenerlist_name,indent=0] |
| ---- |
| |synchronize|false|If set to true, generated methods will be synchronized| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=listenerlist_synchronized,indent=0] |
| ---- |
| |======================================================================= |
| |
| [[xform-Vetoable]] |
| ===== @groovy.beans.Vetoable |
| |
| The `@Vetoable` annotation works in a similar manner to `@Bindable` but generates constrained property according to the JavaBeans specification, instead of bound properties. The annotation |
| can be placed on a class, meaning that all properties will be converted to constrained properties, or on a single property. For example, annotating this class with `@Vetoable`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=vetoable_on_class,indent=0] |
| ---- |
| |
| is equivalent to writing this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=vetoable_on_class_equiv,indent=0] |
| ---- |
| |
| If the annotation is put on a single property, only that property is made vetoable: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/SwingASTTransformsTest.groovy[tags=vetoable_single_property,indent=0] |
| ---- |
| |
| ==== Test assistance |
| [[xform-NotYetImplemented]] |
| ===== @groovy.lang.NotYetImplemented |
| |
| `@NotYetImplemented` is used to invert the result of a JUnit 3/4 test case. It is in particular useful if a feature is not yet implemented but the test is. In that case, it is expected |
| that the test fails. Marking it with `@NotYetImplemented` will inverse the result of the test, like in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=notyetimplemented,indent=0] |
| ---- |
| |
| Another advantage of using this technique is that you can write test cases for bugs before knowing how to fix them. If some time in the future, a modification in the code fixes a bug by side effect, |
| you'll be notified because a test which was expected to fail passed. |
| |
| [[xform-ASTTest]] |
| ===== @groovy.transform.ASTTest |
| |
| `@ASTTest` is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer "explore" the AST during compilation and |
| perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the bytecode is produced. `@ASTTest` can be |
| placed on any annotable node and requires two parameters: |
| |
| * _phase_: sets at which phase at which `@ASTTest` will be triggered. The test code will work on the AST tree at the end of this phase. |
| * _value_: the code which will be executed once the phase is reached, on the annotated node |
| |
| TIP: Compile phase has to be chosen from one of gapi::org.codehaus.groovy.control.CompilePhase . However, since it is not possible to annotate a node twice with the same annotation, you will |
| not be able to use `@ASTTest` on the same node at two distinct compile phases. |
| |
| `value` is a closure expression which has access to a special variable `node` corresponding to the annotated node, and a helper `lookup` method which will be discussed <<asttest-lookup,here>>. |
| For example, you can annotate a class node like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=asttest_basic,indent=0] |
| ---- |
| <1> we're checking the state of the Abstract Syntax Tree after the CONVERSION phase |
| <2> node refers to the AST node which is annotated by @ASTTest |
| <3> it can be used to perform assertions at compile time |
| |
| One interesting feature of `@ASTTest` is that if an assertion fails, then *compilation will fail*. Now imagine that we want to check the behavior of an AST transformation at compile time. |
| We will take `@PackageScope` here, and we will want to verify that a property annotated with `@PackageScope` becomes a package private field. For this, we have to know at which phase the |
| transform runs, which can be found in gapi::org.codehaus.groovy.transform.PackageScopeASTTransformation : semantic analysis. Then a test can be written like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=asttest_packagescope,indent=0] |
| ---- |
| |
| [[asttest-lookup]] |
| The `@ASTTest` annotation can only be placed wherever the grammar allows it. Sometimes, you would like to test the contents of an AST node which is not annotable. In this case, |
| `@ASTTest` provides a convenient `lookup` method which will search the AST for nodes which are labelled with a special token: |
| |
| [source,groovy] |
| ---- |
| def list = lookup('anchor') <1> |
| Statement stmt = list[0] <2> |
| ---- |
| <1> returns the list of AST nodes which label is 'anchor' |
| <2> it is always necessary to choose which element to process since lookup always returns a list |
| |
| Imagine, for example, that you want to test the declared type of a for loop variable. Then you can do it like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=asttest_forloop,indent=0] |
| ---- |
| |
| |
| ==== Grape handling |
| [[xform-Grab]] |
| ===== @groovy.lang.Grab |
| [[xform-GrabConfig]] |
| ===== @groovy.lang.GrabConfig |
| [[xform-GrabExclude]] |
| ===== @groovy.lang.GrabExclude |
| [[xform-GrabResolver]] |
| ===== @groovy.lang.GrabResolver |
| [[xform-Grapes]] |
| ===== @groovy.lang.Grapes |
| |
| `Grape` is a dependency management engine embedded into Groovy, relying on several annotations which are described |
| thoroughly in this <<section-grape,section of the guide>>. |
| |
| [[developing-ast-xforms]] |
| === Developing AST transformations (TBD) |
| ==== Compilation phases guide (TBD) |
| ==== Local transformations (TBD) |
| ==== Global transformations (TBD) |
| ==== AST API guide (TBD) |
| ==== Testing AST transformations (TBD) |