Combine Xcore, Xtend, Ecore, and Maven

Lots of thanks to Christian for figuring this out together.

The complete example is available on github.

Also, we included all the files as Listings at the end of the article. They are heavily commented.

Objective

Inside an Eclipse plugin, we have EMF models defined in both Xcore and Ecore, using types from each other. Also, we have a Helper Xtend class that’s called from Xcore and uses types from the same model. We want to build the Eclipse Plugin with Maven. We also don’t want to commit any generated sources (from Xtend, Xcore, or Ecore).

Issue

Usually, we would use xtend-maven-plugin to build the Xtend classes, xtext-maven-plugin to build the Xcore model, and MWE2 to build the Ecore model.

However, we have a cyclic compile-time dependency between AllGreetings calling GreetingsHelper.compileAllGreetings() which receives a parameter of type AllGreetings.

Solution

Java (and Xtend) can solve such cycles in general, but only if they process all members at the same time. Thus, we need to make sure both Xtend and Xcore are generated within the same Maven plugin.

We didn’t find a way to include the regular Ecore generator in the same step, so we keep this in a separate MWE2-based Maven plugin.

For generating persons.ecore, we call an MWE2 workflow from Maven via exec-maven-plugin. The workflow itself gets a bit more complicated, as we use INamedElement from base.xcore as a supertype to IPerson inside persons.ecore. Thus, we need to ensure that base.xcore is loaded, available, and can be understood by the Ecore generator.

Afterwards, we use xtext-maven-plugin to generate both Xtend and the two Xcore models. To do this, we need to include all the required languages and dependencies into one single plugin in our maven pom.

Missing Bits

The workaround of using MWE2 to build Ecore models has (presumably) the side effect that we cannot refer to Ecore contents within Xtend. The respective parts are commented in GreetingsHelper.compileAllPersons() and drawn grey in the diagram above.

Remarks

  • We developed this using Eclipse Mars.2.
  • The example project should be free of errors and warnings, and builds in all of Eclipse, MWE2, and Maven.

    In Maven, you might see some warnings due to an Xtext bug. It should not have any negative impact.

  • When creating the Ecore file, make sure only to use built-in types (like EString) from http://www.eclipse.org/emf/2002/Ecore. They may be listed several times.
  • In our genmodel file, Eclipse tends to replace this way of referring to platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore by something like ../../org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore. This would lead to ConcurrentModificationException in xtext-maven-plugin or MWE2, or “The referenced packages ”{0}” and ”{1}” have the same namespace URI and cannot be checked at the same time.” when opening the GenModel editor.

    In this case, open the genmodel file with a text editor and use the platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore form in the genmodel:GenModel#usedGenPackages attribute. Sadly, this needs to be fixed every time the Genmodel Editor saves the file.

  • The Xcore builder inside Eclipse automatically picks up the latest EMF complianceLevel, leading to Java generics support in generated code. The maven plugin does not use the latest complianceLevel, thus we need to set it explicitly.
  • The modelDirectory setting in both Xcore and Ecore seem to be highly sensitive to leading or trailing slashes. We found it safest not to have them at all.
  • Using all the involved generators (MWE2, Xcore, Xtend, …) requires quite a few dependencies for our plugin. As a neat trick, we can define them in build.properties rather than MANIFEST.MF. This way, they are available at build time, but do not clog our run-time classpath. As we can see on the right, they are also listed separately in the Eclipse dependency editor.
  • maven-clean-plugin seems to be quite sensitive how its filesets are described. Even when disregarding the .dummy.txt entries in our pom, the *-gen directories were only cleaned if we listed them in separate fileset entries.
  • The workflow should reside within an Eclipse source folder. To separate it from real sources, we created a new source folder named workflow.

Listings

Project Layout

GreetingsHelper.xtend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package de.nikostotz.xtendxcoremaven.greetings.helper

import de.nikostotz.xtendxcoremaven.greetings.AllGreetings

class GreetingsHelper {
// This method is called from greetings.xcore and has AllGreetings as parameter type,
// thus creating a dependency circle
def static String compileAllGreetings(AllGreetings it) {
var totalSize = 0
// We access the AllGreetings.getGreetings() method in two different ways
// (for-each-loop and map) to demonstrate different error messages if we omit
// 'complianceLevel="8.0"' in greetings.xcore
for (greeting : it.getGreetings()) {
totalSize = totalSize + greeting.getMessage().length
}

'''
Greetings:
«it.getGreetings().map[greeting | greeting.getMessage()].join(", ")»
'
''
}

// This does not work currently with Maven. I didn't figure out why yet, but have some clues:
// AllGreetings.getPersons() refers to type IPerson, which is defined in Ecore.
// This might not be generated yet when the Xtend generator runs in Maven.
// def static String compileAllPersons(AllGreetings it) {
// '''
// Hello Humans:
// «it.getPersons().map[person | person.describeMyself()].join(", ")»
// '''
// }
}

base.xcore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@GenModel(
// This sets the target directory where to put the generated classes.
// Make sure NOT to start or end with a slash!
// Doing so would lead to issues either with Eclipse builder, MWE2 launch, or Maven
modelDirectory="de.nikostotz.xtendxcoremaven/xcore-gen",

// required to fix an issue with xcore (see https://www.eclipse.org/forums/index.php/t/367588/)
operationReflection="false"
)
package de.nikostotz.xtendxcoremaven.base

// This enables usage of the @GenModel annotation above. The annotation would work without
// this line in Eclipse, but Maven would fail.
// (WorkflowInterruptedException: Validation problems: GenModel cannot be resolved.)
annotation "http://www.eclipse.org/emf/2002/GenModel" as GenModel

interface INamedElement {
String name
}

persons

persons.ecore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
< ?xml version="1.0" encoding="UTF-8"?>
<ecore:epackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="persons" nsURI="de.nikostotz.xtendxcoremaven.persons" nsPrefix="persons">
  <eclassifiers xsi:type="ecore:EClass" name="IPerson" abstract="true" interface="true"
     eSuperTypes="base.xcore#/EPackage/INamedElement">
    <eoperations name="describeMyself" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"></eoperations>
  </eclassifiers>
  <eclassifiers xsi:type="ecore:EClass" name="Human" eSuperTypes="#//IPerson">
    <estructuralfeatures xsi:type="ecore:EAttribute" name="knownHumanLanguages" upperBound="-1"
       eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"></estructuralfeatures>
  </eclassifiers>
  <eclassifiers xsi:type="ecore:EClass" name="Developer" eSuperTypes="#//IPerson">
    <estructuralfeatures xsi:type="ecore:EAttribute" name="knownProgrammingLanguages"
       upperBound="-1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"></estructuralfeatures>
  </eclassifiers>
</ecore:epackage>

persons.genmodel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
< ?xml version="1.0" encoding="UTF-8"?>
<genmodel:genmodel xmi:version="2.0"
    xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
    xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel"
    modelDirectory="de.nikostotz.xtendxcoremaven/emf-gen" modelName="Persons"
    importerID="org.eclipse.emf.importer.ecore" complianceLevel="8.0"
    copyrightFields="false" importOrganizing="true"
    usedGenPackages="base.xcore#/1/base platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore">
    <!-- Eclipse tends to replace this way of referring to the Ecore genmodel
        by something like "../../org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore".
        This would lead to ConcurrentModificationException in xtext-maven-plugin
        or MWE2, or "The referenced packages ''{0}'' and ''{1}'' have the same namespace
        URI and cannot be checked at the same time." when opening the GenModel editor. -->

    <foreignmodel>persons.ecore</foreignmodel>
    <genpackages prefix="Persons" basePackage="de.nikostotz.xtendxcoremaven.persons"
        disposableProviderFactory="true" ecorePackage="persons.ecore#/">
        <genclasses image="false" ecoreClass="persons.ecore#//IPerson">
            <genoperations ecoreOperation="persons.ecore#//IPerson/describeMyself"></genoperations>
        </genclasses>
        <genclasses ecoreClass="persons.ecore#//Human">
            <genfeatures createChild="false"
                ecoreFeature="ecore:EAttribute persons.ecore#//Human/knownHumanLanguages"></genfeatures>
        </genclasses>
        <genclasses ecoreClass="persons.ecore#//Developer">
            <genfeatures createChild="false"
                ecoreFeature="ecore:EAttribute persons.ecore#//Developer/knownProgrammingLanguages"></genfeatures>
        </genclasses>
    </genpackages>
</genmodel:genmodel>

greetings.xcore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@GenModel(
modelDirectory="de.nikostotz.xtendxcoremaven/xcore-gen",
operationReflection="false",

// This enables Java generics support in EMF (starting with version 6.0).
// If omitted, we'd get a "Validation Problem: The method or field message is undefined" in Maven
// because AllGreetings.getGreetings() would return an EList instead of an EList, thus
// we cannot know about the types of the list elements and whether they have a 'message' property.
// In other cases, this leads to error messages like "Cannot cast Object to Greeting".
complianceLevel="8.0"
)

package de.nikostotz.xtendxcoremaven.greetings

// referring to Ecore
import de.nikostotz.xtendxcoremaven.persons.persons.IPerson

// referring to Xtend
import de.nikostotz.xtendxcoremaven.greetings.helper.GreetingsHelper

annotation "http://www.eclipse.org/emf/2002/GenModel" as GenModel

class AllGreetings {
contains IPerson[] persons
contains Greeting[] greetings

op String compileAllGreetings() {
// calling Xtend inside Xcore
GreetingsHelper.compileAllGreetings(this)
}
}

class Greeting {
String message
refers IPerson person
}

MANIFEST.MF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: de.nikostotz.xtendxcoremaven;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-ClassPath: .
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Bundle-Activator: de.nikostotz.xtendxcoremaven.XtendXcoreMavenActivator
Require-Bundle: org.eclipse.core.runtime,
org.eclipse.emf.ecore;visibility:=reexport,
org.eclipse.xtext.xbase.lib,
org.eclipse.emf.ecore.xcore.lib
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: de.nikostotz.xtendxcoremaven.greetings.impl,
de.nikostotz.xtendxcoremaven.greetings.util,
de.nikostotz.xtendxcoremaven.base,
de.nikostotz.xtendxcoremaven.base.impl,
de.nikostotz.xtendxcoremaven.base.util,
de.nikostotz.xtendxcoremaven.persons.persons,
de.nikostotz.xtendxcoremaven.persons.persons.impl,
de.nikostotz.xtendxcoremaven.persons.persons.util
Bundle-ActivationPolicy: lazy

build.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#

bin.includes = .,\
model/,\
META-INF/,\
plugin.xml,\
plugin.properties
jars.compile.order = .
source.. = src/,\
emf-gen/,\
xcore-gen/,\
xtend-gen/
src.excludes = workflow/
output.. = bin/
# These work the same as entries in MANIFEST.MF#Require-Bundle, but only at build time, not run-time
additional.bundles = org.eclipse.emf.mwe2.launch,\
org.apache.log4j,\
org.apache.commons.logging,\
org.eclipse.xtext.ecore,\
org.eclipse.emf.codegen.ecore.xtext,\
org.eclipse.emf.ecore.xcore,\
org.eclipse.xtend.core,\
org.eclipse.emf.codegen.ecore,\
org.eclipse.emf.mwe.core,\
org.eclipse.emf.mwe.utils,\
org.eclipse.emf.mwe2.lib

generateGenModel.mwe2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module GenerateGenModel

var projectName = "de.nikostotz.xtendxcoremaven"
var rootPath = ".."

Workflow {
// This configures the supported model types for EcoreGenerator.
// Order is important.
// Should be the same list as in Reader below.
bean = org.eclipse.emf.ecore.xcore.XcoreStandaloneSetup {}
bean = org.eclipse.xtend.core.XtendStandaloneSetup {}
bean = org.eclipse.xtext.ecore.EcoreSupport {}
bean = org.eclipse.emf.codegen.ecore.xtext.GenModelSupport {}

bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
// Required for finding the platform contents (Ecore.ecore, Ecore.genmodel, ...) under all circumstances
platformUri = "${rootPath}"

// Required for finding above mentioned models inside their Eclipse plugins
scanClassPath = true
}

// As persons.ecore refers to a type inside base.xcore (IPerson extends INamedElement),
// we need to load base.xcore before we can generate persons.ecore.
component = org.eclipse.xtext.mwe.Reader {
// This configures the supported model types for this Reader.
// Order is important.
// Should be the same list as beans above.
register = org.eclipse.emf.ecore.xcore.XcoreStandaloneSetup {}
register = org.eclipse.xtend.core.XtendStandaloneSetup {}
register = org.eclipse.xtext.ecore.EcoreSupport {}
register = org.eclipse.emf.codegen.ecore.xtext.GenModelSupport {}

// This asks the Reader to read all models it understands from these directories (and sub-directories).
path = "model"
path = "src"

// Put the models inside a ResourceSet that's accessible by the EcoreGenerator.
loadFromResourceSet = {}
}

// Generate persons.ecore (via persons.genmodel).
component = org.eclipse.emf.mwe2.ecore.EcoreGenerator {
genModel = "platform:/resource/${projectName}/model/persons.genmodel"
srcPath = "platform:/resource/${projectName}/emf-gen"
}
}

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelversion>4.0.0</modelversion>
    <groupid>de.nikostotz.xtendxcoremaven</groupid>
    <version>1.0.0-SNAPSHOT</version>
    <artifactid>de.nikostotz.xtendxcoremaven</artifactid>

    <properties>
        <project .build.sourceEncoding>UTF-8</project>

        <!-- Java version, will be honored by Xcore / Xtend -->
        <maven .compiler.source>1.8</maven>
        <maven .compiler.target>1.8</maven>

        <!-- Xtend / Xcore -->
        <core -resources-version>3.7.100</core>
        <eclipse -text-version>3.5.101</eclipse>
        <emf -version>2.12.0</emf>
        <emf -common-version>2.12.0</emf>
        <emf -codegen-version>2.11.0</emf>
        <xtext -version>2.10.0</xtext>
        <ecore -xtext-version>1.2.0</ecore>
        <ecore -xcore-version>1.3.1</ecore>
        <ecore -xcore-lib-version>1.1.100</ecore>
        <emf -mwe2-launch-version>2.8.3</emf>
    </properties>

    <dependencies>
        <dependency>
            <groupid>org.eclipse.emf</groupid>
            <artifactid>org.eclipse.emf.common</artifactid>
            <version>${emf-common-version}</version>
        </dependency>
        <dependency>
            <groupid>org.eclipse.emf</groupid>
            <artifactid>org.eclipse.emf.ecore</artifactid>
            <version>${emf-version}</version>
        </dependency>
        <dependency>
            <groupid>org.eclipse.emf</groupid>
            <artifactid>org.eclipse.emf.ecore.xcore.lib</artifactid>
            <version>${ecore-xcore-lib-version}</version>
        </dependency>
        <dependency>
            <groupid>org.eclipse.xtext</groupid>
            <artifactid>org.eclipse.xtext.xbase.lib</artifactid>
            <version>${xtext-version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupid>org.apache.maven.plugins</groupid>
                <artifactid>maven-compiler-plugin</artifactid>
                <version>3.3</version>
            </plugin>

            <plugin>
                <artifactid>maven-clean-plugin</artifactid>
                <version>2.6.1</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <excludes>
                                <exclude>.dummy.txt</exclude>
                            </excludes>
                            <directory>emf-gen</directory>
                        </fileset>
                        <fileset>
                            <excludes>
                                <exclude>.dummy.txt</exclude>
                            </excludes>
                            <directory>xtend-gen</directory>
                        </fileset>
                        <fileset>
                            <excludes>
                                <exclude>.dummy.txt</exclude>
                            </excludes>
                            <directory>xcore-gen</directory>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>

            <!-- Adds the generated sources to the compiler input -->
            <plugin>
                <groupid>org.codehaus.mojo</groupid>
                <artifactid>build-helper-maven-plugin</artifactid>
                <version>1.9.1</version>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <!-- This should be in sync with xtext-maven-plugin//source-roots,
                                except for /model directory -->
                            <sources>
                                <source />${basedir}/emf-gen
                                <source />${basedir}/xcore-gen
                                <source />${basedir}/xtend-gen
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Generates the Ecore model via MWE2 -->
            <plugin>
                <groupid>org.codehaus.mojo</groupid>
                <artifactid>exec-maven-plugin</artifactid>
                <version>1.4.0</version>
                <executions>
                    <execution>
                        <id>mwe2Launcher</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainclass>org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher</mainclass>
                    <arguments>
                        <argument>${project.basedir}/workflow/generateGenModel.mwe2</argument>
                        <argument>-p</argument>
                        <argument>rootPath=${project.basedir}/..</argument>
                    </arguments>
                    <classpathscope>compile</classpathscope>
                    <includeplugindependencies>true</includeplugindependencies>
                    <cleanupdaemonthreads>false</cleanupdaemonthreads><!-- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=475098#c3 -->
                </configuration>
                <dependencies>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.mwe2.launch</artifactid>
                        <version>${emf-mwe2-launch-version}</version>
                    </dependency>

                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.xtext</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.text</groupid>
                        <artifactid>org.eclipse.text</artifactid>
                        <version>${eclipse-text-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.core</groupid>
                        <artifactid>org.eclipse.core.resources</artifactid>
                        <version>${core-resources-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtend</groupid>
                        <artifactid>org.eclipse.xtend.core</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.ecore</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.xbase</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen.ecore.xtext</artifactid>
                        <version>${ecore-xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.common</artifactid>
                        <version>${emf-common-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore</artifactid>
                        <version>${emf-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xmi</artifactid>
                        <version>${emf-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen</artifactid>
                        <version>${emf-codegen-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen.ecore</artifactid>
                        <version>${emf-codegen-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xcore</artifactid>
                        <version>${ecore-xcore-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xcore.lib</artifactid>
                        <version>${ecore-xcore-lib-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.text</groupid>
                        <artifactid>org.eclipse.text</artifactid>
                        <version>${eclipse-text-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.core</groupid>
                        <artifactid>org.eclipse.core.resources</artifactid>
                        <version>${core-resources-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtend</groupid>
                        <artifactid>org.eclipse.xtend.core</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.ecore</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.xbase</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen.ecore.xtext</artifactid>
                        <version>${ecore-xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.common</artifactid>
                        <version>${emf-common-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore</artifactid>
                        <version>${emf-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xmi</artifactid>
                        <version>${emf-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen</artifactid>
                        <version>${emf-codegen-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen.ecore</artifactid>
                        <version>${emf-codegen-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xcore</artifactid>
                        <version>${ecore-xcore-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xcore.lib</artifactid>
                        <version>${ecore-xcore-lib-version}</version>
                    </dependency>
                </dependencies>
            </plugin>

            <!-- Generates the Xtend and Xcore models -->
            <plugin>
                <groupid>org.eclipse.xtext</groupid>
                <artifactid>xtext-maven-plugin</artifactid>
                <version>${xtext-version}</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <languages>
                        <language>
                            <setup>org.eclipse.xtext.ecore.EcoreSupport</setup>
                        </language>
                        <language>
                            <setup>org.eclipse.emf.codegen.ecore.xtext.GenModelSupport</setup>
                        </language>
                        <language>
                            <setup>org.eclipse.xtend.core.XtendStandaloneSetup</setup>
                            <outputconfigurations>
                                <outputconfiguration>
                                    <outputdirectory>${project.basedir}/xtend-gen</outputdirectory>
                                </outputconfiguration>
                            </outputconfigurations>
                        </language>
                        <language>
                            <setup>org.eclipse.emf.ecore.xcore.XcoreStandaloneSetup</setup>
                            <outputconfigurations>
                                <outputconfiguration>
                                    <outputdirectory>${project.basedir}/xcore-gen</outputdirectory>
                                </outputconfiguration>
                            </outputconfigurations>

                        </language>
                    </languages>
                    <!-- This should be in sync with build-helper-maven-plugin//sources,
                        except for /model directory -->
                    <sourceroots>
                        <root>${basedir}/src</root>
                        <root>${basedir}/emf-gen</root>
                        <!-- Note that we include the /model path here although it's not part
                            of the source directories in Eclipse or Maven -->
                        <root>${basedir}/model</root>
                    </sourceroots>
                    <!-- This does not work currently, as we can see by the missing lambda
                        in generated code for GreetingsHelper.compileAllGreetings(). It does work,
                        however, for xtend-maven-plugin. (see https://github.com/eclipse/xtext-maven/issues/11)-->
                    <javasourceversion>1.8</javasourceversion>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupid>org.eclipse.text</groupid>
                        <artifactid>org.eclipse.text</artifactid>
                        <version>${eclipse-text-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.core</groupid>
                        <artifactid>org.eclipse.core.resources</artifactid>
                        <version>${core-resources-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtend</groupid>
                        <artifactid>org.eclipse.xtend.core</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.ecore</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.xtext</groupid>
                        <artifactid>org.eclipse.xtext.xbase</artifactid>
                        <version>${xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen.ecore.xtext</artifactid>
                        <version>${ecore-xtext-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.common</artifactid>
                        <version>${emf-common-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore</artifactid>
                        <version>${emf-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xmi</artifactid>
                        <version>${emf-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen</artifactid>
                        <version>${emf-codegen-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.codegen.ecore</artifactid>
                        <version>${emf-codegen-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xcore</artifactid>
                        <version>${ecore-xcore-version}</version>
                    </dependency>
                    <dependency>
                        <groupid>org.eclipse.emf</groupid>
                        <artifactid>org.eclipse.emf.ecore.xcore.lib</artifactid>
                        <version>${ecore-xcore-lib-version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

.gitignore

1
2
3
4
5
bin/*
target
emf-gen/*
xcore-gen/*
xtend-gen/*

DemoCamp Mars in Stuttgart: Great People, Talks, and Food

SpeakersWe had a nice DemoCamp in Stuttgart for Eclipse Mars Release train. About 50 people had a great time alongside great food.

The full agenda, including links to all slides, can be found in the Eclipse Wiki.

MatthiasThe first talk by Matthias Zimmermann showed the Business Application Framework Scout, especially the new features of Mars and the upcoming next release.

MartinAfterwards, Martin Schreiber presented their experience with Tycho and some practical solutions.

JinyingJinying Yu gave an overview of the CloudScale project. It analyzes, estimates and simulates the scalability of a software system, especially for moving it to a cloud service.

MarcoAfter some refreshments and discussions during the break, Marco Eilers impressed with an Xtend interpreter, including tracing between the input model of a transformation and the resulting model or text – in both directions!

MiroThe next talk by Miro Spönemann showed the current state of Xtext on the Web and in IntelliJ. As Miro is the lead developer of the Web variant, he could easily answer all questions in-depth.

HaraldFinally, Harald Mackamul gave an overview of the APP4MC project. They extend the findings of Amalthea project to multi- and many-core systems.

We finished the DemoCamp with more discussions, food, and beer.

I’d like to thank all the great speakers and the attendees for making this DemoCamp fun as ever.

Eclipse DemoCamp 2015 “Mars” at University Stuttgart Vaihingen on July 1st, 17:45 hrs

Right on the heels of this year’s Eclipse “Mars” release, we will again run a DemoCamp in Stuttgart. It will take place

on

Wednesday, July 1st, 2015
17:45 hrs

at

Stuttgart University in Stuttgart-Vaihingen
Informatik Building
Universitätsstraße 38
70569 Stuttgart
Room Hörsaal 38.03

Details around the DemoCamp can be found at
https://wiki.eclipse.org/Eclipse_DemoCamps_Mars_2015/Stuttgart

As usual, admission is free, but please register at the above URL to help us plan the event.

Demos will include

  • Eclipse Scout: What’s new with Mars
  • Maven Tycho in practice
  • Interpreting Xtend: How to do it and why
  • Xtext for other platforms – Integration in IntelliJ IDEA and web applications
  • CloudScale Method for Analysing Scalability, Elasticity and Effieciency Engineering
  • APP4MC – a new Eclipse project proposal

Meet the IoT, Dependency Management, the local Eclipse Community and more at the DemoCamp Luna in Stuttgart!

DemoCampStuttgartSmall
On July 2nd, the Eclipse DemoCamp for the Luna release train will take place at the Stuttgart University, Vaihingen campus. You will see demos on several Internet-of-Things projects, including HTML5 device UIs build with Franca, using MQTT and Paho to connect Webpages, and an extensible C for developing embedded software. In addition, there will be demos on modern software translation with Jabylon, Eclipse usability, and package dependency diagnosis. There will be plenty of time, food and drinks to get in touch with speakers and members of the local Eclipse Community in general.

Detailed information can be found at: https://wiki.eclipse.org/Eclipse_DemoCamps_Luna_2014/Stuttgart

Eclipse Demo Camp Kepler Stuttgart Retrospective

Welcome!

Welcome!

We had a great DemoCamp in Stuttgart. About sixty people crowded the room and welcomed every beverage the hotel could come up with.

The room just sufficed

The room just sufficed

Please refer to the Eclipse Wiki for abstracts, slides and additional links for all demos.

Andreas Sewe shows Hippie Code Completion

Andreas Sewe shows Hippie Code Completion

Andreas Sewe demonstrated Code Recommenders, including their newest feature called Hippie Code Completion. He explained the various ways Eclipse supports the developer with code proposals. After a quick recap of already available Code Recommenders features we saw how Hippie Code Completion collects code patterns live during input and distills proposals on the spot.

Jan Stamer on Eclipse Gemini

Jan Stamer on Eclipse Gemini

Jan Stamer gave an overview of Eclipse Gemini, the Eclipse implementation of OSGi Enterprise specification. He compared OSGi Enterprise to Java EE and showed implementation examples for database access, JPA, and dependency injection.

Dominik Obermaier connects anything via Eclipse Paho

Dominik Obermaier connects anything via Eclipse Paho

Dominik Obermaier swapped his slot with the Contracts for Java talk. He demonstrated Eclipse Paho, the umbrella project for machine-to-machine (M2M) protocols. He argued for MQTT as lightweight, low-bandwith, and fault-tolerant protocol suited for small systems. He implemented live a publisher and subscriber of MQTT messages within a few lines of code.

Sebastian Zarnekow in passion about Xtend

Sebastian Zarnekow in passion about Xtend

Sebastian Zarnekow demonstrated Xtend, aka Java on steroids. He began by writing a simple list processing example in Java style. Step by step, he refactored the code using Xtend features, thus becoming far more concise and readable. Finally, he gave a crash course on one of Xtend’s newest features, Active Annotations: Sebastian live developed an Annotation to enable logging on any class.

I had been warned that once we opened the windows, the air conditioning would shut down. After stepping outside and back into the room, I decided in favor of at least some oxygen, taking the risk on the air conditioning. Unfortunately, the warning came true, and we had an increasingly hot second term.

Andreas Graf shows a dozen Eclipse projects combined to allow Function Oriented Development

Andreas Graf shows a dozen Eclipse projects combined to allow Function Oriented Development

Andreas Graf demonstrated the current state of an automotive research project. As an example, he picked the seemingly simple car function of start-stop system to save fuel at a traffic light. He explaind how this function concerns lots of different car subsystems. Most likely, they are provided by different external suppliers and managed by different internal departments. The research project IMES combines lots of Open Source and some commercial Eclipse based projects to tackle this complexity. Andreas showed this combination to enable advanced concepts like traceability, feature and variant modelling, and impact analysis.

Hagen Buchwald explains Stefan Schürle's demonstration of Contracts for Java

Hagen Buchwald explains Stefan Schürle’s demonstration of Contracts for Java

Stefan Schürle stood in for Florian Meyerer to demonstrate Contracts for Java together with Hagen Buchwald. Hagen started by explaining the basic ideas of contracts. Every step Hagen explained, Stefan implemented within the C4J framework. The demo application provided a student’s bank account, guarded by contracts to disallow debit.

Timo Kehrer explains SiLift

Timo Kehrer explains SiLift

Timo Kehrer explained SiLift, the Eclipse implementation of his concept how to compare/merge models on a meaningful level. He focused on edit operations as basic blocks rather than model modifications. Timo illustrated how SiLift can detect the changes of some edit operations automatically; more complex examples need to be described by the developer. He spent quite some time on putting the concept into usable tools.

Sorry Ed, I forgot to take a picture (-:

Sorry Ed, I forgot to take a picture (-:

Ed Merks concluded the sessions with his “first non-modelling talk in the last ten years”, as he put it. He told about his experience on improving Java performance in two customer projects. He learned not to trust anything and to be paranoid about common knowledge, each and any measurement, experts, and even oneself. He showed how to establish some baseline by looking for the quickest action one environment can measure (limited by timer resolution) and the fastest operations a JVM is capable. On this baseline, he wrote a simple framework to work on more complex cases. The extended slides offer much more detail than Ed was able to squeeze into the 20 minute slot.

After more than three hours of new and exciting ideas, combined with a jungle-like climate in the room, both the speakers and the audience rushed for the buffet and more cool drinks. We had a relaxed evening with lots of interesting discussions, leaving the premises just about midnight.

My special thanks go to all the speakers, arriving from all over Germany on their own expenses to show us their Cool Stuff™.

I appreciate all our guests participating in the DemoCamp and the survey we sent. We got more than 50 per cent response rate — thank you! The survey resulted in a favor for short slots in English. The demo vs. presentation question came out more closely, with advantage for a mixed program.

Eclipse DemoCamp Kepler in Stuttgart on 2013-07-17 17:00

At Wednesday, July 17th, itemis sponsors this year’s Eclipse DemoCamp in Stuttgart. All details can be found at the Eclipse Wiki. We’ll start at 5 pm at the Ibis Styles Hotel in Bad Cannstatt.

We could compile an agenda from a wide variety of topics, reaching from cloud-supported code recommendations and enterprise OSGi adaption through programming language improvements using contracts and more concise syntax, function oriented development and machine-to-machine communication to model comparison and Java performance tuning tips. The Eclipse Wiki contains summaries for each demo.

There will be plenty of time to talk to the presenters and other attendees. itemis provides snacks and beverages so we can have an interesting and pleasant evening.

I’m looking forward to meet you in three weeks! Please register so we can spread last-minute updates as necessary.

Merge Text Files with Ant

Apache Ant has lots of useful built-in tasks. However, a task for merging text files is not among them. Fortunately, we can combine some built-in tasks to achieve this goal.

Below, you’ll find the macro along a simple demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<project default="merge-demo">

<target name="merge-demo">
    <merge tofile="merged.txt">
        <sourcefiles>
            <fileset dir="inputfiles"></fileset>
        </sourcefiles>
    </merge>
</target>

    <!-- = = = = = = = = = = = = = = = = =
     macrodef: merge          
    = = = = = = = = = = = = = = = = = -->
    <macrodef name="merge">
        <attribute name="tofile"></attribute>
        <element name="sourcefiles"></element>
       
        <sequential>
            <tempfile property="temp.file" prefix="ant.merge." deleteonexit="true"></tempfile>
           
            <concat destfile="${temp.file}" fixlastline="true">
                <sourcefiles></sourcefiles>
            </concat>
           
            <copy file="${temp.file}" tofile="@{tofile}">
                <filterchain>
                    <tokenfilter>
                        <linetokenizer></linetokenizer>
                    </tokenfilter>
                    <sortfilter></sortfilter>
                    <uniqfilter></uniqfilter>
                </filterchain>
            </copy>
        </sequential>
    </macrodef>
</project>

Remarks and Caveats

  • Contents of all files are sorted by line.
  • Duplicate lines are removed.

Example

The example runs on the following directory:
ant-merge-dirstructure

with the following file contents:

file1.txt

# random prefix	file	line	remark
7609	file1.txt	1
24307	file1.txt	2
31889	file1.txt	3	no newline afterwards

file2.txt

# random prefix	file	line	remark
5808	file2.txt	1
32706	file2.txt	2	preceding empty line

7848	file2.txt	3	following empty line
17727	file2.txt	4
3071	file2.txt	5	one newline afterwards

file3.txt

# random prefix	file	line	remark
16411	file3.txt	1
10605	file3.txt	2	followed by several newlines



Once we run the Ant script, the resulting file will look as follows:
merged.txt


# random prefix	file	line	remark
10605	file3.txt	2	followed by several newlines
16411	file3.txt	1
17727	file2.txt	4
24307	file1.txt	2
3071	file2.txt	5	one newline afterwards
31889	file1.txt	3	no newline afterwards
32706	file2.txt	2	preceding empty line
5808	file2.txt	1
7609	file1.txt	1
7848	file2.txt	3	following empty line

Eclipse Juno DemoCamp Stuttgart 2012

itemis sponsored the Eclipse DemoCamp Stuttgart yesterday. About 60 participants saw impressive demonstrations:

  • Frank Gerhardt started with a demonstration of GEF running in a browser on both PCs and mobile devices. He emphasized the few changes required to GEF code for using it in the browser. Once loaded, all features known from GEF (shape modification, undo/redo, grid, snap to objects, …) run entirely without server interaction. The interaction is adjusted to mobile devices: Click areas are enlarged, and the area below your fingertip is shown in a separate “magnifiying glass” to enable precise modifications.
  • Jochen Krause showed a somewhat opposite approach to mobile apps. RAP mobile provides a unified development environment (including all Eclipse tools magic, debugging and live deployment) for different target platforms. The applications are compiled to native code for each platform. Data processing is server based, with both its advantages and drawbacks: You don’t need to worry about lost data on stolen devices, but you need constant connectivity.
  • Oliver Böhm provided an overview on ten years of PatternTesting. PatternTesting aims at assuring architectural decisions by using aspect-oriented code instrumentation.
  • Mark Brörkens introduced RMF, the Eclipse implementation of OMG ReqIF (Requirements Interchange Format). RMF will become the solid foundation of requirement modeling tools in the Eclipse ecosphere, much the same way Eclipse UML became the de-facto standard implementation.
  • Axel Terfloth demonstrated the remarkable state machine modeling YAKINDU Statechart Tools. They mix the best of graphical and textual modeling and include simulation and debugging capabilities. Additionally, it can attach to domain specific models, shown with a Flash application.
  • Ed Merks solemnized the grand finale with Xcore, an textual syntax for describing ecore models. It supports all ecore features, and then some: Operation implementations inside the model (without quirky annotations), documentation inside ecore models (like Javadoc), live metamodel update inside the reflective editor, and refactoring support down to user-written code.

Before, during and after the demos we had interesting discussions, lots of food and chilled beer.

Parallel Type Trees: Generic Attribute vs. Generic Accessor Method

When working with ecore models, we often encounter super/subtype relations on both the container and the member side. We want to easily access both the generic and the specific container members. Intuitively, we used different approaches to fulfill this requirement. This article explores some implementation possibilities and examines their advantages, drawbacks and performance implications.

Scenario

As example, we’ll use an AbstractLibrary containing many AbstractEntrys. We have a specialized BookLibrary containing Books and a specialized MovieLibrary containing Movies and its subtypes SciFis and Comedys. Both AbstractLibrary and AbstractEntry are considered generic, while all other types are considered specific.

Situation

Typical tasks when working with models include verification and generation. We might want to assure every library has at least some content, or generate an XML descriptor for any entry in any library. In both cases, we’re not interested in the specific type of library or entry. At the same time, we might want to check all books to have an author, or generate different descriptors for science fiction movies and comedies.

Therefore, we would need a list of generic entries in any library, and specific typed lists it specific libraries. Unfortunately, ecore prohibits overriding attributes. This limitation is not that important for single references, as we can easily define getter/setter to cast the return/parameter type as required. It gets much more complicated on multi-valued references.

Requirements

On multi-valued references (aka lists), we want to

  • get the members of the list,
  • add single values, and
  • addAll multiple values at once.

for both

  • generic and
  • specific

types, including type-safety checks at compile-time. We also want to avoid type casts in the code using our model.

Implementation approaches

Luckily, another trait of ecore makes up for the lack of reference overriding: All member access happens through getter/setter methods (as required for JavaBeans). We can leverage this trait to implement an API that feels almost like overriding generic reference types. The basic idea: We replace the missing generic or specific getter by an operation, exposing the same signature as we’d get by the emulated reference. There are two ways to go: Adding generic accessor methods (i. e. holding typed references in the specific containers) or using a generic reference (i. e. adding specific accessor methods to the specific containers).

Generic Accessor Methods

Model

BookLibrary holds a 0..* reference to Book (MovieLibrary accordingly). We want to have an additional method getEntries() for accessing generic entries, so we add this method to AbstractLibrary. In order to avoid superfluous type casts, we need to use a generic type wildcard with upper bound as return type. Entering such a type in the ecore tree editor is quite tricky:

  1. Open the ecore file.
  2. Make sure menu entry Sample Ecore Editor | Show Generics is checked.
  3. If the generic method does not exist yet, create it by right-clicking on the EClass, selecting New Child | EOperation.
  4. Open the Properties View for the generic method.
  5. Make sure the generic method has an appropriate Name (“getEntries” in our case).
  6. If the generic method already has a return type (EType in Properties View), remove it by selecting the empty first row in the drop-down list. Otherwise the context menu entry in the next step will be greyed out. Check also the lowerBound to be 0 and the upperBound to be 1.
  7. Right-click on the generic method in the editor tree, select New Child | EGeneric Return Type.
  8. In the Properties View of our newly added EGeneric Return Type, select EEList<E> for EClassifier.
  9. The EGeneric Return Type in the editor tree now has a new child. Right-click this child, select Add Child | EGeneric Upper Bound Type.
  10. The formerly unspecified generic type in the tree editor changes from <?> to <? extends ?>. In the Properties View of the newly added EGeneric Upper Bound Type, set EClassifier to the generic type (AbstractEntry in our case).

Finally, we should arrive at the following picture:

Implementation

We’ll implement the method in both BookLibraryImpl and MovieLibraryImpl rather than AbstractLibraryImpl. The code snippets below contain lots of code created by EcoreGen, we add only our implementation of getEntries():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class genericAccessorMethods.BookLibraryImpl extends AbstractLibraryImpl implements BookLibrary {
    @Override
    public EList<? extends AbstractEntry> getEntries() {
        return getBooks();
    }

    // ...

    /**
     * The cached value of the '{@link #getBooks() <em>Books</em>}' containment reference list.
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     * @see #getBooks()
     * @generated
     * @ordered
     */

    protected EList<Book> books;

    // ...

    /**
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     * @generated
     */

    public EList<Book> getBooks() {
        if (books == null) {
            books = new EObjectContainmentEList<Book>(Book.class, this, GenericAccessorMethodsPackage.BOOK_LIBRARY__BOOKS);
        }
        return books;
    }
} // BookLibraryImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class genericAccessorMethods.MovieLibraryImpl extends AbstractLibraryImpl implements MovieLibrary {
    @Override
    public EList<? extends AbstractEntry> getEntries() {
        return getMovies();
    }

    // ...

    /**
     * The cached value of the '{@link #getMovies() <em>Movies</em>}' containment reference list.
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     * @see #getMovies()
     * @generated
     * @ordered
     */

    protected EList<Movie> movies;

    // ...

    /**
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     * @generated
     */

    public EList<Movie> getMovies() {
        if (movies == null) {
            movies = new EObjectContainmentEList<Movie>(Movie.class, this, GenericAccessorMethodsPackage.MOVIE_LIBRARY__MOVIES);
        }
        return movies;
    }
} // MovieLibraryImpl

Generic Attributes

Model

AbstractLibrary holds a 0..* reference to AbstractEntry. We add a method getBooks() with return EType Book, Lower Bound 0 and Upper Bound -1 to BookLibrary (MovieLibrary accordingly). We don’t need any generics in this case.

Implementation

The original implementation approach (dubbed genericAttributes) included a serious amount of dynamic casting. This proved to be problematic, especially performance-wise. A second implementation (genericUnitypeAttributes) performed better.

Generic Attributes

We wrap the generic entries into an UnmodifiableEList in order to keep the interface contract of java.lang.List (add() must fail if nothing was added). The second method included below, getEntries(), is a copy of the code generated into AbstractLibraryImpl, with the type (marked in line 19) set to our specific type. This prevents adding illegal types to the collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class genericAttributes.BookLibraryImpl extends AbstractLibraryImpl implements BookLibrary {
    /**
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     *
     * @generated NOT
     */
    public EList<Book> getBooks() {
        return new UnmodifiableEList<Book>(getEntries().size(), getEntries()
                .toArray());
    }

    /**
     * @generated NOT
     */
    @Override
    public EList<AbstractEntry> getEntries() {
        if (entries == null) {
            entries = new EObjectContainmentEList<AbstractEntry>(
                Book.class,
                this,
                GenericAttributesPackage.ABSTRACT_LIBRARY__ENTRIES
            );
        }
        return entries;
    }
} // BookLibraryImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class genericAttributes.MovieLibraryImpl extends AbstractLibraryImpl implements MovieLibrary {
    /**
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     *
     * @generated NOT
     */
    public EList<Movie> getMovies() {
        return new UnmodifiableEList<Movie>(getEntries().size(), getEntries()
                .toArray());
    }

    /**
     * @generated NOT
     */
    @Override
    public EList<AbstractEntry> getEntries() {
        if (entries == null) {
            entries = new EObjectContainmentEList<AbstractEntry>(
                Movie.class,
                this,
                GenericAttributesPackage.ABSTRACT_LIBRARY__ENTRIES
            );
        }
        return entries;
    }
} // MovieLibraryImpl
Generic Unitype[1] Attributes

Performance tests on the different implementations showed the dynamic casting used in the Generic Attributes implementation to be very expensive. It was introduced to satisfy generics casting limitations of Java. However, we can avoid these casts by exploiting the limitations of Java’s generics implementation (and some @SuppressWarnings annotations already used by EcoreGen). The magic happens by removing all generic types from the manually implemented methods[2]. The getEntries() implementation is the same as for Generic Attributes, except the removed generic type information on the return type and the @SuppressWarnings annotation. The specific getter implementation is as simple as humanly possible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class genericUnitypeAttributes.BookLibraryImpl extends AbstractLibraryImpl implements BookLibrary {
    /**
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     *
     * @generated NOT
     */

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public EList getBooks() {
        return getEntries();
    }

    /**
     * @generated NOT
     */

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public EList getEntries() {
        if (entries == null) {
            entries = new EObjectContainmentEList<AbstractEntry>(
                Book.class,
                this,
                GenericUnitypeAttributesPackage.ABSTRACT_LIBRARY__ENTRIES
            );
        }
        return entries;
    }
} // BookLibraryImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class genericUnitypeAttributes.MovieLibraryImpl extends AbstractLibraryImpl implements MovieLibrary {
    /**
     * <!-- begin-user-doc --> <!-- end-user-doc -->
     *
     * @generated NOT
     */

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public EList getMovies() {
        return getEntries();
    }

    /**
     * @generated NOT
     */

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public EList getEntries() {
        if (entries == null) {
            entries = new EObjectContainmentEList<AbstractEntry>(
                Movie.class,
                this,
                GenericUnitypeAttributesPackage.ABSTRACT_LIBRARY__ENTRIES
            );
        }
        return entries;
    }
} // MovieLibraryImpl

Evaluation

Evaluation of the different implementation approaches focuses on three aspects:

  • Contract conformance: We want to keep the contracts intact, i. e. adding Movies into a BookLibrary should be prohibited.
  • Type safety: As one major reason for the whole endeavor is improving development productivity, we want to keep the type checks of the compiler as good as possible.
  • Performance: Increased development productivity should not lead to worse run-time performance.

Contract conformance and type safety

The following table summarizes the evaluation of contract conformance and type safety. We evaluate generic and specific access for each implementation approach. In each case, we check getter, adding one valid/invalid type and multiple valid/invalid types.

Valid denotes types that should be usable in the context (i. e. Book for BookLibrary; Movie, SciFi and Comedy for MovieLibrary), while invalid stands for incompatible types (i. e. Book for MovieLibrary; Movie and subtypes for BookLibrary).

Contract conformance is displayed by the leading symbol: ✓ shows the contract to be fulfilled, ✕ failed to fulfill the contract; ○ are edge cases. The evaluation is done based on the context: For adding an invalid type, the contract is fulfilled (marked with ✓) if the operation fails.

Type safety is shown as second part of each cell:

  • for get(), the return type is coded with G for generic type and S for specific type.
  • for add(), we note down the type of error reporting.
genericAccessorMethods genericAttributes genericUnitypeAttributes
generic specific generic specific generic specific
get() ✓ ​[? extends G] [S] [G] [S] [G] [S]
add() [validType] ✕ (compiler)[A] ○ (Unsupported​Operation​Exception[B])
add() [invalidType] ✕ (compiler)[A] ✓ (compiler) ✓ (Array​Store​Exception) ✓ (compiler) ✓ (Array​Store​Exception) ✓ (compiler)
addAll() [validType] ✕ (compiler)[A] ○ (Unsupported​Operation​Exception[B])
addAll() [invalidType] ✕ (compiler)[A] ✓ (compiler) ✓ (Array​Store​Exception) ✓ (compiler) ✓ (Array​Store​Exception) ✓ (compiler)

[A]: The compiler flags any parameter type as invalid generic type; This case needs more research. This article will be updated.

[B]: java.util.Collection.add() is optional, therefore throwing an UnsupportedOperationException fulfills the contract. However, the ecore user would expect the operation call to succeed, thus we evaluated this as edge case.

Adding invalid types to specific type collections (e. g. bookLibrary.getBooks().add(new Movie());) was correctly flagged as a compiler error in all cases. For both Generic Attributes implementations, adding invalid types to generic type collections (e. g. bookLibrary.getEntries().add(new Movie());) was correctly flagged as Exception (there is no way to detect this at compile-time).

Performance

We evaluate the same categories as for contract conformance and type safety (generic and specific access for each implementation approach). Evaluation is done by counting the number of elements in the collection.[3]

  • statistics denotes a plain call to getEntries().size() (for generic) and accordingly getBooks().size().
  • instanceof uses the “inverted” getter and checks for the required type in instanceof cascades in a very safe, but slow way.
  • dispatch uses Xtend to dispatch on a higher level than instanceof.

+ shows very good performance below measurement precision. − is set for considerably worse performance, which may still be acceptable. −− denotes very poor performance, only usable in a limited set of use cases.

genericAccessorMethods genericAttributes genericUnitypeAttributes
generic specific generic specific generic specific
statistics + + + + +
instanceof + −− −− + −−
dispatch + + + +

Conclusion

Generic Unitype Attributes provided the best results for all of contract conformance, type safety, and performance. Additionally, the straightforward implementation can easily be reused.

As any emulation, this one has its drawbacks. The most obvious one are the “non-standard” access techniques for member collections, which might confuse some ecore based tools. For both Generic Attribute implementations this effect is manageable, as the “magic” happens on Java level. However, the Generic Accessor Method implementation uses advanced ecore generics functionality and needs to adjust the upperBound of a collection reference to 1, effectively “disguising” the reference’s collection nature.

Resources

All code used for this article is available for download. Some remarks:

  • The code was tested under MacOS X 1.7, Eclipse 4.2RC1
  • The code contains compile errors (see [A]), but all tests should run successfully.

Acknowledgment

I would like to thank my colleague Christian Dietrich for intensive discussion and help on this post.

Footnotes

[1]: The name has been chosen from the original idea to limit this implementation to one type (“uni type”). As it turned out, this limitation doesn’t apply.

[2]: The compiler removes generic type information anyways, so we don’t introduce an error.

[3]: Evaluating add()-performance requires future research.