blob: 53c60363570cf53770902504cb4532470385bb1d [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xtext - DSL for Guice</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description"
content="The website of Eclipse Xtext, an open-source framework for development of programming languages and domain-specific languages">
<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="/Xtext/images/favicon.png">
<link href="/Xtext/css/bootstrap.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/bootstrap-responsive.css" rel="stylesheet" type='text/css'>
<link href="/Xtext/css/shield-responsive.css" rel="stylesheet" type='text/css'>
<link href='/Xtext/css/fonts.css' rel='stylesheet' type='text/css'>
<link href="/Xtext/css/prettyPhoto.css" rel="stylesheet" media="screen" type='text/css'>
<link href="/Xtext/css/prettify.css" type="text/css" rel="stylesheet"/>
<link href="/Xtext/css/style.css" rel="stylesheet" type='text/css'>
<!-- cover flow -->
<link href="/Xtext/css/coverflow.css" rel="stylesheet" type='text/css'>
<!--[if lt IE 9]>
<link href="/css/iebugs.css" rel="stylesheet" type='text/css'>
<![endif]-->
</head>
<body>
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-TGDS5S"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TGDS5S');</script>
<!-- End Google Tag Manager -->
<header class="site-header">
<!-- Navbar -->
<div class="navbar navbar-fixed-top">
<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="/Xtext/index.html"></a>
<div class="nav-collapse collapse" style="height: 0px;">
<ul class="nav">
<!--li ><a href="/Xtext/news.html">News</a></li-->
<li ><a href="/Xtext/download.html">Download</a></li>
<li ><a href="/Xtext/documentation/index.html">Documentation</a></li>
<li ><a href="/Xtext/community.html">Community</a></li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Support &amp; Trainings<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="https://www.itemis.com/en/xtext/support-and-team/" target="_blank">itemis</a></li>
<li><a href="https://typefox.io/trainings-2" target="_blank">TypeFox</a></li>
</ul>
</li>
<li ><a href="http://xtend-lang.org">Xtend</a></li>
</ul>
<!--div class="nav pull-right">
<li ><a><iframe src="https://ghbtns.com/github-btn.html?user=eclipse&repo=xtext&type=star&count=true" frameborder="0" scrolling="0" width="170px" height="20px"></iframe></a></li>
</div-->
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<!-- Navbar End -->
</header>
<div class="page-content">
<script>
function startSearch(event) {
if (event.keyCode == 13) {
var q = 'site:eclipse.org/Xtext/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/xtext-website/documentation/205_guice.md">Edit on Github</a>
</span>
<div class="span3" style="margin-left: 0px;">
<div class="search-bar">
<img src="/Xtext/images/search-gray.png"/>
<input type="search" id="google-search" onkeyup="startSearch(event);"/>
</div>
<ul id="nav-outline" style="margin-left: 0px;">
<li class="nav-part">Getting Started</li>
<li><a href="102_domainmodelwalkthrough.html">15 Minutes Tutorial</a></li>
<li><a href="103_domainmodelnextsteps.html">15 Minutes Tutorial - Extended</a></li>
<li><a href="104_jvmdomainmodel.html">Five simple steps to your JVM language</a></li>
<!--li class="nav-part">Seven JVM Languages Built With Xbase</li>
<li><a href="201_sevenlang_introduction.html">Introduction</a></li>
<li><a href="202_scripting.html">Scripting Language</a></li>
<li><a href="203_builddsl.html">Build Language</a></li>
<li><a href="204_mongodb.html">DSL for MongoDB</a></li>
<li><a href="205_guice.html">DSL for Guice</a></li>
<li><a href="206_httprouting.html">Http Routing Language</a></li>
<li><a href="207_template.html">Template Language</a></li>
<li><a href="208_tortoise.html">Little Tortoise</a></li-->
<li class="nav-part">Reference Documentation</li>
<li><a href="301_grammarlanguage.html">The Grammar Language</a></li>
<li><a href="302_configuration.html">Configuration</a></li>
<li><a href="303_runtime_concepts.html">Language Implementation</a></li>
<li><a href="305_xbase.html">Integration with Java</a></li>
<!--li><a href="306_mwe2.html">MWE2</a></li-->
<!--li><a href="307_special_languages.html">Typical Language Configurations</a></li-->
<li><a href="308_emf_integration.html">Integration with EMF</a></li>
<li><a href="310_eclipse_support.html">Eclipse Support</a></li>
<!--li><a href="320_idea_support.html">IntelliJ IDEA Support</a></li-->
<li><a href="330_web_support.html">Web Editor Support</a></li>
<li><a href="350_continuous_integration.html">Continuous Integration</a></li>
<!--li class="nav-part">Appendix</li>
<li><a href="401_migrating_from_1_0_x.html">Migrating from Xtext 1.0.x to 2.0</a></li>
<li><a href="402_migrating_from_0_7.html">Migrating from Xtext 0.7.x to 1.0</a></li-->
</ul>
</div>
<div class="span8 doc-contents">
<h1 id="guice">DSL for Guice</h1>
<p>The Guice DSL is a little language that lets you define Guice modules in a readable and declarative way.</p>
<p><img src="images/guicemodules_screenshot.png" alt="" /></p>
<h2 id="guice-solution">Overview</h2>
<p>Guice is a great dependency injection container, which uses Java types and annotations to declare and refer to injection points. You could for instance have the following field:</p>
<pre><code class="language-java">@Inject
@Named("properties") Map&lt;String,String&gt; properties;
</code></pre>
<p>A module is used to tell the framework what instance to inject into such injection points. For that, Guice comes with a fluent interface API written in Java and a couple of tricks (e.g. <em>TypeLiteral</em>) to make the configuration as readable and maintainable as possible.</p>
<p>You could for instance declare the following module:</p>
<pre><code class="language-java">public class MyModule implements Module {
@Override
public void configure(Binder binder) {
Map&lt;String,String&gt; properties = Maps.newHashMap();
properties.put("debugLevel", "info");
binder.bind(new TypeLiteral&lt;Map&lt;String,String&gt;&gt;(){})
.annotatedWith(Names.named("properties"))
.toInstance(properties);
}
}
</code></pre>
<p>The big advantage of using Java over an external text or XML file is that you can leverage the IDE and the type checking. We want to have that. The downside is that you have to trick (i.e. <em>TypeLiteral</em>) a lot in order to have an agreeable syntax. Also since the configuration is ‘hidden’ in a method implementation and not really declarative you cannot validate a Guice module at compile time.</p>
<p>The Guice DSL described in this section lets you describe the module above like this:</p>
<pre><code class="language-guice"> MyModule {
@Named("properties") Map&lt;String,String&gt;
to-instance newHashMap('debugLevel' -&gt; 'info')
}
</code></pre>
<p>This not only uses the exact same syntax one uses in any injection points, but also opens up all kinds of possibilities for static analysis. Usually the instantiation of a Guice injector at runtime takes quite some time, because then all the very helpful validation is done. A language like the one described in this section could do all theses analysis at compile time, that way speeding up start up of the whole application significantly.</p>
<h2 id="guice-running">Running the Example</h2>
<p>In the example located in the project <em>org.xtext.guicemodules.examples</em> two modules are declared, one for a possible runtime scenario and one for a test scenario (yes, you sometimes want a module for tests).</p>
<pre><code class="language-guice">import com.acme.*
import com.acme.impl.*
com.acme.RuntimeModule {
bind DataProvider to FileDataProvider
bind @DataFile String to-instance 'my-data.txt'
}
com.acme.TestModule mixin RuntimeModule {
bind DataProvider to-instance ['dummy-data' ]
bind LoggingService to BufferedLoggingService
}
</code></pre>
<p>You can see the two modules in action by running <code>com.acme.Main</code> from the context menu as a <em>Java application</em> or as a <em>JUnit test</em>.</p>
<h2 id="guice-grammar">Grammar</h2>
<p>The grammar is less than 30 lines long. It allows declaring any number of imports using the import mechanism already described for the <a href="202_scripting.html#scripting-grammar">scripting language</a>. A module can ‘mixin’ any number of other modules, which allows to reuse existing modules but override keys with different bindings. The mixin feature is described as a cross reference to another <code>ModuleAST</code>. Cross references are covered in detail in the respective <a href="301_grammarlanguage.html#cross-references">documentation section</a>.</p>
<p>The language allows binding keys to other keys and to instances. Other concepts like binding to providers is something you could do, but would not help in getting the idea across. Everybody is encouraged to fork this first prototype and build something really useful ;-)</p>
<p>Instead of extending <code>org.eclipse.xtext.xbase.Xbase</code>, the grammar <code>org.eclipse.xtext.xbase.annotations.XbaseWithAnnotations</code> is extended, which adds full support for annotations. You just have to refer to the rule <code>XAnnotation</code> as it is done in the rule <code>KeyAST</code>. Btw. it is sometimes a good idea to suffix (or prefix) the AST node types to avoid confusion when working with a library where concepts are named similarly.</p>
<pre><code class="language-xtext">grammar org.xtext.guicemodules.GuiceModules
with org.eclipse.xtext.xbase.annotations.XbaseWithAnnotations
import "http://www.eclipse.org/xtext/common/JavaVMTypes" as types
generate guiceModules "http://www.xtext.org/guicemodules/GuiceModules"
ModulesAST :
importSection=XImportSection?
modules+=ModuleAST*;
ModuleAST :
name=QualifiedName ('mixin' mixins+=[ModuleAST|QualifiedName]
(',' mixins+=[ModuleAST|QualifiedName])*)?
'{'
bindings+=BindingAST*
'}'
;
BindingAST:
'bind' from=KeyAST
('to' to=KeyAST | 'to-instance' toInstance=XExpression)?;
KeyAST:
annotation=XAnnotation? type=JvmTypeReference;
</code></pre>
<h2 id="guice-inferrer">Translation to Java</h2>
<p>A module is mapped to a single java class. The ‘mixin’ modules are not translated to Java inheritance but to a delegation approach. Here is how a simple module declaration with a single mixed-in module is translated.</p>
<ul>
<li>
<p>DSL:</p>
<pre><code class="language-guice">MyModule mixin OtherModule {
}
</code></pre>
</li>
<li>
<p>Java:</p>
<pre><code class="language-java">public class MyModule implements Module {
private OtherModule otherModule = new OtherModule();
public void configure(final Binder binder) {
configure(binder, new HashSet&lt;com.google.inject.Key&lt;?&gt;&gt;());
}
public void configure(final Binder bind,
final Set&lt;Key&lt;? extends Object&gt;&gt; usedKeys) {
try {
testModule.configure(bind, usedKeys);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
</code></pre>
</li>
</ul>
<p>As you can see, the language simply leverages the nice <code>equals</code>/<code>hashCode</code> implementation of Guice’s <a href="https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Key.html">Key</a> to implement the override semantics of mixed-in modules.</p>
<p>The corresponding parts in <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.guicemodules/src/org/xtext/guicemodules/jvmmodel/GuiceModulesJvmModelInferrer.xtend">GuiceModulesJvmModelInferrer</a> are:</p>
<pre><code class="language-xtend">accept(module.toClass(module.fullyQualifiedName))[
...
// declare a field for each mixed-in module
for (mixin : module.mixins) {
if (!mixin.eIsProxy)
members += mixin.toField( mixin.simpleName,
typeRef(mixin.fullyQualifiedName.toString)) [
initializer = '''new «mixin.name»()'''
]
}
// and later when declaring the configure method
members+= module.toMethod("configure", typeRef(void)) [
documentation = 'Registers bindings for keys not present in ...'
parameters += module.toParameter("bind", typeRef(Binder))
parameters += module.toParameter("usedKeys", typeRef(Set, typeRef(Key, wildcard)))
body = '''
try {
...some other code
«FOR mix : module.mixins»
«mix.simpleName».configure(bind, usedKeys);
«ENDFOR»
} catch (Exception e) {
throw new RuntimeException(e);
}
'''
]
</code></pre>
<p>Expressions are used in <code>to-instance</code> bindings and as always they need a proper scope to live in. The simplest way is to declare a private method for each expression.</p>
<pre><code class="language-xtend">for (binding : module.bindings) {
// if it's a toInstance binding, create a synthetic
// method to give the expression a proper scope
if (binding.toInstance != null) {
members += binding.toMethod(binding.syntheticToInstanceName,
binding.from.type) [
visibility = JvmVisibility.PRIVATE
body = binding.toInstance
]
}
...
</code></pre>
<p>In order to obtain instances of annotations as required by Guice’s <a href="https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Binder.html">Binder</a>, we declare dummy fields for annotated types and use reflection to get corresponding instances.</p>
<ul>
<li>
<p>DSL:</p>
<pre><code class="language-guice">com.acme.RuntimeModule {
bind @DataFile String to-instance 'my-data.txt'
}
</code></pre>
</li>
<li>
<p>Java:</p>
<pre><code class="language-java">// declaration of dummy field with annotation
@DataFile
private String _from0;
// and later the following code is used to get the key
Key&lt;java.lang.String&gt; key =
Key.get(new TypeLiteral&lt;String&gt;(){},
getClass().getDeclaredField("_from0").getAnnotations()[0]);
</code></pre>
</li>
</ul>
<p>The two sections in the model inferrer responsible for this are:</p>
<pre><code class="language-xtend">for (binding : module.bindings) {
...
// if a key has an annotation, declare a field so we can use that
// annotation via reflection later.
if (binding.to?.annotation != null) {
members += binding.toField(binding.to.syntheticName, binding.to.type) [
addAnnotation(binding.to.annotation)
visibility = JvmVisibility.PRIVATE
]
}
if (binding.from.annotation != null) {
members += binding.toField(binding.from.syntheticName,
binding.from.type) [
addAnnotation(binding.from.annotation)
visibility = JvmVisibility.PRIVATE
]
}
}
// and the following method
def guiceKey(KeyAST it) '''
Key.get(new TypeLiteral&lt;«type&gt;(){}«
IF annotation != null
», getClass().getDeclaredField("«syntheticName»").getAnnotations()[0]«
ENDIF»)'''
</code></pre>
<p>That is basically it. The rest should hopefully be self-explanatory.</p>
<h2 id="guice-validation">Validation</h2>
<p>One of the sweet spots for a Guice modules DSL is the ability to do a lot of the validation usually done at runtime during compile time. Since this is just an example it just scratches the surface. There is just a single compiler check validating whether any used annotation is itself annotated with <code>BindingAnnotation</code>.</p>
<p>This is the relevant code from <a href="https://github.com/xtext/seven-languages-xtext/blob/master/languages/org.xtext.guicemodules/src/org/xtext/guicemodules/validation/GuiceModulesValidator.xtend">GuiceModulesValidator</a>:</p>
<pre><code class="language-xtend"> @Check def checkAnnotationIsBindingAnnotation(XAnnotation it) {
switch type : annotationType {
JvmAnnotationType:
if(!type.annotations.exists[
annotation.is(BindingAnnotation)
])
error("The annotation is not annotated with @BindingAnnotation",
XANNOTATION__ANNOTATION_TYPE)
}
}
</code></pre>
<p>It would be really cool to leverage the full information, which is available and analyze the dependencies transitively so you get feedback while you type for any unfulfilled dependencies. Also using all the information to compile a ready to use <a href="https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/Injector.html">Injector</a> instead of a module seems interesting.</p>
<hr />
<p><strong><a href="206_httprouting.html">Next Chapter: Http Routing Language</a></strong></p>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div id="extra">
<div class="inner">
<div class="container">
<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://live.eclipse.org/">Eclipse Live</a></li>
<li><a href="http://www.planeteclipse.org/">Eclipse Planet</a></li>
</ul>
</div>
<div class="span6">
<h3 class="footer-links-header"><a href="https://twitter.com/xtext" style="color: white;">@Xtext</a> on Twitter</h3>
<a class="twitter-timeline" href="https://twitter.com/xtext" data-widget-id="346625441290928128"
data-chrome="noheader nofooter transparent"
data-theme="dark">Tweets by @xtext</a>
<script>
!function(d,s,id) {
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
if(!d.getElementById(id)) {
js=d.createElement(s);
js.id=id;
js.src=p+"://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);
}
}(document,"script","twitter-wjs");
</script>
</div>
</div>
</div>
</div>
</div>
<a href="#" class="scrollup fadeOutRight animated" style="display: none;">ScrollUp</a>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/Xtext/js/jquery-1.11.3.min.js"></script>
<script src="/Xtext/js/bootstrap.min.js"></script>
<script src="/Xtext/js/jquery.easing.1.3.js" type="text/javascript"></script>
<script src="/Xtext/js/jquery.prettyPhoto.js" type="text/javascript"></script>
<script src="/Xtext/js/twitter.js" type="text/javascript"></script>
<script src="/Xtext/js/prettify.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-xtend.js" type="text/javascript"></script>
<script src="/Xtext/js/lang-common.js" type="text/javascript"></script>
<script src="/Xtext/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-3' ]);
_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-->
<script src="/Xtext/js/coverflow.min.js" type="text/javascript"></script>
<script>
$(function() {
$('#coverflow').coverflow({
active : 1,
visibleAside: 2,
overlap : 0.5,
scale : 0.9,
angle : 20,
trigger : {
"itemfocus" : true,
"swipe" : true,
"mousewheel" : false
}
});
$('#coverflow :hidden').toggle();
$(window).resize(function() {
$('#coverflow').coverflow();
});
});
</script>
</footer>
</body>
</html>