For the Xtext based modelling tool chain to model objective c (iPhone, iPad) applications for orderbird I needed to manipulate an Xtext model programmatically and serialize it back to disk.
I did encounter some challenges while serializing enums and want to share my experience in this post.
Here is a snippet of the grammar I am using for the well known use case of entity modelling:
EntityModel:
(entities += Entity)*
;
Entity:
(annotations+=Annotation)*
'entity' name=ID '{' '}'
;
Annotation:
'@' option=ConfigOption (':' value=ConfigValue)?
;
enum ConfigOption:
persistency
;
enum ConfigValue:
CoreData | FMDB
;
So, entities can be annotated to determine with which technology they will be stored persistently.
At first glance, serialization does not seem to be a hassle. Having an IResourceSetProvider, I can get the model from its XtextResource:
@Inject
private IResourceSetProvider provider;
private EntityModel loadEntityModelFromFile(IFile file) {
ResourceSet xrs = provider.get(file.getProject());
URI uri = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
Resource resource = xrs.getResource(uri, true);
EntityModel em = (EntityModel)resource.getContents().get(0);
return em;
}
Now I can programmatically change the model and then serialize it back to disk by saving it in a XtextResource:
private void save(EntityModel em) {
ResourceSet xrs = provider.get(getProject());
XtextResource xr =
(XtextResource) xrs.getResource
(URI.createPlatformResourceURI(getModelPath(), true) , true);
xr.getContents().set(0, em);
Map
SaveOptions.defaultOptions().addTo(options);
xr.save(options);
}
Serializing this model:
@persistency:CoreData
entity Foo {}
I get:
@persistencyentity Foo {}
The ConfigOption is missing! Hmmm, why? Xtext somehow assumes the ConfigOption to be transient (not serializable).
The Xtext documentation says "The default transient value service considers a model element to be transient if it is unset or equals its default value." and "By default, EMF returns false for eIsSet(..) if the value equals the default value."
Looking at the generated java code for the ecore meta model, the 'CoreData' literal is defined as the default:
/*
* @generated
*/
public class AnnotationImpl extends EObjectImpl implements Annotation {
//...
protected static final ConfigValue VALUE_EDEFAULT = ConfigValue.CORE_DATA;
protected ConfigValue value = VALUE_EDEFAULT;
//...
}
In order to tell Xtext which model elements can be considered as (non) transient, an instance of ITransientValueService has to be specified in your DSL's guice runtime module. This seems to be an easy task: Inherit from DefaultTransientValueService and overwrite isTransient(…) to yield the correct semantics:
public class DataDslTransientValueService extends DefaultTransientValueService {
@Override
public boolean isTransient(EObject owner, EStructuralFeature feature, int index) {
if (owner instanceof Annotation && DataDslPackage.ANNOTATION__VALUE == feature.getFeatureID()) {
return false;
}
return super.isTransient(owner, feature, index);
}
}
… and hook it into the guice module:
public Class bindITransientValueService() {
return DataDslTransientValueService.class;
}
Damn! Still the same errorneous output:
@persistencyentity Foo {}
A little bit of code archeology and debugging reveals that Xtext has two distinct hierarchies of the ITransientValueService interface.
One in the package org.eclipse.xtext.parsetree.reconstr and the other in the package org.eclipse.xtext.serializer.sequencer.
My DataDslTransientValueService implemented org.eclipse.xtext.parsetree.reconstr.ITransientValueService. But this does not seem to be sufficient. Thus, I also implemented org.eclipse.xtext.serializer.sequencer.ITransientValueService:
@SuppressWarnings("restriction")
public class SequencerTransientValueService extends TransientValueService {
public ValueTransient isValueTransient(EObject semanticObject, EStructuralFeature feature) {
if (semanticObject instanceof Annotation && DataDslPackage.ANNOTATION__VALUE == feature.getFeatureID()) {
return ValueTransient.NO;
}
return super.isValueTransient(semanticObject, feature);
}
}
My DSL's guice runtime module contains these two bindings for ITransientValueService:
public Class bindITransientValueService() {
return DataDslTransientValueService.class;
}
public Class bindITransientValueService2() {
return SequencerTransientValueService.class;
}
And finally the serialization yields the correct result!
As you might have noticed I annotated the SequencerTransientValueService with @SuppressWarnings("restriction"). The class org.eclipse.xtext.serializer.sequencer.TransientValueService seems not be intended for public use. But obviously it is required to get the serialization to work correctly.
At the time of writing this post, I also realized that my grammar's enum ConfigOption only has one literal that is defined as default and should thus not be serialized by default. But in my implementations of the two ITransientValueService interfaces I only specified ConfigValue to be non-transient. However, ConfigOption is serialized without adding the corresponding semantics to the implementations of the ITransientValueService. Maybe this is due to the Annotation's option attribute not being optional in the grammar as the value attribute is. And maybe I should study the Xtext documentation in more detail. Maybe ..
Regards,
steven
Posts mit dem Label objective c werden angezeigt. Alle Posts anzeigen
Posts mit dem Label objective c werden angezeigt. Alle Posts anzeigen
Sonntag, 20. November 2011
Dienstag, 18. Oktober 2011
Xtext Objective C Formatter/Beautifier
This post shows how I integrated uncrustify into Xtext. At the end of this post you will be able to package uncrustify with your language UI plugin and run uncrustify as part of a MWE2 workflow (the described approach was tested with Xtext 2.0 on Mac OS X 10.7).
First, you have to get uncrustify. Unpack it, run ./configure and then make. The binary is located in the src/ folder and is named uncrustify - what a surprise. Create a folder formatting/ in your language's UI plugin. Copy the binary into this folder. To tell uncrustify how to format the code we have to supply it with a config file. A config file for objective c can be found here. Download this file and put it into the formatting/ folder. Besides the binary and a config file we need a shell script that runs uncrustify. Create a file formatSource.sh in formatting/. The shell script looks like this:
#! /bin/sh touch files.txt find . -name "*.[hm]" > files.txt while read line; do ./uncrustify_osx -l OC -c ./uncrustify_obj_c.cfg --no-backup $line done < files.txt
rm files.txtThis script will look for *.h and *.m files recursively down from its location, run over them, and format them without creating a backup copy.
Now that we have the necessary files for running uncrustify ... oh well ... we must be able to run uncrustify from within java. For executing shell scripts from within a java process - welcome platform dependency - check out my ShellCommandExecutor. This class is also used for making the shell script formatSource.sh executable after copying it to a language project:
private void copyFormattingFiles(final IProject project){ Bundle bundle = SystemDslActivator.getInstance().getBundle(); IPath scriptPath = copyFile("formatting/formatSource.sh" , "formatSource.sh", project, bundle); IPath binaryPath = copyFile("formatting/uncrustify_osx", "uncrustify_osx", project, bundle); copyFile("formatting/uncrustify_obj_c.cfg", "uncrustify_obj_c.cfg", project, bundle); //make script and binary executable try { ShellCommandExecutor.execute("chmod", "+x", scriptPath.toString()); ShellCommandExecutor.execute("chmod", "+x", binaryPath.toString()); } catch (Exception e) { //TODO: write to error log } }The above method can be found in this class. The execution of formatSource.sh in a MWE2 workflow component looks like this:
public class ObjectiveCFormatter extends org.eclipse.emf.mwe.core.lib.AbstractWorkflowComponent2{ private static final String SCRIPT_PATH = "./formatSource.sh"; @Override protected void invokeInternal(WorkflowContext ctx, ProgressMonitor monitor, Issues issues) { try{ CommandResult cr = ShellCommandExecutor.execute(SCRIPT_PATH, new String[]{}); if (cr.success){ System.out.println("Formatting complete!"); } else{ issues.addError(cr.output); } }catch (Exception e){ issues.addError(e.getMessage()); } } }If you add this component after your objective c generator in your workflow all *.h and *.m files will be formatted as described by the uncrustify objective c config file.
regards,
steven
Labels:
code generation,
dsl,
formatting,
objective c,
style guide,
uncrustify,
xcode,
xtext
Abonnieren
Posts (Atom)