I have this grammar:
Feature returns ecore::EStructuralFeature:
{Feature} name=ID ':' (fp_many?='*')? eType=[ecore::EClassifier];
And the EClass:
Class returns ecore::EClass:
{EClassClass}
name=ID (interface?=':Api')?
(BEGIN
(eStructuralFeatures+=Feature)*
(eOperations+=Operation)*
END)?;
My goal is to have a DSL for textual Ecore mm with a YAML like syntax, so I need to convert the Feature object depending on its EType to either an EAttribute or an EReference.
I have tried to hook the afterModelLinked
in LazyLinker
like this:
Queue<Feature> ftrs = Queues.newArrayDeque(features);
Feature f = null;
while ((f = ftrs.poll()) != null) {
if (f.getEType() == null)
continue;
if (f.getEType() instanceof EDataType) {
createEAttribute(eClazz, f);
} else if (f.getEType() instanceof EClass) {
createEReference(eClazz, f);
}
eClazz.getEStructuralFeatures().remove(f);
}
This code does converts the feature to the appropriate type but I get an error with the validation service an here the stack trace:
org.eclipse.emf.common.util.WrappedException: java.lang.NullPointerException
at org.eclipse.xtext.linking.lazy.LazyLinkingResource.getEObject(LazyLinkingResource.java:233)
at org.eclipse.xtext.resource.persistence.StorageAwareResource.getEObject(StorageAwareResource.java:124)
at org.eclipse.xtext.linking.lazy.LazyLinkingResource.doResolveLazyCrossReference(LazyLinkingResource.java:192)
at org.eclipse.xtext.linking.lazy.LazyLinkingResource.resolveLazyCrossReference(LazyLinkingResource.java:151)
at org.eclipse.xtext.linking.lazy.LazyLinkingResource.resolveLazyCrossReferences(LazyLinkingResource.java:137)
at org.eclipse.xtext.EcoreUtil2.resolveLazyCrossReferences(EcoreUtil2.java:528)
at org.eclipse.xtext.validation.ResourceValidatorImpl.resolveProxies(ResourceValidatorImpl.java:163)
at org.eclipse.xtext.validation.ResourceValidatorImpl.validate(ResourceValidatorImpl.java:75)
at org.eclipse.xtext.ui.editor.validation.ValidationJob$1.exec(ValidationJob.java:91)
at org.eclipse.xtext.ui.editor.validation.ValidationJob$1.exec(ValidationJob.java:1)
at org.eclipse.xtext.util.concurrent.CancelableUnitOfWork.exec(CancelableUnitOfWork.java:26)
at org.eclipse.xtext.resource.OutdatedStateManager.exec(OutdatedStateManager.java:121)
at org.eclipse.xtext.ui.editor.model.XtextDocument$XtextDocumentLocker.internalReadOnly(XtextDocument.java:520)
at org.eclipse.xtext.ui.editor.model.XtextDocument$XtextDocumentLocker.readOnly(XtextDocument.java:492)
at org.eclipse.xtext.ui.editor.model.XtextDocument.readOnly(XtextDocument.java:133)
at org.eclipse.xtext.ui.editor.validation.ValidationJob.createIssues(ValidationJob.java:86)
at org.eclipse.xtext.ui.editor.validation.ValidationJob.run(ValidationJob.java:67)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54)
Caused by: java.lang.NullPointerException
at org.eclipse.xtext.linking.impl.ImportedNamesAdapter.find(ImportedNamesAdapter.java:34)
at org.eclipse.xtext.linking.impl.DefaultLinkingService.getImportedNamesAdapter(DefaultLinkingService.java:95)
at com.eacg.dsl.faml.linker.FamlLinkingService.getImportedNamesAdapter(FamlLinkingService.java:53)
at org.eclipse.xtext.linking.impl.DefaultLinkingService.registerImportedNamesAdapter(DefaultLinkingService.java:86)
at org.eclipse.xtext.linking.impl.DefaultLinkingService.registerImportedNamesAdapter(DefaultLinkingService.java:90)
at org.eclipse.xtext.linking.impl.DefaultLinkingService.registerImportedNamesAdapter(DefaultLinkingService.java:80)
at org.eclipse.xtext.linking.impl.DefaultLinkingService.getScope(DefaultLinkingService.java:58)
at com.eacg.dsl.faml.linker.FamlLinkingService.getScope(FamlLinkingService.java:47)
at org.eclipse.xtext.linking.impl.DefaultLinkingService.getLinkedObjects(DefaultLinkingService.java:119)
at org.eclipse.xtext.linking.lazy.LazyLinkingResource.getEObject(LazyLinkingResource.java:250)
at org.eclipse.xtext.linking.lazy.LazyLinkingResource.getEObject(LazyLinkingResource.java:225)
When debugging I found that it's still using in context the object Feature
even if I have removed it when I created the mapping.
My question is this: How to safely replace the object Feature
without corrupting my model. I've also tried to implement IDerivedStateComputer
but got the some error.
I think the underlying problem here is that EMF is a graph-based format; classes can be features of other classes, arguments to operations, etc. In general, this graph of relations can contain loops, cycles and knots. So anything that tries to modify things in-place is going to be tricky, requiring a full-blown graph traversal algorithm to make sure you don't change something depended on by something you haven't processed yet.
The alternative is to let the model load and link in it's native form, and then transform it as a single pass. This is the way xcore implements things; the equivalent declaration is:
XClass:
{XClass}
(annotations+=XAnnotation)*
((abstract?='abstract'? 'class') | interface?= 'interface') name = ID
('<' typeParameters+=XTypeParameter (',' typeParameters+=XTypeParameter)* '>')?
('extends' superTypes+=XGenericType (',' superTypes+=XGenericType)*)?
('wraps' instanceType=JvmTypeReference) ?
'{'
(members+=XMember)*
'}'
;
Note all the X's, those are local model classes. Then later on, there is just a function:
protected EClass getEClass(final XClass xClass)