Can I make Xtext throw, when a containment reference is reassigned? [message #1832810] |
Fri, 25 September 2020 12:55 |
Jan Hermes Messages: 27 Registered: September 2020 |
Junior Member |
|
|
Hello fellow Xtexters,
while designing my language and especially its type-system, I a specific requirement reoccurs sometimes:
I want to forbid reassigning containment-references to other containers.
I want to do it so I avoid bugs that occur when I reassign a parsed containment-reference to an JIT-model-element.
So there are two situations that should be valid for me:
- copy the containment-reference
- wrap the containment-reference in a model element as a non-containment reference
In all other situations where a containment-reference gets "stolen" from its container I want java to throw an exception.
Is this easily possible to achieve?
Greetings, Jan
[Updated on: Fri, 25 September 2020 13:19] Report message to a moderator
|
|
|
|
|
|
|
|
|
|
|
Re: Can I make Xtext throw, when a containment reference is reassigned? [message #1832874 is a reply to message #1832868] |
Mon, 28 September 2020 11:53 |
Jan Hermes Messages: 27 Registered: September 2020 |
Junior Member |
|
|
Hi,
yes I also really wanted this to help me to avoid bugs too.
For now I will be using this approach until it's not working anymore:
Use an adapter that will be added to every EObject created while I develop the language-code
The adapter essentially has an internal map of potentially stolen objects and throws if it receives the
notification of an object that was added and remove before from somewhere else.
@Singleton
class StealingAdapter extends AdapterImpl {
Map<Object, Object> stolenObjects = newHashMap
override notifyChanged(Notification notification) {
switch (notification.eventType) {
case Notification.REMOVE: containerRemove(notification.notifier, notification.oldValue)
case Notification.ADD: containerAdd(notification.notifier, notification.newValue)
}
}
def containerAdd(Object potentialThief, Object wallet) {
val maybeVictim = Optional.ofNullable(stolenObjects.get(wallet))
if (maybeVictim.present) {
throw new IllegalStateException('''«wallet» was stolen from «maybeVictim.get»''')
}
}
def containerRemove(Object potentialVictim, Object wallet) {
this.stolenObjects.put(wallet, potentialVictim)
}
}
Add the adapter upon creation of EObjects for the semantic model
I just wrote a custom "create()" method for the ECoreElementFactory and bound this custom factory in the runtime module.
class MyDslEcoreElementFactory extends DefaultEcoreElementFactory {
@Inject MyDslStealingAdapter adapter
override EObject create(EClassifier classifier) {
val eobj = super.create(classifier)
eobj.eAdapters().add(adapter)
eobj
}
}
Add the adapter upon creation of objects from the generic MyDslFactory
This is just a delegating Factory that uses use the delegate to create objects but adds adapters before creating them.
I need to implement this process for each create... method of the MyDslFactory interface, which is a bit ugly, but I couldn't get
a better generic solution for now.
public class MyDslAdapterAddingFactory extends MyDslSwitch<EObject> implements MyDslFactory {
@Inject MyDslStealingAdapter adapter;
private MyDslFactory delegate;
public MyDslAdapterAddingFactory(MyDslFactory delegate) {
this.delegate = delegate;
}
@Override
public EObject defaultCase(EObject obj) {
// final MyDslStealingAdapter adapter = adapterProvider.get();
obj.eAdapters().add(adapter);
return obj;
}
@SuppressWarnings("unchecked")
public <T extends EObject> T adapt(T obj) {
return (T) doSwitch(obj);
}
public Model createModel() {
return adapt(delegate.createModel());
}
public createFoo createFoo() {
return adapt(delegate.createFoo());
}
// ... rest of the overriden methods
}
Bind everything in the MyDslRuntimeModule
The usual process of binding the newly implementing classes:
class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
def MyDslFactory bindMyDslFactory() {
val factory = MyDslFactory.eINSTANCE
return new MyDslAdapterAddingFactory(factory)
}
override Class<? extends IAstFactory> bindIAstFactory() {
return MyDslEcoreElementFactory
}
// ...
}
Now my code raises an exception when I add an object to another container that was stolen from somewhere else before.
Thanks for your help,
Jan
[Updated on: Mon, 28 September 2020 11:59] Report message to a moderator
|
|
|
|
Re: Can I make Xtext throw, when a containment reference is reassigned? [message #1832879 is a reply to message #1832878] |
Mon, 28 September 2020 15:51 |
Jan Hermes Messages: 27 Registered: September 2020 |
Junior Member |
|
|
Hi Ed,
this adapter solution would only be enabled during developing. For releases I would like to change the bindings again to the default factories.
But anyway I would like to choose your solution and just making sure every element inherits from a common custom element, but I don't know which button to tweak for this to work.
I have a basic mwe2-workflow which generates a MyDsl.genmodel with the standard property "Model Class Defaults" -> "Root Extends Class" = org.eclipse.emf.ecore.impl.MinimalEObjectImpl$Container
Is it possible to change this automatically generated property to always use my custom base class upon model-generation via the mwe2-workflow?
Regards
Jan Hermes
UPDATE
I found the configuration of the emfGenerator fragment
emfGenerator {
rootExtendsClass = "org.example.mydsl.MyDslEObjectImpl"
}
So now I will try to inherit MinimalEObjectImpl and override your suggested function with the specific test.
Thanks a lot
[Updated on: Mon, 28 September 2020 16:18] Report message to a moderator
|
|
|
|
|
|
Re: Can I make Xtext throw, when a containment reference is reassigned? [message #1832907 is a reply to message #1832879] |
Tue, 29 September 2020 05:52 |
Jan Hermes Messages: 27 Registered: September 2020 |
Junior Member |
|
|
Jan Hermes wrote on Mon, 28 September 2020 15:51
...
UPDATE
I found the configuration of the emfGenerator fragment
emfGenerator {
rootExtendsClass = "org.example.mydsl.MyDslEObjectImpl"
}
...
Jan Hermes wrote on Mon, 28 September 2020 16:26
... with the above configuration for the mwe2 workflow and the custom EObjectImpl below everything works now as desired. Thank you very much for this additional insight. :)
class MyDslEObjectImpl extends MinimalEObjectImpl.Container {
override NotificationChain eBasicSetContainer(InternalEObject newContainer, int newContainerFeatureID,
NotificationChain msgs) {
val oldContainer = eInternalContainer();
if (oldContainer !== null && newContainer !== null) {
throw new IllegalStateException('''«newContainer» attempts to steal «this» from «oldContainer»''')
}
super.eBasicSetContainer(newContainer, newContainerFeatureID, msgs)
}
}
...
Hello Ed Merks and Ed Willink,
thanks for your advice. As I you can see in the quoted posts above, I found the place to exchange the "rootExtendsClass" (@see "UPDATE") already and managed to use the "MyDslEObjectImpl" successfully. Everything is working as expected and I learned a lot from it again, also very helpful for future customizations, I'm really grateful for that.
As for the conditional breakpoint, I might have totally overread it, because I didn't grasp its concept. I wasn't aware that this is possible in eclipse and java. I normally develop in completely different environments. So I will add this conditional breakpoint now as you suggested, which then would of course be the least invasive way to assert the stealing-condition.
Thanks for educating me. :D
Regards, Jan Hermes
|
|
|
Powered by
FUDForum. Page generated in 0.04713 seconds