Home » Modeling » Epsilon » Feature not found in undefined object(Property not working in ETL without setter or on nested class)
Feature not found in undefined object [message #1787199] |
Tue, 22 May 2018 03:01 |
Flavio Costa Messages: 23 Registered: February 2018 |
Junior Member |
|
|
Hello,
I am writing a standalone (i.e. Java runtime) transformation where the source is an EMF model generated by Xtext and the target is plain (non-EMF) Java classes.
The source class, loaded with new InMemoryEmfModel(...), is generated as:
public class MySourceImpl extends MinimalEObjectImpl.Container implements MySource {
protected MySourceData data;
public MySourceData getData()
{
return data;
}
}
The target class is loaded with new JavaModel(...) and defined as:
public class MyTarget extends MySuperTarget<Data> {
public class Data extends MySuperTarget.Data {
// stuff here
}
}
public abstract class MySuperTarget<D extends Data> {
private D data;
public D getData() {
return data;
}
}
This is how I am trying to implement the transformation:
rule TransformTest
transform ms : Origin!MySource
to md : Destination!MyTarget {
md.data ::= ms.data;
md.data.id = ms.data.id;
}
When module.execute() runs, it fails with the following exception:
Property 'data' not found in object org.eclipse.test.MyTarget@7aa85607
at (bundleresource://14.fwk154482552/org/eclipse/test/transformation/TransformTest.etl@5:4-5:27)
at (unknown@0:0-0:0)
The message is clear that it does not find the data property in the target. After some debugging, I found out that OperationContributorRegistry.findContributedMethodForEvaluatedParameters(...) looks for a MyTarget.setData() method, but it does not really exist because the class is normally instantiated by Gson, which uses the fields directly, instead of setter methods. Anyway, I added a setData() method for test purposes with Epsilon, and now the error changes to the following:
Called feature id on undefined object
at (bundleresource://14.fwk342597804/org/eclipse/test/transformation/TransformTest.etl@6:4-6:31)
at (unknown@0:0-0:0)
Two questions:
- It looks like the script is trying to call setId() on the MyTarget.data, which is null. Why is the object null if I just set it to the equivalent() of the source object by using the special assignment on the previous line?
- I tried to create another specific rule to transform MySourceData to MyTarget.Data, but the editor complains about the dot - so should I assume that nested classes are simply not supported as explicit targets of transformation rules?
Thanks,
Flavio
[Updated on: Tue, 22 May 2018 03:04] Report message to a moderator
|
|
| |
Re: Feature not found in undefined object [message #1787249 is a reply to message #1787226] |
Tue, 22 May 2018 17:56 |
Flavio Costa Messages: 23 Registered: February 2018 |
Junior Member |
|
|
Hi Dimitris,
I have created a minimal working example for this situation, which gives the same problem as reported above - project (plugin) files attached here.
I am using Epsilon 1.4.0.201611012202 with Java 8.
After importing the project, you can right click on org.eclipse.epsilon.mwe.EtlTest, then Run As -> Java Application. It will run the main method, which gives out the expected error:
Called feature id on undefined object
at (...\org.eclipse.epsilon.mwe\target\classes\org\eclipse\epsilon\mwe\TransformTest.etl@6:4-6:28)
at (unknown@0:0-0:0)
at org.eclipse.epsilon.eol.dom.PropertyCallExpression.execute(PropertyCallExpression.java:46)
at org.eclipse.epsilon.eol.dom.PropertyCallExpression.execute(PropertyCallExpression.java:40)
at org.eclipse.epsilon.eol.dom.AssignmentStatement.execute(AssignmentStatement.java:52)
at org.eclipse.epsilon.eol.execute.ExecutorFactory.executeAST(ExecutorFactory.java:97)
at org.eclipse.epsilon.eol.dom.StatementBlock.execute(StatementBlock.java:49)
at org.eclipse.epsilon.eol.execute.ExecutorFactory.executeAST(ExecutorFactory.java:97)
at org.eclipse.epsilon.eol.dom.ExecutableBlock.executeBlockOrExpressionAst(ExecutableBlock.java:94)
at org.eclipse.epsilon.eol.dom.ExecutableBlock.execute(ExecutableBlock.java:108)
at org.eclipse.epsilon.eol.dom.ExecutableBlock.execute(ExecutableBlock.java:137)
at org.eclipse.epsilon.eol.dom.ExecutableBlock.execute(ExecutableBlock.java:81)
at org.eclipse.epsilon.etl.dom.TransformationRule.executeSuperRulesAndBody(TransformationRule.java:221)
at org.eclipse.epsilon.etl.dom.TransformationRule.transform(TransformationRule.java:175)
at org.eclipse.epsilon.etl.strategy.FastTransformationStrategy.executeTransformations(FastTransformationStrategy.java:165)
at org.eclipse.epsilon.etl.strategy.FastTransformationStrategy.transformModels(FastTransformationStrategy.java:157)
at org.eclipse.epsilon.etl.EtlModule.execute(EtlModule.java:133)
at org.eclipse.epsilon.mwe.EtlTest.main(EtlTest.java:41)
Maybe it's just a problem with my ETL script, maybe it's on the way I load the models, or due to the nested class in the target... not sure, so any support would be appreciated.
Thanks,
Flavio
|
|
|
Re: Feature not found in undefined object [message #1787254 is a reply to message #1787249] |
Tue, 22 May 2018 19:27 |
|
Hi Flavio,
In your transformation, md.data ::= ms.data; is a shorthand for md.data = ms.data.eqivalent(). As there's no rule in your transformation that transforms instances of MySourceData, the right hand side of the assignment returns null, which is the expected behaviour, hence the NPE in the following line. Does this help?
Cheers,
Dimitris
|
|
| |
Re: Feature not found in undefined object [message #1787290 is a reply to message #1787267] |
Wed, 23 May 2018 08:27 |
|
Hi Flavio,
You should be able to add your nested class to the classes of your JavaModel i.e. replace
classes = Collections.singletonList(MyTarget.class);
with
classes = Arrays.asList(MyTarget.class, MyTarget.Data.class);
and then refer to your nested class as Destination!Data in the transformation.
Cheers,
Dimitris
|
|
|
Re: Feature not found in undefined object [message #1787339 is a reply to message #1787290] |
Wed, 23 May 2018 20:06 |
Flavio Costa Messages: 23 Registered: February 2018 |
Junior Member |
|
|
Dimitris,
It seems to work, thanks for the help! I would prefer to be able to reference it in the transformation as Destination!MyTarget.Data instead, as it would avoid ambiguity in case we have MyTarget.Data and MyOtherTarget.Data, but I hope that won't be an issue with my current model. I also had to make the nested class static, else Epsilon was not able to instantiate it, but that would also be a minor functional improvement.
My main concern now is that Epsilon forces me to provide setters for any properties used in the transformation. My target model is basically read-only, as it's derived from the editable source model, so I don't need and don't want to add setters everywhere so the person working on the client application using the model does not get confused by those.
I see that JavaPropertySetter looks specifically for a setX() method, so the transformation does not work without these. As I mentioned on my initial message above, Gson uses reflection to set private fields even if a setter is not exposed, and I would like to have that feature on Epsilon as well. Is there a way for me to write my own AbstractPropertySetter to implement this feature, or can I add a OperationContributor to do that? Else, should I create a new enhancement request in Bugzilla?
Thanks,
Flavio
[Updated on: Wed, 23 May 2018 20:46] Report message to a moderator
|
|
| |
Re: Feature not found in undefined object [message #1787453 is a reply to message #1787344] |
Fri, 25 May 2018 15:34 |
Flavio Costa Messages: 23 Registered: February 2018 |
Junior Member |
|
|
Hi Dimitris,
It was actually fairly easy to implement it! A JavaFieldPropertySetter implementation extending JavaPropertySetter did the trick, with 82 lines of code, and it seems to work very well in my initial testing (it would be even less if org.eclipse.epsilon.eol.execute.introspection.java.ObjectField defined a constructor to set its properties.
Since I was already extending JavaModel, I also added some reflection functionality to automatically identify the classes and object instances to be added to the model, starting from its root element. Therefore, this code:
MySource sourceObj = new MySource("My data id");
Collection<Object> objects = Arrays.asList(sourceObj, sourceObj.getData());
Collection<Class<?>> classes = Arrays.asList(MySource.class, MySourceData.class);
JavaModel javaModel = new JavaModel("Origin", sourceObj, sourceObj.getData());
Can be replaced by one single line of code:
JavaModel javaModel = new ReflectiveJavaModel("Origin", new MySource("My data id"));
The same can be done for the target model, I can now load it with one line and reflection does its magic internally:
JavaModel javaModel = new ReflectiveJavaModel("Destination", Collections.singleton(MyTarget.class));
The other two changes I'd like to see, namely the support for non-static inner classes and explicitly specifying the name of the enclosing class in the ETL rule (e.g. Destination!MyTarget$Data), would apparently be more complicated. It might require changes to FastTransformationStrategy, EolModelElementType, and probably other places.
I am happy with the results, I am attaching the final code here for your appreciation. It would be good if at least JavaFieldPropertySetter is added to Epsilon's code base, since it's generic enough and could be useful for anyone using read-only target Java models.
Thanks for all the assistance,
Flavio
|
|
| | | |
Goto Forum:
Current Time: Mon Sep 23 01:14:31 GMT 2024
Powered by FUDForum. Page generated in 0.05023 seconds
|