Language Workbench Challenge 2013 Xtext Submission

Language Workbench Challenge 2013 Xtext Submission

LWC13 Submission

3.3 Code Generator

In this section you will learn how to implement the code generator for the target application.

For simplicity, the code generator templates are placed in the org.eclipse.xtext.example.ql

project in a sub-package generator. Usually it would be better to create a separate project which contains the generator, since the language is independent from a single target platform.

It would be possible to create different code generators for different target platforms, and it would be better to implement each of them as separate projects.

Generator templates in Xtend are implementations of the IGenerator interface:

1

2

3

4

5

6

7

8

9 package org.eclipse.xtext.generator; public interface IGenerator {

/**

* @param input - the input for which to generate resources

* @param fsa - file system access to be used to generate files

*/ public void doGenerate(Resource input, IFileSystemAccess fsa);

}

3.3.1 Dispatcher template

The code generator is invoked with a Resource instance, which holds a Questionnaire instance. We have to generate multiple artifacts for each resource, so it is a common pattern to create a template class which serves as entry point and dispatches to other template classes to create the artifacts. Usually one template per artifact is created.

Create the class Root.java in package org.eclipse.xtext.example.ql.generator:

6

7

4

5

1

2

3

8

9

10

11

12 package org.eclipse.xtext.example.ql.generator; import javax.inject.Inject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess; import org.eclipse.xtext.generator.IGenerator; import org.eclipse.xtext.xbase.compiler.JvmModelGenerator;

@SuppressWarnings ( "restriction" ) public class Root implements IGenerator {

@Inject

47

LWC13 Submission

17

18

19

13

14

15

16

}

JvmModelGenerator jvmModelGenerator; public void doGenerate(Resource input, IFileSystemAccess fsa) {

// dispatch to other generators jvmModelGenerator.doGenerate(input, fsa);

}

As a first generator to which is dispatched, we inject an instance of JvmModelGenerator.

This is a standard generator shipped with Xtext which translates types inferred by the Jvm

Model Inferrer to Java classes. In our case, the Java class for Forms are generated by the

JvmModelGenerator

. In JSF terms, we speek of the Backing Bean.

1

2

3

4

Next, Xtext has to know that Root is the template that has to be invoked as generator implementation. Whenever a default implementation must be exchanged by a custom one, this has to be added or overridden in the respective Guice module. In the case of the custom IGenerator implementation, this has to be added to the QlDslRuntimeModule class. Open this class and add a configuration that binds the IGenerator interface to the Root class.

@Override public Class<?

extends IGenerator> bindIGenerator() { return Root.

class ;

}

Now we are ready to add additional templates and register them in the Root class.

3.3.2 Output Configuration Provider

In JSF applications all the web related content is normally placed under ./WebContent instead of ./src-gen, which is mostly used as output for generated java artifacts. We want to adapt to the web applications structure and seperate generated java classes and JSF artifacts from each other. For that purposes add a class called JsfOutputConfigurationProvider.java derived from org.eclipse.xtext.generator.OutputConfigurationProvider

39

within package org.eclipse.xtext.example.ql.generator

.The new provider adds an additional

OutputConfiguration for the ouput directory ./WebContent as shown in the listing below.

1

2

3

4 package org.eclipse.xtext.example.ql.generator; import java.util.Set;

39 http://xtextcasts.org/episodes/15-output-configurations

48

LWC13 Submission

9

10

11

12

13

14

15

7

8

5

6

19

20

21

22

16

17

18

28

29

30

31

23

24

25

26

27 import org.eclipse.xtext.generator.OutputConfiguration; import org.eclipse.xtext.generator.OutputConfigurationProvider; public class JSFOutputConfigurationProvider extends OutputConfigurationProvider {

} public final

/** public Set<OutputConfiguration> getOutputConfigurations() {

}

* @return a set of {@link OutputConfiguration} available for the generator

*/

Set<OutputConfiguration> outputConfigurations = super

.getOutputConfigurations();

OutputConfiguration webContent = new OutputConfiguration(WEB_CONTENT); webContent

.setDescription( "Read-only Output Folder for web generated application artifacts" ); webContent.setOutputDirectory( "./WebContent" ); webContent.setOverrideExistingResources( true ); webContent.setCreateOutputDirectory( true ); webContent.setCleanUpDerivedResources( true ); webContent.setSetDerivedProperty( true ); outputConfigurations.add(webContent); return

String WEB_CONTENT = outputConfigurations;

"WebContent" ;

The interface OutputConfiguration provides several options to configure the behavior of the so called Outlet. We use the defaults as in OutputConfigurationProvider except its name, description and outputDirectoy.

1

2

3

4

5

Our JsfOutputConfigurationProvider can be bound in the QlDslRuntimeModule by overriding/extending the method configure.

@Override public void configure(Binder binder) { super .configure(binder); binder.bind(IOutputConfigurationProvider.

class )

.to(JSFOutputConfigurationProvider.

class ).in(Singleton.

class );

}

After this step we can refer to the additional OutputConfiguration in generators by use of the constant WEB_CONTENT defined in class JsfOutputConfigurationProvider.

49

LWC13 Submission

3.3.3 JSF Generator

After creation of the class Root in section

3.3.1

where we easily can add new Generators and the defintion of the JsfOutputConfigurationProvider in section

3.3.2

which prvides an output folder for JSF artifacts, we use the New Xtend Class Wizard to create a new Xtend class called

JSFGenerator.xtend

in package org.eclipse.xtext.example.ql.generator.

This class will be our entry point to generate JSF related artifacts. The New Xtend Class

Wizard

provides the possibility to bind interfaces to the new class by use of the Add button near the interface section.

As we want to create a new generator we add the interface org.eclipse.xtext.generator.IGenerator

to our new Xtend class. After typing in the package, the name and the interface of our new Xtend class as shown in the figure above, we can finish the wizard so that the class shown in the following listing will be created in our project.

7

8

9

10

1

2

3

4

5

6 package org.eclipse.xtext.example.ql.generator

import org.eclipse.xtext.generator.IGenerator

import org.eclipse.emf.ecore.resource.Resource

import org.eclipse.xtext.generator.IFileSystemAccess

class JSFGenerator implements IGenerator { override doGenerate(Resource input, IFileSystemAccess fsa) { throw new UnsupportedOperationException( "TODO: auto-generated method stub" )

50

LWC13 Submission

11

12 }

}

To get the created JSFGenarator executed we have to inject and dispatch to it in our dispatcher template Root.java which was created in section

3.3.1

earlier.

14

15

16

17

11

12

13

18

19

20

21

9

10

6

7

8

4

5

1

2

3

} package org.eclipse.xtext.example.ql.generator; import javax.inject.Inject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess; import org.eclipse.xtext.generator.IGenerator; import org.eclipse.xtext.xbase.compiler.JvmModelGenerator; public class Root implements IGenerator {

@Inject

JvmModelGenerator jvmModelGenerator;

@Inject

JSFGenerator jsfGenerator; public void doGenerate(Resource input, IFileSystemAccess fsa) {

}

// dispatch to other generators jvmModelGenerator.doGenerate(input, fsa); jsfGenerator.doGenerate(input, fsa);

1

2

Now it is time to add some functionality to the JSFGenarator. Open the file JSFGenarator.xtend

and go to the doGenerate extension which is responsible to generate artifacts. Delete the autogenerated body of the extension - initially it just throws an UnsupportedOperationException

- and add the following lines as first statements to prevent execution of the generator if the file extension does not fit.

if (input.URI.fileExtension!= "ql" ) return

1

After this pre condition is passed we want to execute the generator logic for our model so it is a good idea to save the models root node in a variable.

val questionnaire = input.contents.head as Questionnaire

Because we want to generate JSF artifacts into the WebContent folder in the following steps we let Guice add a JSFOutputConfigurationProvider extension to our JSFGenerator.

51

LWC13 Submission

3

4

1

2 class JSFGenerator implements IGenerator{

@Inject extension JSFOutputConfigurationProvider

}

...

After this we have the possibility to use the WEB_CONTENT outlet constant as described in section

3.3.2

.

1

2

3

4

8

9

10

5

6

7

The following section

3.3.3

will describe the different extensions of the JSFGenerator which are responsible to generate the JSF artifacts described in

3.1

. In a real world project it can

be a good decision to seperate different artifacts in different Xtend files. Our sample is a very simple one, so we will add a new extension definition derived from the sample below to the

JSFGenerator.xtend

class which encapsulates the logic to generate a single artifact.

def generate_Artifact (EObject modelInfo)

’’’<?xml version=’1.0’ encoding=’UTF-8’ ?>

<!

-- @generated ->

<!

DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-transitional.dtd" >

< html xmlns = "http://www.w3.org/1999/xhtml" xmlns :h= "http://java.sun.com/jsf/html" xmlns :ui= "http://java.sun.com/jsf/facelets" >

... artifact content

</ html >

’’’

To get a valid XHTML page we have to generate the DOCTYPE and HTML tag into each

XHTML file. It was replaced in the listings of the following sections to focus on what matters.

JSF Form index

To get simple access to all generated forms in the application we want to generate an index page where a link is included for each form which is defined in our model. The generated index page should be saved in a file called index.xhtml within a subfolder ’generated/forms/’ of the WEB_CONTENT outlet created in section

3.3.2

.

1

2

3

4

5 class JSFGenerator implements IGenerator{

@Inject extension JSFOutputConfigurationProvider

@Inject extension QlDslExtensions override doGenerate(Resource input, IFileSystemAccess fsa) {

52

LWC13 Submission

10

11

12

13

14

8

9

6

7

15

16

17

}

}

...

if (input.URI.fileExtension!= "ql" ) return

// model root val questionnaire = input.contents.head as Questionnaire

// generate index page with links to generated forms val contentIndex = generate_FormIndex(questionnaire.forms) val fileNameIndex = "generated/forms/index.xhtml" fsa.generateFile(fileNameIndex,WEB_CONTENT, contentIndex)

...

10

11

8

9

12

13

3

4

1

2

5

6

7

For the new artifact add a new extension called def generate_FormIndex. It receives a list of

Form elements. In a short loop it generates a html:outputlink node for each element in the given list of forms. Because doGenerate is called for each QL resource, the generator currently has the limitation that all QL model elements have to be defined within the same QL resource to ensure generation of a correct form index page.

def generate_FormIndex (List<Form> forms)

’’’...

<ui:composition template="/index.xhtml">

<ui:define name="content">

«FOR elem: forms SEPARATOR "<br/>"»

<h:outputLink value="«elem.name».jsf">«elem.name»</h:outputLink>

«ENDFOR»

’’’

</ui:define>

</ui:composition>

...

By using the attribute template of a composition tag as already described in

3.1.3

, the

structure and styles of index.xhtml will be derived in our form index page. Our generated form index needs a index.xhtml in the applications root folder which itself or one of its parent templates defines a facelet:insert section with name ’content’ like described in section

3.1.3

.

To get the possibility to change the template for all generated files in a single file easily later we will use the generated form index as template for generated form pages in a later step. The generated form index looks similar to the one described in section

3.1.4

.

53

LWC13 Submission

QlDslExtensions

When implementing a generator, often some basic logic concerning the information extraction from the model needs to be implemented. We extracted this functionality into an own Xtend class for modularity and reuse purposes. All of these functions are used in the JSF Generator and some of them are even called in the QLS generator which will be described in chapter

4.2

later on. The created Xtend class is called QlDslExtension.xtend:

28

29

30

31

25

26

27

19

20

21

22

23

24

32

33

34

35

14

15

16

17

18

9

10

11

12

13

1

2

3

4

7

8

5

6 class QlDslExtensions {

@Inject extension IJvmModelAssociations

/**

* Computes the FormElements which are accessed by the expression of a Question.

*/ def Iterable<FormElement> getDependentElementsWithExpression (Question q) { if (q.expression != null ) return emptyList

// The JvmField which is inferred from a Question val JvmField field = q.jvmElements.filter( typeof (JvmField)).head

// Get all FormElements which have an expression val Iterable<FormElement> allFormElementsWithExpression = q.form.eAllContents

.filter( typeof (FormElement))

.filter[it.expression!=

.toSet

null ]

// search the expressions of the form elements which call the JvmField field in a feature call val result = allFormElementsWithExpression.filter[ val exp = it.expression

if (exp instanceof XFeatureCall) {

// a simple expression e.g. ’(XFeatureCall)’

(exp as XFeatureCall).feature.simpleName == field.simpleName

} else {

// a complex expression e.g. ’(XFeatureCall1 - XFeatureCall2)’ val xfeaturecalls = exp.eAllContents.filter( typeof (XFeatureCall)) xfeaturecalls.exists[ feature.simpleName == field.simpleName

]

}

]

} return result

54

LWC13 Submission

62

63

64

65

59

60

61

66

67

68

69

70

74

75

76

77

71

72

73

50

51

52

53

47

48

49

54

55

56

57

58

40

41

42

43

44

45

46

36

37

38

39

}

/**

* Creates an id for the given domain object.

*/ def String getId (EObject o) { switch (o) {

ConditionalQuestionGroup: "group" +allConditionalGroups(o).indexOf(o)

Question: o.name

Form: o.name.toLowerCase

}

}

/** def private allConditionalGroups (EObject ctx) {

}

/**

} def getForm(EObject question) {

EcoreUtil2::getContainerOfType(question,

}

* Get all ConditionalGroups underneath the given context.

*/ ctx.form.eAllContents.filter(

* Get the parent form’s name

*/ elem.form.name.toFirstLower

/**

*/ typeof def getFormName(FormElement elem){

(ConditionalQuestionGroup)).toList

* Get the Form container of the given question.

typeof

/**

* Returns the expression assigned to a FormElement, dependent on subtype for FormElement.

}

*/ def getExpression (FormElement elem) { switch (elem) {

}

Question: elem.expression

ConditionalQuestionGroup: elem.condition

(Form)) as Form

55

Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project