blob: 753480dbab6654912356a394969733ef655b2f3d [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Xtend - The Movies Example</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/102_moviesexample.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="movies-example">The Movies Example</h1>
<p>The movies example is included in the example project <em>Xtend Introductory Examples</em> (src/examples6/Movies.xtend) and is about reading a file with data about movies and doing some analysis on it.</p>
<h2 id="the-data">The Data</h2>
<p>The movie database is a plain text file (data.csv) with data sets describing movies. Here is an example data set:</p>
<pre><code>Naked Lunch 1991 6.9 16578 Biography Comedy Drama Fantasy
</code></pre>
<p>The values are separated by two spaces. The columns are :</p>
<ol>
<li>title</li>
<li>year</li>
<li>rating</li>
<li>numberOfVotes</li>
<li>categories</li>
</ol>
<p>Let us define a data type <code>Movie</code> representing a data set:</p>
<pre><code class="language-xtend">@Data class Movie {
String title
int year
double rating
long numberOfVotes
Set&lt;String&gt; categories
}
</code></pre>
<p>A movie is a POJO with a strongly typed field for each column in the data sets. The <a href="204_activeannotations.html#data-annotation">@Data</a> annotation will turn the class into an immutable value class, that is it will get</p>
<ul>
<li>a getter-method for each field,</li>
<li>a <code>hashCode()</code>/<code>equals()</code> implementation,</li>
<li>implementation of <code>Object.toString(),</code></li>
<li>a constructor accepting values for all fields in the declared order.</li>
</ul>
<h2 id="parsing-the-data">Parsing The Data</h2>
<p>Let us now add another class to the same file and initialize a field called movies with a list of movies. For the initialization we parse the text file and turn the data records into <code>Movie</code>s:</p>
<pre><code class="language-xtend">import java.io.FileReader
import java.util.Set
import static extension com.google.common.io.CharStreams.*
class Movies {
val movies = new FileReader('data.csv').readLines.map [ line |
val segments = line.split(' ').iterator
return new Movie(
segments.next,
Integer.parseInt(segments.next),
Double.parseDouble(segments.next),
Long.parseLong(segments.next),
segments.toSet
)
]
}
</code></pre>
<p>A <a href="202_xtend_classes_members.html#fields">field’s type</a> can be inferred from the expression on the right hand-side. That is called local type inference and is supported everywhere in Xtend. We want the field to be final, so we declare it as a value using the keyword <code>val</code>.</p>
<p>The initialization on the right hand side first creates a new <a href="http://docs.oracle.com/javase/8/docs/api/java/io/FileReader.html">FileReader</a>. Then the method <code>readLines()</code> is invoked on that instance. But if you have a look at <code>FileReader</code> you will not find such a method. In fact <code>readLines()</code> is a static method from Google Guava’s <a href="https://guava.dev/releases/27.1-jre/api/docs//com/google/common/io/CharStreams.html">CharStreams</a> which was imported as an <a href="202_xtend_classes_members.html#extension-imports">extension</a>. Extensions allow us to use this readable syntax.</p>
<pre><code class="language-xtend">import static extension com.google.common.io.CharStreams.*
</code></pre>
<p><a href="https://guava.dev/releases/27.1-jre/api/docs//com/google/common/io/CharStreams.html"><code>CharStreams.readLines(Reader)</code></a> returns a <a href="http://docs.oracle.com/javase/8/docs/api/java/util/List.html"><code>List&lt;String&gt;</code></a> on which we call another extension method <code>map</code>. This one is defined in the runtime library (<a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/ListExtensions.java">ListExtensions.map(…)</a>) and is automatically imported and therefore available on all lists. The <code>map</code> extension expects a function as a parameter. It basically invokes that function for each value in the list and returns another list containing the results of the function invocations. Actually this mapping is performed lazily so if you never access the values of the result list, the mapping function is never executed.</p>
<p>Function objects are created using <a href="203_xtend_expressions.html#lambdas">lambda expressions</a> (the code in squared brackets). Within the lambda we process a single line from the text file and turn it into a movie by splitting the string using two whitespace characters as the separator. On the result of the split operation, the method <code>iterator()</code> is invoked. As you might know <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/String.html">String.split(String)</a> returns a string array (<code>String[]</code>), which Xtend <a href="201_types.html#conversion-rules">auto-converts to a list</a> when we call <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html"><code>Iterable.iterator()</code></a> on it.</p>
<pre><code class="language-xtend">val segments = line.split(' ').iterator
</code></pre>
<p>Now we use the iterator to create an instance of <code>Movie</code> for each String that it yields. The data type conversion (e.g. <code>String</code> to <code>int</code>) is done by calling <a href="203_xtend_expressions.html#static-access">static methods</a> from the wrapper types. The rest of the <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html">Iterable</a> is turned into a set of categories. Therefore, the extension method <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/IteratorExtensions.java"><code>IteratorExtensions.toSet(Iterator&lt;T&gt;)</code></a> is invoked on the iterator to consume its remaining values.</p>
<pre><code class="language-xtend">return new Movie (
segments.next,
Integer.parseInt(segments.next),
Double.parseDouble(segments.next),
Long.parseLong(segments.next),
segments.toSet
)
</code></pre>
<h2 id="answering-some-questions">Answering Some Questions</h2>
<p>Now that we have parsed the text file into a <code>List&lt;Movie&gt;</code>, we are ready to execute some queries against it. We use <em>JUnit</em> to make the individual queries executable and to confirm their results.</p>
<h3 id="question1">Question 1 : What Is The Number Of Action Movies?</h3>
<pre><code class="language-xtend">@Test def numberOfActionMovies() {
assertEquals(828,
movies.filter[ categories.contains('Action') ].size)
}
</code></pre>
<p>First the movies are <code>filter</code>ed. The lambda expression checks whether the current movie’s categories contain the entry <code>'Action'</code>. Note that unlike the lambda we used to turn the lines in the file into movies, we have not declared a parameter name this time. We could have written</p>
<pre><code class="language-xtend">movies.filter[ movie | movie.categories.contains('Action') ].size
</code></pre>
<p>but since we left out the name and the vertical bar the variable is automatically named <code>it</code>. <code>it</code> is an <a href="203_xtend_expressions.html#implicit-variables">implicit variable</a>. It’s uses are similar to the implicit variable <code>this</code>. We can write either</p>
<pre><code class="language-xtend">movies.filter[ it.categories.contains('Action') ].size
</code></pre>
<p>or even more compact</p>
<pre><code class="language-xtend">movies.filter[ categories.contains('Action') ].size
</code></pre>
<p>Eventually we call <code>size</code> on the resulting iterable which is an extension method, too. It is defined in the utility class <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/IterableExtensions.java">IterableExtensions</a>.</p>
<h3 id="question2">Question 2 : What Is The Year The Best Movie From The 80’s Was Released?</h3>
<pre><code class="language-xtend">@Test def void yearOfBestMovieFrom80s() {
assertEquals(1989,
movies.filter[ (1980..1989).contains(year) ].sortBy[ rating ].last.year)
}
</code></pre>
<p>Here we <code>filter</code> for all movies whose year is included in the range from 1980 to 1989 (the 80’s). The <code>..</code> operator is again an extension defined in <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/IntegerExtensions.java">IntegerExtensions</a> and returns an instance of <a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/IntegerRange.java">IntegerRange</a>. Operator overloading is explained in <a href="203_xtend_expressions.html#operators">section</a>.</p>
<p>The resulting iterable is sorted (<a href="https://github.com/eclipse/xtext-lib/blob/master/org.eclipse.xtext.xbase.lib/src/org/eclipse/xtext/xbase/lib/IterableExtensions.java"><code>IterableExtensions.sortBy</code></a>) by the <code>rating</code> of the movies. Since it is sorted in ascending order, we take the last movie from the list and return its <code>year</code>.</p>
<p>We could have sorted descending and take the head of the list as well:</p>
<pre><code class="language-xtend">movies.filter[ (1980..1989).contains(year) ].sortBy[ -rating ].head.year
</code></pre>
<p>Another possible solution would be to reverse the order of the sorted list:</p>
<pre><code class="language-xtend">movies.filter[ (1980..1989).contains(year) ].sortBy[ rating ].reverseView.head.year
</code></pre>
<p>Note that first sorting and then taking the last or first is slightly more expensive than needed. We could have used the method <code>reduce</code> instead to find the best movie which would be more efficient. Maybe you want to try it on your own?</p>
<p>The calls to <code>movie.year</code> as well as <code>movie.categories</code> in the previous example in fact access the corresponding <a href="203_xtend_expressions.html#property-access">getter methods</a>.</p>
<h3 id="question3">Question 3 : What Is The Sum Of All Votes Of The Top Two Movies?</h3>
<pre><code class="language-xtend">@Test def void sumOfVotesOfTop2() {
val long sum = movies.sortBy[ -rating ].take(2).map[ numberOfVotes ].reduce[ a, b | a + b ]
assertEquals(47_229L, sum)
}
</code></pre>
<p>First the movies are sorted by rating, then we take the best two. Next the list of movies is turned into a list of their <code>numberOfVotes</code> using the <code>map</code> function. Now we have a <a href="http://docs.oracle.com/javase/8/docs/api/java/util/List.html"><code>List&lt;Long&gt;</code></a> which can be reduced to a single <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/Long.html">Long</a> by adding the values.</p>
<p>You could also use <code>reduce</code> instead of <code>map</code> and <code>reduce</code>. Do you know how?</p>
<hr />
<p><strong><a href="201_types.html">Next Chapter: Java Interoperability</a></strong></p>
</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>