Home » Modeling » TMF (Xtext) » Error saving an EMF model with Crossreferences in a new XtextResource(A NewProject wizard creates two xtext models from dynamically created EMF models.)
Error saving an EMF model with Crossreferences in a new XtextResource [message #1803251] |
Mon, 25 February 2019 21:09 |
Alex Lotz Messages: 9 Registered: October 2017 |
Junior Member |
|
|
Context: I am working on a regular new-project wizards that internally creates two Xtext models based on dynamically created EMF models (using the regular EMF Factory methods). For the sake of simplicity I have boiled down my use-case to the bare minimum, now consisting of two very simple DSLs, MyDsl1 and MyDsl2 as shown in the attached figure below:
From these two Ecore models I have instantiated related Xtext grammars (that are straight forward) and I can use both languages as expected. Please note that MyRoot2 element of MyDsl2 references a MyRoot1 element from MyDsl1 (this will be relevant in a moment).
Next, I have created a simple new-project creation wizard that besides of creating the project itself, also instantiates the two Xtext models as follows (I added the full example implementation, but there is only a small part really relevant as explained below):
package mynewprojectwizard;
// ... several imports ...
import myDsl1.MyDsl1Factory;
import myDsl1.MyRoot1;
import myDsl2.MyDsl2Factory;
import myDsl2.MyRoot2;
public class WizardMyNewProject extends Wizard implements INewWizard {
private WizardNewProjectCreationPage pageOne;
@Override
public void addPages() {
pageOne = new WizardNewProjectCreationPage("MyWizard");
pageOne.setTitle("My Wizard");
pageOne.setInitialProjectName("TestProject");
addPage(pageOne);
}
@Override
public void init(IWorkbench workbench, IStructuredSelection selection) { }
@Override
public boolean performFinish() {
// create a proper project description
final IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(pageOne.getName());
java.net.URI location = pageOne.getLocationURI();
description.setLocationURI(location);
boolean result = true; // as long as no exception will be thrown this remains true
IRunnableWithProgress createProjectRunnable = new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
SubMonitor subMonitor = SubMonitor.convert(monitor, "Project Creation Wizard", 150);
try {
IProject project = pageOne.getProjectHandle();
project.create(description, subMonitor.split(10));
// open project
project.open(IResource.BACKGROUND_REFRESH, subMonitor.split(10));
// add Xtext and Java project natures by default
addProjectNatures(project, new String[] {"org.eclipse.xtext.ui.shared.xtextNature", JavaCore.NATURE_ID}, subMonitor.split(10));
//Create a Java Project
IJavaProject javaProject = JavaCore.create(project);
//set the model folder as a source entry
int defaultEntriesNumber = 2;
IClasspathEntry[] buildPath = new IClasspathEntry[defaultEntriesNumber];
buildPath[0] = JavaRuntime.getDefaultJREContainerEntry();
buildPath[1] = JavaCore.newSourceEntry(project.getFullPath().append("model"));
javaProject.setRawClasspath(buildPath, project.getFullPath().append("bin"), subMonitor.split(10));
if(project.isOpen()) {
// create model folder
IFolder modelFolder = project.getFolder("model");
modelFolder.create(true, true, subMonitor.split(10));
// now create our two DSL-models
createModels(project, modelFolder, subMonitor.split(10));
}
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (CoreException e) {
e.printStackTrace();
} finally {
subMonitor.done();
}
}
};
try {
boolean doFork = false;
boolean cancelable = true;
getContainer().run(doFork, cancelable, createProjectRunnable);
} catch (InvocationTargetException e) {
e.printStackTrace();
result = false;
} catch (InterruptedException e) {
e.printStackTrace();
result = false;
}
return result;
}
private void createModels(IProject project, IFolder modelFolder, IProgressMonitor monitor) {
// create model for MyDsl1
MyRoot1 root1 = createDsl1();
Injector i1 = getDsl1Injector();
Resource r1 = createNewEMFResource(project, modelFolder, i1, null);
saveEMFModelInResource(root1, r1, monitor);
//########################################################
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//########################################################
// create model for MyDsl2, referencing the resource from MyDsl1
List<Resource> relatedResources = Arrays.asList(r1);
MyRoot2 root2 = createDsl2(root1);
Injector i2 = getDsl2Injector();
Resource r2 = createNewEMFResource(project, modelFolder, i2, relatedResources);
saveEMFModelInResource(root2, r2, monitor);
}
private Injector getDsl1Injector() {
return Mydsl1Activator.getInstance().getInjector(Mydsl1Activator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL1);
}
private Injector getDsl2Injector() {
return Mydsl2Activator.getInstance().getInjector(Mydsl2Activator.ORG_XTEXT_EXAMPLE_MYDSL_MYDSL2);
}
private MyRoot1 createDsl1() {
MyRoot1 r1 = MyDsl1Factory.eINSTANCE.createMyRoot1();
r1.setName("Test1");
return r1;
}
private MyRoot2 createDsl2(MyRoot1 r1) {
MyRoot2 r2 = MyDsl2Factory.eINSTANCE.createMyRoot2();
r2.setName("Test2");
r2.setRoot1ref(r1);
return r2;
}
private Resource createNewEMFResource(IProject project, IFolder modelFolder, Injector injector, List<Resource> relatedResources) {
if(modelFolder.exists()) {
String modelFileExtrnsion = getModelFileExtension(injector);
IResourceSetProvider resourceSetProvider = injector.getInstance(IResourceSetProvider.class);
ResourceSet resourceSet = resourceSetProvider.get(project);
// add related resources (if there are any) to the resource-set
if(relatedResources != null) {
for(Resource relatedResource: relatedResources) {
resourceSet.getResources().add(relatedResource);
}
}
// get an existing resource for the current model
URI uri = URI.createURI("platform:/resource/"
+ project.getName()+ "/"
+ modelFolder.getProjectRelativePath() + "/"
+ project.getName()+ "." + modelFileExtrnsion);
return resourceSet.createResource(uri);
}
return null;
}
private void saveEMFModelInResource(EObject emfModel, Resource newResource, IProgressMonitor monitor) {
// add emfModel to the new resource
newResource.getContents().add(emfModel);
try {
// the resource will internally parse and save the newly added EMF model
SaveOptions saveOptions = SaveOptions.newBuilder().format().getOptions();
newResource.save(saveOptions.toOptionsMap());
// we need to wait until the saved resource gets recognized within the eclipse workspace
} catch (IOException e) {
e.printStackTrace();
}
}
private String getModelFileExtension(Injector injector) {
return injector.getInstance(FileExtensionProvider.class).getPrimaryFileExtension();
}
private void addProjectNatures(IProject project, String[] natures, IProgressMonitor monitor) throws CoreException {
IProjectDescription description = project.getDescription();
String[] currNatures = description.getNatureIds();
String[] newNatures = new String[currNatures.length+natures.length];
System.arraycopy(currNatures, 0, newNatures, 0, currNatures.length);
System.arraycopy(natures, 0, newNatures, currNatures.length, natures.length);
// validate the natures
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IStatus status = workspace.validateNatureSet(newNatures);
// only apply new nature, if the status is ok
if (status.getCode() == IStatus.OK) {
description.setNatureIds(newNatures);
project.setDescription(description, monitor);
}
}
}
Basically, the most interesting part here is the method createModels() which instantiates the two EMF models, and then saves both in a related XtextResource. Please note the commented out part where I call Thread.sleep(1000);. When I comment this sleep back in, the overall wizard works (most of the time). But in some rare cases and in all cases when the sleep is deactivated, I get this error:
java.lang.reflect.InvocationTargetException
at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:448)
at org.eclipse.jface.operation.ModalContext.run(ModalContext.java:353)
at org.eclipse.jface.wizard.WizardDialog.run(WizardDialog.java:980)
at mynewprojectwizard.WizardMyNewProject.performFinish(WizardMyNewProject.java:120)
at org.eclipse.jface.wizard.WizardDialog.finishPressed(WizardDialog.java:778)
at org.eclipse.jface.wizard.WizardDialog.buttonPressed(WizardDialog.java:417)
at org.eclipse.jface.dialogs.Dialog.lambda$0(Dialog.java:619)
at org.eclipse.swt.events.SelectionListener$1.widgetSelected(SelectionListener.java:81)
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:249)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:86)
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5348)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1348)
at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4602)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4183)
at org.eclipse.jface.window.Window.runEventLoop(Window.java:818)
at org.eclipse.jface.window.Window.open(Window.java:794)
at org.eclipse.ui.actions.NewProjectAction.run(NewProjectAction.java:115)
at org.eclipse.jface.action.Action.runWithEvent(Action.java:473)
at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:565)
at org.eclipse.jface.action.ActionContributionItem.lambda$4(ActionContributionItem.java:397)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:86)
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:5348)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1348)
at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4602)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:4183)
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1150)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336)
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1039)
at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153)
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:680)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:594)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:148)
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:151)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:388)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:243)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:653)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:590)
at org.eclipse.equinox.launcher.Main.run(Main.java:1499)
at org.eclipse.equinox.launcher.Main.main(Main.java:1472)
Caused by: java.lang.RuntimeException: No EObjectDescription could be found in Scope MyRoot2.root1ref for MyRoot1'Test1'
Semantic Object: MyRoot2'Test2'
URI: platform:/resource/TestProject/model/TestProject.mydsl2
EStructuralFeature: myDsl2::MyRoot2.root1ref
at org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic$ExceptionThrowingAcceptor.accept(ISerializationDiagnostic.java:131)
at org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer.getCrossReferenceNameFromScope(CrossReferenceSerializer.java:138)
at org.eclipse.xtext.serializer.tokens.CrossReferenceSerializer.serializeCrossRef(CrossReferenceSerializer.java:111)
at org.eclipse.xtext.serializer.acceptor.SequenceFeeder.getToken(SequenceFeeder.java:481)
at org.eclipse.xtext.serializer.acceptor.SequenceFeeder.accept(SequenceFeeder.java:238)
at org.xtext.example.mydsl.serializer.MyDsl2SemanticSequencer.sequence_MyRoot2(MyDsl2SemanticSequencer.java:59)
at org.xtext.example.mydsl.serializer.MyDsl2SemanticSequencer.sequence(MyDsl2SemanticSequencer.java:36)
at org.eclipse.xtext.serializer.sequencer.AbstractSemanticSequencer.createSequence(AbstractSemanticSequencer.java:67)
at org.eclipse.xtext.serializer.impl.Serializer.serialize(Serializer.java:115)
at org.eclipse.xtext.serializer.impl.Serializer.serializeToRegions(Serializer.java:136)
at org.eclipse.xtext.serializer.impl.Serializer.serialize(Serializer.java:142)
at org.eclipse.xtext.serializer.impl.Serializer.serialize(Serializer.java:189)
at org.eclipse.xtext.resource.XtextResource.doSave(XtextResource.java:386)
at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:1430)
at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:999)
at mynewprojectwizard.WizardMyNewProject.saveEMFModelInResource(WizardMyNewProject.java:205)
at mynewprojectwizard.WizardMyNewProject.createModels(WizardMyNewProject.java:152)
at mynewprojectwizard.WizardMyNewProject.access$2(WizardMyNewProject.java:131)
at mynewprojectwizard.WizardMyNewProject$1.run(WizardMyNewProject.java:104)
at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:437)
... 46 more
I interpret the error such that the MyDsl2 model does not find the reference to MyDsl1 model. The strange thing is, that if I comment the sleep command back in, then this error does not occur. I spent a lot of time debugging and analyzing the problem and came to the following conclusion. The problem seems to be related to the Java-based resolution of the Xtext cross references. I have found out that a Java project creates the bin folder into which the new xtext models are somehow copied (automatically) at some point. I don't know which of the magic Eclipse background process does this, but obviously, the Xtext resource is not aware of this update and fails each time it tries to resolve the former resource that has not yet been created in the bin folder.
Well, obviously, the solution with sleep is a very bad solution. My question now is, how can I ensure that resource-saving runs in sync with the workspace, or what can I do else to fix my problem? My secondary question is: Do I need to use something different to IRunnableWithProgress (e.g. some transactional editing domain)? Did I maybe overlooked some obvious solution?
I appreciate any help!
Alex
|
|
| | | | |
Goto Forum:
Current Time: Thu Mar 28 21:13:46 GMT 2024
Powered by FUDForum. Page generated in 0.03276 seconds
|