URLs in Xtext

DSLs often include URLs. However, Ecore does not provide such a datatype by default. Also Xtext doesn’t provide a native rule for entering URLs as it is available for strings or integers.

In this post, I will explain all the nescessary steps to use URLs in Ecore models and in Xtext-based DSLs.

The java class for holding URLs is java.net.URL. This is not a primitive datatype in Ecore. Hence, the first step is to provide an EDataType that makes this class available to Ecore. Create a new project using the wizard “Empty EMF Project” and then create a .ecore model that contains the EDataType. The following snippet is a minimal example of such a model:

<?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="datatypes"
	nsURI="http://www.xtext.org/example/url/Url" 
	nsPrefix="org.xtext.example.url.datatype">

  	<eClassifiers 
		xsi:type="ecore:EDataType" 
		name="URL" 
		instanceClassName="java.net.URL"/>
</ecore:EPackage>

Furthermore, we have to create a corresponding generator model (.genmodel) for this Ecore model. Select the .ecore file and run the wizard “EMF Generator Model”. Before running the Ecore code generator that creates the model classes and their factories we should adapt the base package and the package’s prefix. The generator model may look like this:

<?xml version="1.0" encoding="UTF-8"?>
<genmodel:GenModel 
	xmi:version="2.0"
	xmlns:xmi="http://www.omg.org/XMI" 
	xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" 	modelDirectory="/org.xtext.example.url.datatype/src-gen"
	modelPluginID="org.xtext.example.url.datatype" 
	modelName="UrlExample" 
	importerID="org.eclipse.emf.importer.ecore"
	complianceLevel="6.0" 
	copyrightFields="false">

	<foreignModel>UrlExample.ecore</foreignModel>
	<genPackages 
		prefix="Datatypes" 
		disposableProviderFactory="true" 
		ecorePackage="UrlExample.ecore#/">
	<genDataTypes ecoreDataType="UrlExample.ecore#//URL"/>
	</genPackages>
</genmodel:GenModel>

Note: I used the name org.xtext.example.url.datatype for the Ecore project. The .ecore and .genmodel files are located in the the package org.xtext.example.url.datatype that is in a source folder named src.

Now we can start defining the rule in the DSLs grammar that accepts URLs. We assume URLs of the form protocol://host[:port][path][query]. Note that this limits to protocolls such as http or ftp. URLs in less common protocols such as news or mailto are not accepted. At the W3C website the complete BNF-based grammar of URLs can be found which includes more protocols. Make sure that the grammar definition imports the Ecore model that introduces the URL datatype as shown in the example:

terminal URL returns URL:
	('a'..'z')+ '://' // Protocol

	( // Hostname
		(
			('0'..'9')+ '.' ('0'..'9')+ '.' ('0'..'9')+ '.' ('0'..'9')+
		) |
		(
			('a'..'z'|'A'..'Z')
			(
				'a'..'z'|'A'..'Z'|
				'0'..'9'|
				'$'|
				'-'|
				'_'|
				(
					'\\'
					('0'..'9'|'a'..'f'|'A'..'F')
					('0'..'9'|'a'..'f'|'A'..'F')
				) |
				(
					'%'
					('0'..'9')
					('0'..'9')
				)
			)*
			(
				'.'
				('a'..'z'|'A'..'Z')
				(
					'a'..'z'|'A'..'Z'|
					'0'..'9'|
					'$'|
					'-'|
					'_'|
					(
						'\\'
						('0'..'9'|'a'..'f'|'A'..'F')
						('0'..'9'|'a'..'f'|'A'..'F')
					) |
					(
						'%'
						('0'..'9')
						('0'..'9')
					)
				)*
			)*
		)
	)
	(':' ('0'..'9')+)? // Portnumber

	('/'
		(
			'a'..'z'|'A'..'Z'|
			'0'..'9'|
			'$'|
			'-'|
			'_'|
			'~'|
			'+'|
			'.'|
			(
				'\\'
				('0'..'9'|'a'..'f'|'A'..'F')
				('0'..'9'|'a'..'f'|'A'..'F')
			) |
			(
				'%'
				('0'..'9')
				('0'..'9')
			)
		)*
	)* // Path

	('?'
		(
			'a'..'z'|'A'..'Z'|
			'0'..'9'|
			'$'|
			'-'|
			'_'|
			'='|
			'&'|
			';'|
			(
				'\\'
				('0'..'9'|'a'..'f'|'A'..'F')
				('0'..'9'|'a'..'f'|'A'..'F')
			) |
			(
				'%'
				('0'..'9')
				('0'..'9')
			)
		)+

		('+'
			(
				'a'..'z'|'A'..'Z'|
				'0'..'9'|
				'$'|
				'-'|
				'_'|
				'='|
				'&'|
				';'|
				(
					'\\'
					('0'..'9'|'a'..'f'|'A'..'F')
					('0'..'9'|'a'..'f'|'A'..'F')
				) |
				(
					'%'
					('0'..'9')
					('0'..'9')
				)
			)+
		)*
	)?; // Query

If we run the Xtext generator it will complain that an external package is not registered. To fix this problem go to your MWE workflow and add a reference to the generator model in the EcoreGeneratorFragment of the Generator component:

fragment = ecore.EcoreGeneratorFragment {
	referencedGenModels="platform:/resource/org.xtext.example.url.datatype/src/org/xtext/example/url/datatype/UrlExample.genmodel"
}

After this the MWE can be executed. However, the generated code still contains errors because of unresolved dependencies. They can be fixed by adding a dependency in the plugin.xml to the project that contains the generated model classes.

The final step is to register a value converter for the rule that returns an instance of java.net.URL. It may look like this:

public static class URLValueConverter extends AbstractNullSafeConverter {
	@Override
	protected String internalToString(java.net.URL value) {
		return value.toString();
	}

	@Override
	protected java.net.URL internalToValue(String string, AbstractNode node) 
		throws ValueConverterException {

		try {
			return new URL(string);
		} catch (MalformedURLException e) {
			throw new ValueConverterException(
				"Couldn't convert '" + string + "' to url.", 
				node, 
				e);
		}
	}
}

The source code of this post is available here.

Advertisements
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: