Can I make Xtext throw, when a containment reference is reassigned? [message #1832810] |
Fri, 25 September 2020 08:55  |
Eclipse User |
|
|
|
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 09:19] by 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 07:53   |
Eclipse User |
|
|
|
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 07:59] by 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 01:52  |
Eclipse User |
|
|
|
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.08074 seconds