blob: d715a06b7e336ffb91ced5b1ef4ac65d66ff9051 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xtend - Active Annotations</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description"
content="Xtend is a statically typed programming language sitting on top of Java.">
<meta name="author" content="Sven Efftinge">
<meta name="author" content="Miro Spoenemann">
<!-- styles -->
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="/xtend/images/favicon.png">
<link href="/xtend/css/bootstrap.css" rel="stylesheet" type='text/css'>
<link href="/xtend/css/bootstrap-responsive.css" rel="stylesheet" type='text/css'>
<link href="/xtend/css/shield-responsive.css" rel="stylesheet" type='text/css'>
<link href='/xtend/css/fonts.css' rel='stylesheet' type='text/css'>
<link href="/xtend/css/prettyPhoto.css" rel="stylesheet" media="screen" type='text/css'>
<link href="/xtend/css/prettify.css" type="text/css" rel="stylesheet"/>
<link href="/xtend/css/style.css" rel="stylesheet" type='text/css'>
<!--[if lt IE 9]>
<link href="css/iebugs.css" rel="stylesheet" type='text/css'>
<![endif]-->
<!-- BEGIN Cookie Consent
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.0.3/cookieconsent.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/3.0.3/cookieconsent.min.js"></script>
<script>
window.addEventListener("load", function(){
window.cookieconsent.initialise({
"palette": {
"popup": {
"background": "#000"
},
"button": {
"background": "#f1d600"
}
},
"theme": "edgeless",
"type": "opt-in",
onInitialise: function (status) {
var type = this.options.type;
var didConsent = this.hasConsented();
if (type == 'opt-in' && didConsent) {
// TODO: enable cookies
}
if (type == 'opt-out' && !didConsent) {
// TODO: disable cookies
}
},
onStatusChange: function(status, chosenBefore) {
var type = this.options.type;
var didConsent = this.hasConsented();
if (type == 'opt-in' && didConsent) {
// TODO: enable cookies
}
if (type == 'opt-out' && !didConsent) {
// TODO: disable cookies
}
},
onRevokeChoice: function() {
var type = this.options.type;
if (type == 'opt-in') {
// TODO: disable cookies
}
if (type == 'opt-out') {
// TODO: enable cookies
}
},
"content": {
"href": "http://www.eclipse.org/legal/privacy.php"
}
})});
</script>
END Cookie Consent -->
</head>
<body>
<!-- Navbar -->
<div class="navbar navbar-fixed-top"
style="border-bottom: 1px solid #000;">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse"
data-target=".nav-collapse"> <span class="icon-bar"></span> <span
class="icon-bar"></span> <span class="icon-bar"></span>
</a> <a class="brand" href="/xtend/index.html"></a>
<div class="nav-collapse collapse" style="height: 0px;">
<ul class="nav">
<li ><a href="/xtend/download.html">Download</a></li>
<li ><a href="/xtend/documentation/index.html">Documentation</a></li>
<li ><a href="/xtend/community.html">Community</a></li>
<li ><a href="http://xtext.org">Xtext</a></li>
<li ><a href="http://www.eclipse.org">Eclipse.org</a></li>
</ul>
<!--div class="btn-group pull-right">
<div class="g-plusone" data-href="http://www.xtend-lang.org"></div>
</div-->
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<!-- Navbar End -->
<div class="page-content">
<script>
function startSearch(event) {
if (event.keyCode == 13) {
var q = 'site:eclipse.org/xtend/documentation+' + event.target.value;
window.open('https://www.google.com/search?q=' + q, "_self");
}
}
</script>
<div class="wrapper">
<div id="page">
<div class="inner">
<div id="maincontainer" class="container">
<span class="edit-on-github pull-right">
<a href="https://github.com/eclipse/xtext/edit/website-published/xtend-website/documentation/204_activeannotations.md">Edit on Github</a>
</span>
<div class="span3" style="margin-left: 0px;">
<div class="search-bar">
<img src="/xtend/images/search-gray.png"/>
<input type="search" id="google-search" onkeyup="startSearch(event);"/>
</div>
<ul id="nav-outline">
<li>&nbsp;</li>
<li style="color : #333;">Getting Started</li>
<li><a href="101_gettingstarted.html">Hello World</a></li>
<li><a href="102_moviesexample.html">The Movies Example</a></li>
<li>&nbsp;</li>
<li style="color : #333;">Reference Documentation</li>
<li><a href="201_types.html">Java Interoperability</a></li>
<li><a href="202_xtend_classes_members.html">Classes and Members</a></li>
<li><a href="203_xtend_expressions.html">Expressions</a></li>
<li><a href="204_activeannotations.html">Active Annotations</a></li>
</ul>
</div>
<div class="span9 doc-contents">
<h1 id="active-annotations">Active Annotations</h1>
<p><em>Active annotations</em> allow developers to participate in the translation process of Xtend source code to Java code via library. That’s useful in cases where Java requires to write a lot of boilerplate manually. For instance, many of the good old design patterns fall into this category. With <em>active annotations</em> you no longer need to remember how the <a href="http://en.wikipedia.org/wiki/Visitor_pattern">Visitor</a> or the <a href="http://en.wikipedia.org/wiki/Observer_pattern">Observer</a> pattern should be implemented. In Xtend you can implement the expansion of such patterns in a library and let the compiler do the heavy lifting for you.</p>
<p>An <em>active annotation</em> is just an annotation declared either in Java or Xtend, which is itself annotated with <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/Active.java">Active</a>. <code>@Active</code> takes a type literal as a parameter pointing to the processor.</p>
<p>The IDE plugin comes with an example project, which you can easily materialize into your workspace. To do so use the new project wizard and in the category <em>Xtend Examples</em> choose the active annotation example. The examples contain three different annotations which we will use for further explanation.</p>
<p>For instance, <code>@Extract</code> is an annotation which extracts an interface for a class. The annotation declaration looks like this:</p>
<pre><code class="language-xtend">@Active(ExtractProcessor)
annotation Extract {}
</code></pre>
<h2 id="active-annotations-processor">Annotation Processor</h2>
<p>A processor class must implement one or more of the lifecycle call-back interfaces provided by the compiler. This interfaces are:</p>
<ul>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/RegisterGlobalsParticipant.java">RegisterGlobalsParticipant</a> The first call back. Only called to register type names when a global symbol table (i.e. index) is created. See <a href="204_activeannotations.html#active-annotations-register-globals">Phase 1</a>.</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/TransformationParticipant.java">TransformationParticipant</a> This callback is often the most useful, as it allows to alter the translated Java structure by e.g. adding, removing or altering members See <a href="204_activeannotations.html#active-annotations-transformation-phase">Phase 2</a>.</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/ValidationParticipant.java">ValidationParticipant</a> Although you can already do validation during transformation, you are only during this callback allowed to fully resolve any types. See <a href="204_activeannotations.html#active-annotations-validation-phase">Phase 3</a>.</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/CodeGenerationParticipant.java">CodeGenerationParticipant</a> If you want to generate some additional text, e.g. XML, this hook is for you. See <a href="204_activeannotations.html#active-annotations-code-generation">Phase 4</a>.</li>
</ul>
<p>There are base classes that implment all callback interfaces. You should subclass one of those depending on your annotation target:</p>
<ul>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/AbstractClassProcessor.java">AbstractClassProcessor</a> is a base class for class annotations</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/AbstractInterfaceProcessor.java">AbstractInterfaceProcessor</a> is a base class for interface annotations</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/AbstractAnnotationTypeProcessor.java">AbstractAnnotationTypeProcessor</a> is a base class for annotation type annotations</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/AbstractEnumerationTypeProcessor.java">AbstractEnumerationTypeProcessor</a> is a base class for enumeration type annotations</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/AbstractMethodProcessor.java">AbstractMethodProcessor</a> is a base class for method annotations</li>
<li><a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/AbstractFieldProcessor.java">AbstractFieldProcessor</a> is a base class for field annotations</li>
</ul>
<p>If you want to annotate other elements such as parameters or constructors, you should have a look at the bases classes and adapt their implementation (basically the type parameter) accordingly. It is also possible to have a very generic processor by using <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/declaration/Declaration.java">Declaration</a>, the super type of all declared elements.</p>
<h3 id="active-annotations-register-globals">Phase 1: Register Globals</h3>
<p>The first phase in the lifecycle of the compiler is about indexing the types as globally available symbols. By implementing a <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/RegisterGlobalsParticipant.java">RegisterGlobalsParticipant</a> you have the chance to create and register new Java types during this phase. It’s important to do this in a first phase so later on the compiler can find and access these types.</p>
<p>For example the ExtractProcessor adds one interface per annotated class:</p>
<pre><code class="language-xtend">class ExtractProcessor extends AbstractClassProcessor {
override doRegisterGlobals(ClassDeclaration annotatedClass, RegisterGlobalsContext context) {
context.registerInterface(annotatedClass.interfaceName)
}
def getInterfaceName(ClassDeclaration annotatedClass) {
annotatedClass.qualifiedName+"Interface"
}
...
}
</code></pre>
<p>The <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/RegisterGlobalsContext.java">RegisterGlobalsContext</a> provides all the services that are available during this compilation step. It is passed into the method <code>doRegisterGlobals()</code> along with a read-only representation of the annotated source elements. The AbstractClassProcessor in this example is invoked for all classes that are annotated with <code>@Extract</code>.</p>
<p>The compiler calls <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/RegisterGlobalsParticipant.java">RegisterGlobalsParticipant</a> once per compilation unit and provides access to all elements which are annotated with the <em>active annotation</em> this processor is registered for. Therefore the <code>ExtractProcessor</code> is invoked with a list of all classes that are defined in the same Xtend file for all the files that are being compiled.</p>
<h3 id="active-annotations-transformation-phase">Phase 2: Transformation</h3>
<p>In the second phase developers can modify the compiled Java classes and Java code. Annotation processors that implement <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/TransformationParticipant.java">TransformationParticipant</a> participate in this compile step. Similar to the <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/RegisterGlobalsParticipant.java">RegisterGlobalsParticipant</a> interface the compiler provides two arguments: The list of annotated translated and now mutable Java elements and a <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/TransformationContext.java">TransformationContext</a>. The context provides services that are specific for this second step.</p>
<p>A transformation participant can access and modify mutable Java elements. These are an in-memory representation of the generated Java code. They are usually very similar to the source elements, but can be modified and new methods, fields or constructors can be added. It is not possible to create new types during the transformation step. Also note, that other annotation processors might already have altered the model.</p>
<p>The <code>ExtractProcessor</code> implements the method <code>doTransform</code> like this:</p>
<pre><code class="language-xtend">class ExtractProcessor extends AbstractClassProcessor {
override doRegisterGlobals(ClassDeclaration annotatedClass, RegisterGlobalsContext context) {
context.registerInterface(annotatedClass.interfaceName)
}
def getInterfaceName(ClassDeclaration annotatedClass) {
annotatedClass.qualifiedName+"Interface"
}
override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
val interfaceType = findInterface(annotatedClass.interfaceName)
// add the interface to the list of implemented interfaces
annotatedClass.implementedInterfaces = annotatedClass.implementedInterfaces + #[interfaceType.newTypeReference]
// add the public methods to the interface
for (method : annotatedClass.declaredMethods) {
if (method.visibility == Visibility.PUBLIC) {
interfaceType.addMethod(method.simpleName) [
docComment = method.docComment
returnType = method.returnType
for (p : method.parameters) {
addParameter(p.simpleName, p.type)
}
exceptions = method.exceptions
]
}
}
}
}
</code></pre>
<p>In the first line, <code>findInterface</code> retrieves the interface which was registered during the registration of global symbols in the first phase: The method is defined in <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/TransformationContext.java">TransformationContext</a> which is used as an <a href="202_xtend_classes_members.html#extension-provider">extension provider</a>.</p>
<p>Next up the newly created interface is added to the existing list of implemented interfaces.</p>
<pre><code class="language-xtend">annotatedClass.implementedInterfaces = annotatedClass.implementedInterfaces + #[interfaceType.newTypeReference]
</code></pre>
<p>The code calls <code>setImplementedInterfaces(Iterable&lt;TypeReference&gt;)</code> on the annotated class. The right hand side of the assignment is a concatenation of the existing implemented interfaces and a type reference pointing to the freshly created interface.</p>
<p>A <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/declaration/TypeReference.java">TypeReference</a> can be created using one of the various methods from <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/services/TypeReferenceProvider.java">TypeReferenceProvider</a> which is a super type of <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/TransformationContext.java">TransformationContext</a>. These utilities are available as extensions, too.</p>
<h3 id="active-annotations-validation-phase">Phase 3: Validation</h3>
<p>The third lifecycle allows to participate in validation. You can already do validation during transformation and even during registerGlobals, but it is only now that you can fully resolve everything, including inferred type references.</p>
<h3 id="active-annotations-code-generation">Phase 4: Code Generation</h3>
<p>The last phase in the lifecycle of the compiler lets you participate in writing and updating the files. In the IDE this phase is only executed on save , while the previous two get executed after minor edits in the editor as well. In order to participate your processor needs to implement <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/CodeGenerationParticipant.java">CodeGenerationParticipant</a>. The extract interface example doesn’t make use of this hook, but another example for internationalization which is also included, generates a *.properties file, like this:</p>
<pre><code class="language-xtend">class ExternalizedProcessor extends AbstractClassProcessor {
...
override doGenerateCode(List&lt;? extends ClassDeclaration&gt; annotatedSourceElements, extension CodeGenerationContext context) {
for (clazz : annotatedSourceElements) {
val filePath = clazz.compilationUnit.filePath
val file = filePath.targetFolder.append(clazz.qualifiedName.replace('.', '/') + ".properties")
file.contents = '''
«FOR field : clazz.declaredFields»
«field.simpleName» = «field.initializerAsString»
«ENDFOR»
'''
}
}
}
</code></pre>
<p>The <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/CodeGenerationContext.java">CodeGenerationContext</a> provides all the services that are available during this phase. Have a look at the Java doc for more details.</p>
<h2 id="active-annotations-expression">On Expressions and Statements</h2>
<p>Most of the generated Java code is represented in the form of in-memory elements. Basically all the structural elements are represented as a dedicated <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/declaration/Element.java">Element</a>. If you want to generate the body of a method or the initializer of a field, you have two options.</p>
<h3 id="active-annotations-compilation-strategies">Generating Blackbox Java Code</h3>
<p>The first option is to assign a compilation strategy and take care of the Java code by yourself. As an example the body of a setter method of an observable instance is implemented by the following code snippet:</p>
<pre><code class="language-xtend">observableType.addMethod('set' + fieldName.toFirstUpper) [
addParameter(fieldName, fieldType)
body = '''
«fieldType» _oldValue = this.«fieldName»;
this.«fieldName» = «fieldName»;
_propertyChangeSupport.firePropertyChange("«fieldName»", _oldValue, «fieldName»);
'''
]
</code></pre>
<p>A template expression is used to implement the body. Although this code is execute during transform, the actual concatenation only happens during code generation. It’s a special form of a string concatenation, that will automatically add imports when concatenating a TypeReference, java.lang.Class or a TypeDeclaration. It takes existing imports into account and adds new imports on the fly if necessary.</p>
<h3 id="active-annotations-assigning-expressions">Assigning Expressions</h3>
<p>A second alternative is to put expressions from the Xtend source into the context of a generated Java element. This allows to directly use the code that was written in the source file. An annotation <code>@Lazy</code> which turns fields into lazily initialized properties, may be used like this:</p>
<pre><code class="language-xtend">class MyClass {
@Lazy String myField = expensiveComputation()
}
</code></pre>
<p>The processor for this <em>active annotation</em> could infer a synthetic initializer method and add a getter-method, which calls the initializer if the field is still <code>null</code>. Therefore, the initialization expression of the field has to become the method body of the synthesized initializer method. The following code performs this transformation:</p>
<pre><code class="language-xtend">override doTransform(MutableFieldDeclaration field, extension TransformationContext context) {
// add synthetic init-method
field.declaringType.addMethod('_init' + field.simpleName) [
visibility = Visibility.PRIVATE
returnType = field.type
// reassign the initializer expression to be the init method's body
// this automatically removes the expression as the field's initializer
body = field.initializer
]
// add a getter method which lazily initializes the field
field.declaringType.addMethod('get' + field.simpleName.toFirstUpper) [
returnType = field.type
body = ['''
if («field.simpleName»==null)
«field.simpleName» = _init«field.simpleName»();
return «field.simpleName»;
''']
]
}
</code></pre>
<h2 id="active-annotations-validation">Custom Compiler Checks</h2>
<p>The previous example requires each annotated field to have an initializer. Otherwise it would not be possible to use lazy initialization to assign its value. Also a simple check for a <code>null</code> reference could cause trouble with primitive values. A validation for that case would be sensible, too. In order to guide the user dedicated compilation errors should be raised if these constrains are violated.</p>
<p>The <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/TransformationContext.java">TransformationContext</a> inherits methods for exactly that purpose from the <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib.macro/src/org/eclipse/xtend/lib/macro/services/ProblemSupport.java">ProblemSupport</a> service.</p>
<p>Since the <code>context</code> is declared as an <a href="202_xtend_classes_members.html#extension-provider">extension provider</a>, those methods can be used as extensions and it allows to implement the constraint check accordingly:</p>
<pre><code class="language-xtend">override doTransform(MutableFieldDeclaration field, extension TransformationContext context) {
if (field.type.primitive)
field.addError("Fields with primitives are not supported by @Lazy")
if (field.initializer == null)
field.addError("A lazy field must have an initializer.")
...
}
</code></pre>
<p>This ensures that the user is notified about invalid applications of the <em>active annotation</em> <code>@Lazy</code>.</p>
<h2 id="active-annotation-classpath">Class Path Setup and Testing</h2>
<p>An <em>active annotation</em> can not be used in the same project it is declared in, but has to reside on an upstream project. Alternatively it can be compiled and deployed in a jar. The annotation and the processor itself only rely on the interfaces defined in <code>org.eclipse.xtend.lib.macro</code> which is part of Xtend’s small standard library.</p>
<p>Also note that the macro library as well as the processors are strictly speaking compile-time only dependencies. So if it matters, like e.g. on Android devices, you don’t need to ship them at runtime.</p>
<p>When used in an IDE the compiled annotation processor is loaded and executed on the fly within the IDE process.</p>
<p>Therefore, careful testing and debugging of the processor is essential. It is best done in a unit test. Such a test needs the whole Xtend compiler on the class path, which you can obtain by means of an OSGi bundle dependency or via Maven. The maven dependency is</p>
<pre><code>&lt;dependency&gt;
&lt;groupId&gt;org.eclipse.xtend&lt;/groupId&gt;
&lt;artifactId&gt;org.eclipse.xtend.core&lt;/artifactId&gt;
&lt;version&gt;2.25.0&lt;/version&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.eclipse.xtext&lt;/groupId&gt;
&lt;artifactId&gt;org.eclipse.xtext.xbase.testing&lt;/artifactId&gt;
&lt;version&gt;2.25.0&lt;/version&gt;
&lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
</code></pre>
<p>and the equivalent OSGI bundle is <code>org.eclipse.xtend.core</code>.</p>
<h3 id="active-annotation-compiler-tester">Testing</h3>
<p>The <code>XtendCompilerTester</code> is a convenient helper class for testing the processing and the compilation. It allows to test active annotation processors by either comparing the generated Java source using a String comparison or by inspecting the produced Java elements. In addition you can even compile the generated Java source to a Java class and create and test instances of it reflectively.</p>
<p>The example project contains a couple of test cases:</p>
<pre><code class="language-xtend">class LazyTest {
extension XtendCompilerTester compilerTester = XtendCompilerTester.newXtendCompilerTester(Lazy.classLoader)
@Test def void testLazy() {
'''
import lazy.Lazy
class Person {
@Lazy String name = 'foo'
}
'''.assertCompilesTo(
'''
import lazy.Lazy;
@SuppressWarnings("all")
public class Person {
@Lazy
private String name;
private String _initname() {
return "foo";
}
public String getName() {
if (name==null)
name = _initname();
return name;
}
}
''')
}
}
</code></pre>
<p>This is a basic string comparison. It is a good way to start the development of a new annotation processor. Later on assertions against the produced elements and syntax tree (AST) may be a better choice since these are not so fragile against changes in the formatting. The <code>@Extract</code>-example uses this technique:</p>
<pre><code class="language-xtend">@Test def void testExtractAnnotation() {
'''
@extract.Extract
class MyClass {
override String doStuff(String myParam) throws IllegalArgumentException {
return myParam
}
}
'''.compile [
// declare the transformation context as a local extensions
val extension ctx = transformationContext
// look up the interface and the class
val interf = findInterface('MyClassInterface')
val clazz = findClass('MyClass')
// do assertions
assertEquals(interf, clazz.implementedInterfaces.head.type)
interf.declaredMethods.head =&gt; [
assertEquals('doStuff', simpleName)
assertEquals(string, returnType)
assertEquals(IllegalArgumentException.newTypeReference, exceptions.head)
]
]
}
</code></pre>
<h3 id="wrap-up">Wrap Up</h3>
<p><em>Active Annotations</em> are a powerful and unique concept that allows to solve a large class of problems that previously had to be solved in cumbersome ways. IDE wizards, many code generators or manually writing boilerplate code are no longer state of the art. Active annotations basically <em>is</em> a means of code generation, but its simple integration with existing projects and the fast development turnarounds diminish the typical downsides of code generation.</p>
<h2 id="existing-active-annotations">Existing Active Annotations</h2>
<p>Xtend comes with ready-to-use active annotations for common code patterns. They reside in the <code>org.eclipse.xtend.lib.annotations</code> plug-in/jar which must be on the class path of the project containing the Xtend files.</p>
<h2 id="property-annotation">@Accessors</h2>
<p>If you want to add getter and or setter methods for your fields <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib/src/org/eclipse/xtend/lib/annotations/Accessors.xtend"><code>@Accessors</code></a> is your friend. Here’s a basic example.</p>
<pre><code class="language-xtend">@Accessors String name
</code></pre>
<p>will compile to the Java code</p>
<pre><code class="language-java">private String name;
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
</code></pre>
<p>So by default a public getter and a public setter method is created. The <code>@Accessors</code> can be configured to tell that you only want one or the other and to change the visibility. this is done by means of <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib/src/org/eclipse/xtend/lib/annotations/Accessors.xtend">AccessorType</a> You can also use the annotation on class level to do the same for all fields.</p>
<p>Here is a more complex example, that shows how it works:</p>
<pre><code class="language-xtend">@Accessors class Person {
String name
String firstName
@Accessors(PUBLIC_GETTER, PROTECTED_SETTER) int age
@Accessors(NONE) String internalField
}
</code></pre>
<p>will compile to the Java code</p>
<pre><code class="language-java">@Accessors public class Person {
private String name
private String firstName
@Accessors(PUBLIC_GETTER, PROTECTED_SETTER) private int age
@Accessors(NONE) private String internalField
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public int getAge() {
return this.age;
}
protected void setAge(final int age) {
this.age = age;
}
}
</code></pre>
<h2 id="data-annotation">@Data</h2>
<p>The annotation <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib/src/org/eclipse/xtend/lib/annotations/Data.xtend"><code>@Data</code></a>, will turn an annotated class into a value object class. A class annotated with <code>@Data</code> is processed according to the following rules:</p>
<ul>
<li>all fields are final,</li>
<li>getter methods will be generated (if they do not yet exist),</li>
<li>a constructor with parameters for all non-initialized fields will be generated (if it does not exist),</li>
<li>equals(Object) / hashCode() methods will be generated (if they do not exist),</li>
<li>a toString() method will be generated (if it does not exist).</li>
</ul>
<p>Example:</p>
<pre><code class="language-xtend">@Data class Person {
String firstName
String lastName
def static void main(String[] args) {
val p = new Person(args.get(0), args.get(1))
println(p.getFirstName() + ' ' + p.lastName)
}
}
</code></pre>
<h2 id="delegate-annotation">@Delegate</h2>
<p>The <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtend.lib/src/org/eclipse/xtend/lib/annotations/Delegate.xtend"><code>@Delegate</code></a> annotation automatically generated delegate methods for all interfaces shared between the delegate and the currently implemented class. You can optionally restrict it to explicitly stated interfaces.</p>
<p>Let’s start with a basic example:</p>
<pre><code class="language-xtend">class MyClass implements SomeInterface {
// generates all methods of List and delegates to this field
@Delegate SomeSubTypeOfSumInterface myDelegate
}
</code></pre>
<p>It is not only possible to delegate to fields, but also to methods so you for instance could lazily create the delegate object or use a different one each time. If you use a method you can also declare additional parameters, that will tell you about the method that should be invoked.</p>
<p>Here’s another example:</p>
<pre><code class="language-xtend">class MyClass implements SomeInterface {
SomeInterface myDelegate;
@Delegate def List&lt;? extends String&gt; provideDelegate(String methodName, Class[] paramTypes, Object[] actualArguments) {
if (!canHandle(methodName,paramTypes,actualArguments) {
throw new UnsupportedOperationException("The method "+methodName+" is not supported.");
}
return myDelegate
}
}
</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div id="extra">
<div class="container inner-footer">
<div class="row">
<div class="span6">
<h3 class="footer-links-header">Quick Links</h3>
<ul class="footer-links clearfix">
<li><a href="http://www.eclipse.org/legal/privacy.php">Privacy Policy</a></li>
<li><a href="http://www.eclipse.org/legal/termsofuse.php">Terms of Use</a></li>
<li><a href="http://www.eclipse.org/legal/copyright.php">Copyright Agent</a></li>
<li><a href="http://www.eclipse.org/legal/">Legal</a></li>
</ul>
<ul class="footer-links clearfix">
<li><a href="http://www.eclipse.org">Eclipse Home</a></li>
<li><a href="http://marketplace.eclipse.org/">Market Place</a></li>
<li><a href="http://www.planeteclipse.org/">Eclipse Planet</a></li>
<li><a href="https://www.eclipse.org/forums/index.php/f/27/">Xtext Forum</a></li>
</ul>
</div>
<div class="span6">
<!-- Social Media Links -->
<h3 class="footer-links-header"">Social Media</h3>
<ul class="footer-links clearfix">
<li>
<a href="https://twitter.com/xtendlang"><img src="/xtend/images/Twitter-bird-darkgray.png" class="img-responsive" style="margin-right: 5px;height: 1em;" alt="Twitter icon">@xtendlang on Twitter</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<a href="#" class="scrollup fadeOutRight animated" style="display: none;">ScrollUp</a>
<a href="http://dryicons.com/">Icons by http://dryicons.com</a>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/xtend/js/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="/xtend/js/bootstrap.min.js" type="text/javascript"></script>
<!-- include pretty-print files -->
<script src="/xtend/js/prettify.js" type="text/javascript" ></script>
<script src="/xtend/js/lang-xtend.js" type="text/javascript"></script>
<!-- Include the plug-in -->
<script src="/xtend/js/jquery.prettyPhoto.js" type="text/javascript"></script>
<script src="/xtend/js/jquery.easing.1.3.js" type="text/javascript"></script>
<script src="/xtend/js/custom.js" type="text/javascript"></script>
<!--script src="https://apis.google.com/js/platform.js" async defer></script-->
<!--script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push([ '_setAccount', 'UA-2429174-4' ]);
_gaq.push([ '_trackPageview' ]);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl'
: 'http://www')
+ '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script-->
</footer>
</body>
</html>