Xtext: Calling the Generator from a Context Menu

October 15, 2011 41 comments

Xtext offers the user to implement an Xtend Class that implements the org.eclipse.xtext.generator.IGenerator Interface. By Default this Generator is called by the Builder through a org.eclipse.xtext.builder.IXtextBuilderParticipant when saving the file. But what to do if I want to call the Generator explicitely through a context menu entry on the file as shown in the screenshot below? This will be shown in the Following example.

Grammar & IGenerator

We first start with writing the Grammar and after generating the language we implement the Generator Stub Xtext created for us.

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Model:
	greetings+=Greeting*;
	
Greeting:
	'Hello' name=ID ('from' from=[Greeting])?'!';
package org.xtext.example.mydsl.generator

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.xtext.example.mydsl.myDsl.Greeting

import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*

class MyDslGenerator implements IGenerator {
	
	override void doGenerate(Resource resource, IFileSystemAccess fsa) {
		for (g : resource.allContentsIterable.filter(typeof(Greeting))) {
			fsa.generateFile(g.name+".txt",'''
				Hello «g.name» «IF g.from != null»from «g.from.name»«ENDIF»!
			''')
		}
	}
}

Disabling the IBuilderParticipant

Then we go to the UI Projects Plugin.xml and disable the default registration of an IXtextBuilderParticipant

<!--
   <extension
         point="org.eclipse.xtext.builder.participant">
      <participant
            class="org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:org.eclipse.xtext.builder.IXtextBuilderParticipant">
      </participant>
   </extension>
-->

Setting up the Context Menu

Then we setup the context menu with Eclipse means

<extension
        point="org.eclipse.ui.handlers">
     <handler
           class="org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:org.xtext.example.mydsl.ui.handler.GenerationHandler"
           commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand">
     </handler>
     
  </extension>
  
  <extension
        point="org.eclipse.ui.commands">
        <command name="Generate Code"
              id="org.xtext.example.mydsl.ui.handler.GenerationCommand">
        </command>
  </extension>
  
  <extension point="org.eclipse.ui.menus">
    <menuContribution locationURI="popup:org.eclipse.jdt.ui.PackageExplorer">
        <command
            commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand"
            style="push">
            <visibleWhen
                  checkEnabled="false">
                  <iterate>
       <adapt type="org.eclipse.core.resources.IResource">
          <test property="org.eclipse.core.resources.name" 
                value="*.mydsl"/>
       </adapt>
    </iterate>
            </visibleWhen>
        </command>
    </menuContribution>
	</extension>

Implementing the Handler / Calling the Generator

The last thing we have to do is to call the IGenerator from the handler class.
this could look like

public class GenerationHandler extends AbstractHandler implements IHandler {
	
	@Inject
	private IGenerator generator;

	@Inject
	private Provider<EclipseResourceFileSystemAccess> fileAccessProvider;
	
	@Inject
	IResourceDescriptions resourceDescriptions;
	
	@Inject
	IResourceSetProvider resourceSetProvider;
	
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		
		ISelection selection = HandlerUtil.getCurrentSelection(event);
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection structuredSelection = (IStructuredSelection) selection;
			Object firstElement = structuredSelection.getFirstElement();
			if (firstElement instanceof IFile) {
				IFile file = (IFile) firstElement;
				IProject project = file.getProject();
				IFolder srcGenFolder = project.getFolder("src-gen");
				if (!srcGenFolder.exists()) {
					try {
						srcGenFolder.create(true, true,
								new NullProgressMonitor());
					} catch (CoreException e) {
						return null;
					}
				}

				final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();
				fsa.setOutputPath(srcGenFolder.getFullPath().toString());
				
				URI uri = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
				ResourceSet rs = resourceSetProvider.get(project);
				Resource r = rs.getResource(uri, true);
				generator.doGenerate(r, fsa);
				
			}
		}
		return null;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

Give it a try
We start a runtime app, create a bunch of model files that refernce each other,
try the genenerator – and hey – it works.

And how if i want to call the generator from the context menu of the open editor

I register the command for the editor and write a Handler like this one

<extension point="org.eclipse.ui.menus">
    <menuContribution locationURI="popup:#TextEditorContext?after=additions">
        <command
            commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand"
            style="push">
            <visibleWhen
	                  checkEnabled="false">
	               <reference
	                     definitionId="org.xtext.example.mydsl.MyDsl.Editor.opened">
	               </reference>
	            </visibleWhen>
        </command>
    </menuContribution>
</extension>
public class GenerationHandler extends AbstractHandler implements IHandler {
	
	@Inject
	private IGenerator generator;

	@Inject
	private Provider<EclipseResourceFileSystemAccess> fileAccessProvider;
	
	@Inject
	IResourceDescriptions resourceDescriptions;
	
	@Inject
	IResourceSetProvider resourceSetProvider;
	
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		
		IEditorPart activeEditor = HandlerUtil.getActiveEditor(event);
		IFile file = (IFile) activeEditor.getEditorInput().getAdapter(IFile.class);
		if (file != null) {
			IProject project = file.getProject();
			IFolder srcGenFolder = project.getFolder("src-gen");
			if (!srcGenFolder.exists()) {
				try {
					srcGenFolder.create(true, true,
							new NullProgressMonitor());
				} catch (CoreException e) {
					return null;
				}
			}
	
			final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();
			fsa.setOutputPath(srcGenFolder.getFullPath().toString());
			
			
			if (activeEditor instanceof XtextEditor) {
				((XtextEditor)activeEditor).getDocument().readOnly(new IUnitOfWork<Boolean, XtextResource>() {
				
					@Override
					public Boolean exec(XtextResource state)
							throws Exception {
						generator.doGenerate(state, fsa);
						return Boolean.TRUE;
					}
				});
				
			}
		}
		return null;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}
Advertisements

Xtext Content Assist Auto Activation

September 19, 2011 16 comments

Xtext offers nice Content Assist facilities. JDT offers a nice additional feature: Content assist is autoactivated if a certain character (.) is typed. To activate this feature in Xtext simply customize your UiModule

	
public class MyDslUiModule extends org.xtext.example.mydsl.ui.AbstractMyDslUiModule {
public MyDslUiModule(AbstractUIPlugin plugin) {
		super(plugin);
	}
	
	@Override
	public void configure(Binder binder) {
		super.configure(binder);
	binder.bind(String.class)
.annotatedWith(com.google.inject.name.Names.named(
(XtextContentAssistProcessor.COMPLETION_AUTO_ACTIVATION_CHARS)))
.toInstance(".,:");
	}
}

In this case content assist is autoactivated on . , and :

Xtend2 Code Generators with Non-Xtext Models

In this blog post i want to show a simple example of how to use Xtend2 to generate code from Non-Xtext but EMF-based model.

Having a simple EMF Model i’ve created the genmodel + Model + Edit + Editor code.
Using the Editor i’ve created a bunch of .sample files and now want to
generate code using Xtend2.

Xtend comes with an IGenerator interface that i implement in my SampleGenerator Xtend file

package sample

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.emf.ecore.EObject

class SampleGenerator implements IGenerator {

	override void doGenerate(Resource resource, IFileSystemAccess fsa) {
		for (EObject o : resource.contents) {
			o.compile(fsa)
		}
	}

	def dispatch void compile(Model m, IFileSystemAccess fsa) {
		for (e : m.elements) {
			e.compile(fsa)
		}
	}

	def compile(Element e, IFileSystemAccess fsa) {
		fsa.generateFile(e.name+".txt", '''
		this is element «e.name»
		''')
	}

	def dispatch void compile(EObject m, IFileSystemAccess fsa) { }

}

The last step we need is a workflow that reads the model files and invokes the generator

First we need to create some java classes that exposes our .sample to the reader
(resourceseriveprovider) and
do some Guice Binding Stuff (Generator / ResourceSet ….)

package sample;

import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.xtext.generator.IGenerator;
import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule;

public class SampleGeneratorModule extends AbstractGenericResourceRuntimeModule {

	@Override
	protected String getLanguageName() {
		return "sample.presentation.SampleEditorID";
	}

	@Override
	protected String getFileExtensions() {
		return "sample";
	}

	public Class<? extends IGenerator> bindIGenerator() {
		return SampleGenerator.class;
	}

	public Class<? extends ResourceSet> bindResourceSet() {
		return ResourceSetImpl.class;
	}

}

 

package sample;

import org.eclipse.xtext.ISetup;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class SampleGeneratorSetup implements ISetup {

	@Override
	public Injector createInjectorAndDoEMFRegistration() {
		return Guice.createInjector(new SampleGeneratorModule());
	}

}

 

package sample;

import org.eclipse.xtext.resource.generic.AbstractGenericResourceSupport;

import com.google.inject.Module;

public class SampleGeneratorSupport extends AbstractGenericResourceSupport {

	@Override
	protected Module createGuiceModule() {
		return new SampleGeneratorModule();
	}

}

finally we wire this together in the workflow file

module sample.SampleGenerator

import org.eclipse.emf.mwe.utils.*

var targetDir = "src-gen"
var modelPath = "model"

Workflow {

	bean = StandaloneSetup {
		registerGeneratedEPackage = "sample.SamplePackage"
	}

	component = DirectoryCleaner {
		directory = targetDir
	}

	component = sample.SampleGeneratorSupport {}

	component = org.eclipse.xtext.mwe.Reader {
		path = modelPath
		register = sample.SampleGeneratorSetup {}
		loadResource = {
			slot = "model"
		}
	}

	component = org.eclipse.xtext.generator.GeneratorComponent {
		register = sample.SampleGeneratorSetup {}
		slot = 'model'
		outlet = {
			path = targetDir
		}
	}
}

running the workflow we get nice files generated

Categories: Xtext Tags: , ,

Customizing Xtext Metamodel Inference using Xtend2

Xtext has nice metamodel inference capabilities. But sometimes you have to do some customizations to the generated ecore metamodel, e.g. adding a derived operation. You have basically two options: (1) move to a manually maintained metamodel (2) use Customized Post Processing as described here http://www.eclipse.org/Xtext/documentation/2_0_0/020-grammar-language.php#customPostProcessing

The second possibility uses good old Xpand/Xtend1 extensions to do the postprocessing. But what if i want to use Xtend2 for that? A very simple solution i’d like to show in this post.

So lets start with the gramar

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Model:
	persons+=Person*
;

Person:
	"person" firstname=STRING lastname=STRING
;

We now want to add a getFullname Operation to our person.
Xtext offers the Interface IXtext2EcorePostProcessor for the postprocessing.
So we write a Xtend2 class for that

package org.xtext.example.mydsl

import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor
import org.eclipse.xtext.GeneratedMetamodel
import org.eclipse.emf.ecore.EPackage
import org.eclipse.emf.ecore.EClassifier
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EcoreFactory
import org.eclipse.emf.ecore.EcorePackage
import org.eclipse.emf.ecore.EcorePackage.Literals
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage
import org.eclipse.emf.common.util.BasicEMap$Entry
import org.eclipse.emf.ecore.impl.EStringToStringMapEntryImpl

class MyXtext2EcorePostProcessor implements IXtext2EcorePostProcessor {
	
 	override void process(GeneratedMetamodel metamodel) {
		metamodel.EPackage.process
	}
	
	def process(EPackage p) {
		for (c : p.EClassifiers.filter(typeof(EClass))) {
			if (c.name == "Person") {
				c.handle
			}
		}
	}
	
	def handle (EClass c) {
		val op = EcoreFactory::eINSTANCE.createEOperation
		op.name = "getFullName"
		op.EType = EcorePackage::eINSTANCE.EString
		val body = EcoreFactory::eINSTANCE.createEAnnotation
		body.source = GenModelPackage::eNS_URI
		val map = EcoreFactory::eINSTANCE.create(EcorePackage::eINSTANCE.getEStringToStringMapEntry()) as BasicEMap$Entry<String,String>
		map.key = "body"
		map.value = "return getFirstname() + \" \" + getLastname();"
		body.details.add(map)
		op.EAnnotations += body
		c.EOperations += op
	}
	
}

The last Problem left is how to make the Generator use this class.
Xtext does not offer a explicit place to change the IXtext2EcorePostProcessor.
Its default Implementation is bound in the XtextRuntimeModule,
that is instantiated in the org.eclipse.xtext.generator.Generator
class. so we have to subclass the Generator to get it changed

package org.xtext.example.mydsl;

import org.eclipse.xtext.XtextRuntimeModule;
import org.eclipse.xtext.XtextStandaloneSetup;
import org.eclipse.xtext.generator.Generator;
import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor;

import com.google.inject.Guice;
import com.google.inject.Injector;

@SuppressWarnings("restriction")
public class ExtendedGenerator extends Generator {
	
	public ExtendedGenerator() {
		new XtextStandaloneSetup() {
			@Override
			public Injector createInjector() {
				return Guice.createInjector(new XtextRuntimeModule() {
					@Override
					public Class<? extends IXtext2EcorePostProcessor> bindIXtext2EcorePostProcessor() {
						return MyXtext2EcorePostProcessor.class;
					}
				});
			}
		}.createInjectorAndDoEMFRegistration();
	}

}

finally we use the in the Generator Workflow

Workflow {
    bean = StandaloneSetup {
        scanClassPath = true
        platformUri = "${runtimeProject}/.."
    }

    component = DirectoryCleaner {
        directory = "${runtimeProject}/src-gen"
    }

    component = DirectoryCleaner {
        directory = "${runtimeProject}.ui/src-gen"
    }

    component = ExtendedGenerator {
        pathRtProject = runtimeProject
        pathUiProject = "${runtimeProject}.ui"
        pathTestProject = "${runtimeProject}.tests"
        projectNameRt = projectName
        projectNameUi = "${projectName}.ui"
        language = {
....

as a result our person has the getFullname Operation

public interface Person extends EObject
{

  String getFirstname();


  void setFirstname(String value);


  String getLastname();


  void setLastname(String value);


  String getFullName();

} // Person
public class PersonImpl extends MinimalEObjectImpl.Container implements Person
{
 
  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public String getFullName()
  {
    return getFirstname() + " " + getLastname();
  }



} //PersonImpl

Xtext 2.0 and UML

Consider you have some (Eclipse) UML2 models and now want to reference e.g. Classes from these model from your dsl. In this blog post i want to show a simple example what to do. We have to do two things: Provide an IResourceServiceProvider for .uml resources and reference the UML class from the grammar. So first we write the grammar. that is quite easy

grammar org.xtext.example.umldsl.UmlDsl with org.eclipse.xtext.common.Terminals

import "http://www.eclipse.org/uml2/3.0.0/UML" as uml
import "http://www.eclipse.org/emf/2002/Ecore" as ecore

generate umlDsl "http://www.xtext.org/example/umldsl/UmlDsl"

Model:
	elements+=Element*
;

Element:
	"element" name=ID "mapsTo" ref=[uml::Class|FQN]
;

FQN returns ecore::EString:
	ID ("." ID)*
;

Then we have to add some stuff to the Language workflow to get the stuff running

module org.xtext.example.umldsl.GenerateUmlDsl

import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.generator.*
import org.eclipse.xtext.ui.generator.*

var grammarURI = "classpath:/org/xtext/example/umldsl/UmlDsl.xtext"
var file.extensions = "umldsl"
var projectName = "org.xtext.example.umldsl"
var runtimeProject = "../${projectName}"

Workflow {
    bean = StandaloneSetup {
        scanClassPath = true
        platformUri = "${runtimeProject}/.."
        uriMap = {
			from = "platform:/plugin/org.eclipse.emf.codegen.ecore/model/GenModel.genmodel"
			to = "platform:/resource/org.eclipse.emf.codegen.ecore/model/GenModel.genmodel"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.emf.ecore/model/Ecore.genmodel"
			to = "platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.uml2.codegen.ecore/model/GenModel.genmodel"
			to = "platform:/resource/org.eclipse.uml2.codegen.ecore/model/GenModel.genmodel"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.uml2.uml/model/UML.genmodel"
			to = "platform:/resource/org.eclipse.uml2.uml/model/UML.genmodel"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.emf.codegen.ecore/model/GenModel.ecore"
			to = "platform:/resource/org.eclipse.emf.codegen.ecore/model/GenModel.ecore"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.emf.ecore/model/Ecore.ecore"
			to = "platform:/resource/org.eclipse.emf.ecore/model/Ecore.ecore"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.uml2.codegen.ecore/model/GenModel.ecore"
			to = "platform:/resource/org.eclipse.uml2.codegen.ecore/model/GenModel.ecore"
		}
		uriMap = {
			from = "platform:/plugin/org.eclipse.uml2.uml/model/UML.ecore"
			to = "platform:/resource/org.eclipse.uml2.uml/model/UML.ecore"
		}
		//
        registerGeneratedEPackage = "org.eclipse.emf.ecore.EcorePackage"
        registerGeneratedEPackage = "org.eclipse.uml2.uml.UMLPackage"
        registerGeneratedEPackage = "org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage"
        registerGeneratedEPackage = "org.eclipse.uml2.codegen.ecore.genmodel.GenModelPackage"
        registerGenModelFile = "platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel"
        registerGenModelFile = "platform:/resource/org.eclipse.emf.codegen.ecore/model/GenModel.genmodel"
        registerGenModelFile = "platform:/resource/org.eclipse.uml2.uml/model/UML.genmodel"
        registerGenModelFile = "platform:/resource/org.eclipse.uml2.codegen.ecore/model/GenModel.genmodel"

    }

    ...
}

We add some extra deps to the manifest

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: org.xtext.example.umldsl
Bundle-Vendor: My Company
Bundle-Version: 1.0.0
Bundle-SymbolicName: org.xtext.example.umldsl; singleton:=true
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.xtext;bundle-version="2.0.0";visibility:=reexport,
 org.apache.log4j;bundle-version="1.2.15";visibility:=reexport,
 org.apache.commons.logging;bundle-version="1.0.4";resolution:=optional;visibility:=reexport,
 org.eclipse.xtext.generator;resolution:=optional,
 org.eclipse.emf.codegen.ecore;resolution:=optional,
 org.eclipse.emf.mwe.utils;resolution:=optional,
 org.eclipse.emf.mwe2.launch;resolution:=optional,
 org.eclipse.uml2.uml;bundle-version="3.2.0",
 org.eclipse.xtext.util,
 org.eclipse.emf.ecore,
 org.eclipse.emf.common,
 org.antlr.runtime,
 org.eclipse.xtext.common.types,
 org.eclipse.uml2.codegen.ecore;bundle-version="1.7.0"
Import-Package: org.apache.log4j,
 org.apache.commons.logging,
 org.eclipse.xtext.xbase.lib,
 org.eclipse.xtext.xtend2.lib
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Export-Package: org.xtext.example.umldsl,
 org.xtext.example.umldsl.services,
 org.xtext.example.umldsl.umlDsl,
 org.xtext.example.umldsl.umlDsl.impl,
 org.xtext.example.umldsl.umlDsl.util,
 org.xtext.example.umldsl.serializer,
 org.xtext.example.umldsl.parser.antlr,
 org.xtext.example.umldsl.parser.antlr.internal,
 org.xtext.example.umldsl.validation

and generate the Language. If we now run a runtime application and create a .uml file and a .umlmodel file we see nothing since the uml stuff is not yet referenceable So the second with we create is a IResourceServiceProvider . Therefore we take the plugins org.eclipse.xtext.ecore and org.eclipse.xtext.ui.ecore, that do the same for .ecore files, as inspiration. So we create the plugins org.eclipse.xtext.uml and org.eclipse.xtext.ui.uml with following content: The UmlResourceDescriptionStrategy customizes the creation of IEObjectDescriptions. This is the stuff that Xtext puts into the index and maps a Name to an EObject or Proxy. In our case we simply subclass from the default and do no further customizations.

package org.eclipse.xtext.uml;

import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;

public class UmlResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy {

}

The UmlQualifiedNameProvider gives our UML Stuff a fully qualified name – we take the defaults here too

package org.eclipse.xtext.uml;

import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider;

public class UmlQualifiedNameProvider extends DefaultDeclarativeQualifiedNameProvider {

}

Then we have to create a Guice Module to Glue the stuff

package org.eclipse.xtext.uml;

import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy;
import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule;

public class UmlRuntimeModule extends AbstractGenericResourceRuntimeModule {

	@Override
	protected String getLanguageName() {
		return "org.eclipse.uml2.uml.editor.presentation.UMLEditorID";
	}

	@Override
	protected String getFileExtensions() {
		return "uml";
	}

	public Class&amp;amp;amp;lt;? extends IDefaultResourceDescriptionStrategy&amp;amp;amp;gt; bindIDefaultResourceDescriptionStrategy() {
		return UmlResourceDescriptionStrategy.class;
	}

	@Override
	public Class&amp;amp;amp;lt;? extends IQualifiedNameProvider&amp;amp;amp;gt; bindIQualifiedNameProvider() {
		return UmlQualifiedNameProvider.class;
	}

}

If we want to use this stuff from an mwe(2) workflow we have to create a Support-Class for this too

package org.eclipse.xtext.uml;

import org.eclipse.xtext.resource.generic.AbstractGenericResourceSupport;

import com.google.inject.Module;

public class UmlSupport extends AbstractGenericResourceSupport {

	@Override
	protected Module createGuiceModule() {
		return new UmlRuntimeModule();
	}

}

Finally we add some stuff to the manifest and we’re done with the runtime stuff

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Uml
Bundle-SymbolicName: org.eclipse.xtext.uml
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: org.eclipse.xtext;bundle-version="2.0.0",
 org.eclipse.uml2.uml;bundle-version="3.2.0"
Export-Package: org.eclipse.xtext.uml

Then we have to do some stuff at the ui side too. We create some glue code (Activator, ExecutableExtensionFactory (to be able to use guice in the plugin.xml) and an UiModule)

/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.ui.uml;

import org.apache.log4j.Logger;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.xtext.ui.shared.SharedStateModule;
import org.eclipse.xtext.uml.UmlRuntimeModule;
import org.osgi.framework.BundleContext;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.util.Modules;

/**
 * The activator class controls the plug-in life cycle
 */
public class Activator extends AbstractUIPlugin {

	private static final Logger logger = Logger.getLogger(Activator.class);

	// The plug-in ID
	public static final String PLUGIN_ID = "org.eclipse.xtext.ui.uml"; //$NON-NLS-1$

	// The shared instance
	private static Activator plugin;

	private Injector injector;

	/**
	 * The constructor
	 */
	public Activator() {
	}

	public Injector getInjector() {
		return injector;
	}

	private void initializeEcoreInjector() {
		injector = Guice.createInjector(
				Modules.override(Modules.override(new UmlRuntimeModule())
				.with(new UmlUiModule(plugin)))
				.with(new SharedStateModule()));
	}

	@Override
	public void start(BundleContext context) throws Exception {
		super.start(context);
		plugin = this;
		try {
			initializeEcoreInjector();
		} catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e;
		}
	}

	@Override
	public void stop(BundleContext context) throws Exception {
		plugin = null;
		injector = null;
		super.stop(context);
	}

	/**
	 * Returns the shared instance
	 *
	 * @return the shared instance
	 */
	public static Activator getDefault() {
		return plugin;
	}

}

/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.ui.uml;

import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.xtext.ui.LanguageSpecific;
import org.eclipse.xtext.ui.editor.IURIEditorOpener;
import org.eclipse.xtext.ui.resource.generic.EmfUiModule;

public class UmlUiModule extends EmfUiModule {

	public UmlUiModule(AbstractUIPlugin plugin) {
		super(plugin);
	}

	@Override
	public void configureLanguageSpecificURIEditorOpener(com.google.inject.Binder binder) {
		binder.bind(IURIEditorOpener.class).annotatedWith(LanguageSpecific.class).to(UmlEditorOpener.class);
	}

}

/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.ui.uml;

import org.eclipse.xtext.ui.guice.AbstractGuiceAwareExecutableExtensionFactory;
import org.osgi.framework.Bundle;

import com.google.inject.Injector;

public class ExecutableExtensionFactory extends AbstractGuiceAwareExecutableExtensionFactory {

	@Override
	protected Bundle getBundle() {
		return Activator.getDefault().getBundle();
	}

	@Override
	protected Injector getInjector() {
		return Activator.getDefault().getInjector();
	}

}

Of course we want to UML Editor to open smoothly if we click on an element referenced from UML too so we create an LanguageSpecificURIEditorOpener too

/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtext.ui.uml;

import java.util.Collections;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.ui.IEditorPart;
import org.eclipse.xtext.ui.editor.LanguageSpecificURIEditorOpener;
import org.eclipse.uml2.uml.editor.presentation.UMLEditor;

public class UmlEditorOpener extends LanguageSpecificURIEditorOpener {

	@Override
	protected void selectAndReveal(IEditorPart openEditor, URI uri,
			EReference crossReference, int indexInList, boolean select) {
		UMLEditor umlEditor = (UMLEditor) openEditor.getAdapter(UMLEditor.class);
		if (umlEditor != null) {
			EObject eObject = umlEditor.getEditingDomain().getResourceSet().getEObject(uri, true);
			umlEditor.setSelectionToViewer(Collections.singletonList(eObject));
		}
	}

}

we add some deps to the manifest

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Uml
Bundle-SymbolicName: org.eclipse.xtext.ui.uml;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: org.eclipse.uml2.uml.editor;bundle-version="3.1.100",
 org.eclipse.xtext.uml;bundle-version="1.0.0",
 org.eclipse.xtext.ui;bundle-version="2.0.0",
 org.eclipse.xtext.ui.shared;bundle-version="2.0.0"
Import-Package: org.apache.log4j;version="1.2.15"
Bundle-Activator: org.eclipse.xtext.ui.uml.Activator
Bundle-ActivationPolicy: lazy

And finally register our resource service provider to the extensionpoint Xtext offers for that.

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
    
    <extension
          point="org.eclipse.xtext.extension_resourceServiceProvider">
        <resourceServiceProvider
              class="org.eclipse.xtext.ui.uml.ExecutableExtensionFactory:org.eclipse.xtext.ui.resource.generic.EmfResourceUIServiceProvider"
              uriExtension="uml">
        </resourceServiceProvider>
    </extension>
 
</plugin>

We restart our runtime application, and TATATATA: it works 😉

https://github.com/cdietrich/xtext-uml-example

Categories: Xtext

IQualifiedNameProviders in Xtext 2.0

Xtext 2.0 come with a change to the IQualifiedNameProvider interface. This interface is used to calculate a name for EObjects. The Name is used in Xtext’s index, for cross referencing and much more.

public interface IQualifiedNameProvider extends Function<EObject, QualifiedName> {

	QualifiedName getFullyQualifiedName(EObject obj);

}

Here we see the API change: There is a rename of the getQualifiedName method to getFullyQualifedName. In Xtext 1.0.x the qualified name was a simple String, now it is a wrapper class that holds the segements of the qualified name.

There are two default implementations for a IQualifiedNameProviderSimpleNameProvider and DefaultDeclarativeQualifiedNameProvider. Consider we have a grammar like

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Package:
	"package" name=ID "{"
		elements+=Element*
	"}"
;

Element:
	"element" name=ID
;

and a sample model like

package TestPackage {
	element A
	element B
}

Then with SimpleNameProvider we would have following qualified names.

  • TestPackage for the package
  • A and B for the elements

And with DefaultDeclarativeQualifiedNameProvider we would have following qualified names.

  • TestPackage for the package
  • TestPackage.A and TestPackage.B for the elements
Both Providers take the name EAttribute of our Package and Element to do the calculation. The DefaultDeclarativeQualifiedNameProvider  uses the Elements parents qualified name too. (a fully qualified name ;-))
But it won’t work e.g. if our grammar would look like
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Package:
	"package" name=ID "{"
		elements+=Element*
	"}"
;

Element:
	"element" id=ID
;
with Element having and id and not a name. We can easily change this by creating and binding our own IQualifiedNameProvider e.g. by extending DefaultDeclarativeQualifiedNameProvider
package org.xtext.example.mydsl;

import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
import org.xtext.example.mydsl.myDsl.Element;
import org.xtext.example.mydsl.myDsl.Package;

public class MyDslQNP extends DefaultDeclarativeQualifiedNameProvider{

	QualifiedName qualifiedName(Element e) {
		Package p = (Package) e.eContainer();
		return QualifiedName.create(p.getName(), e.getId());
	}

}

We simply write a method qualifiedName that is called from the polymorpthic dispatcher when calculating the name of an Element

package org.xtext.example.mydsl;

import org.eclipse.xtext.naming.IQualifiedNameProvider;

/**
 * Use this class to register components to be used at runtime / without the Equinox extension registry.
 */
public class MyDslRuntimeModule extends org.xtext.example.mydsl.AbstractMyDslRuntimeModule {

	@Override
	public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() {
		return MyDslQNP.class;
	}

}
Categories: Xtext Tags: ,

Hover support in Xtext 2.0: Tutorial

Xtext 2.0 comes with an all new Hover API (see Christoph’s Blog http://ckulla.wordpress.com/2011/02/06/hover-support-in-xtext-2-0/). I want to give a short introduction on how to use what with Xtext’s Greeting Example.

So first we create a new Xtext project with the wizard and generate the language. We start a runtime application and create a project with a model file. Here is what the default hover looks like:

We want is to adopt the hover support to look like this:

We have to basically implement 2 interfaces: IEObjectHoverProvider to customize the header line and IEObjectDocumentationProvider to customize the content section. Here 2 simple implementations (using appropriate superclasses)

package org.xtext.example.mydsl.ui;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.ui.editor.hover.html.DefaultEObjectHoverProvider;
import org.xtext.example.mydsl.myDsl.Greeting;

public class MyDslEObjectHoverProvider extends DefaultEObjectHoverProvider {

	@Override
	protected String getFirstLine(EObject o) {
		if (o instanceof Greeting) {
			return "Damn good greeting: " + ((Greeting)o).getName();
		}
		return super.getFirstLine(o);
	}

}

 

package org.xtext.example.mydsl.ui;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
import org.xtext.example.mydsl.myDsl.Greeting;

public class MyDslEObjectDocumentationProvider implements IEObjectDocumentationProvider {

	@Override
	public String getDocumentation(EObject o) {
		if (o instanceof Greeting) {
			return "This is a nice Greeting with nice <b>markup</b> in the <i>documentation</i>";
		}
		return null;
	}

}

Finally we have to bind these classes in the UiModule of our dsl. 

package org.xtext.example.mydsl.ui;

import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
import org.eclipse.xtext.ui.editor.hover.IEObjectHoverProvider;

/**
 * Use this class to register components to be used within the IDE.
 */
public class MyDslUiModule extends org.xtext.example.mydsl.ui.AbstractMyDslUiModule {
	public MyDslUiModule(AbstractUIPlugin plugin) {
		super(plugin);
	}

	public Class<? extends IEObjectHoverProvider> bindIEObjectHoverProvider() {
		return MyDslEObjectHoverProvider.class;
	}

	public Class<? extends IEObjectDocumentationProvider> bindIEObjectDocumentationProviderr() {
		return MyDslEObjectDocumentationProvider.class;
	}
}

That is all we have to do to get customized hovers with Xtext 2.0. 

Categories: Xtext Tags: