Problem:
I'm using EML and Epsilon Language Workbench to merge two project models, represented by two metamodels (MM1 and MM2), into a third metamodel (target). While I can achieve a simple merge based on element names, I need a more complex rule to assign tasks from the second model (M2) to people from the first model (M1) based on certain conditions.
Desired Outcome:
For each instance of People p in M1, I want to assign Tasks t in M2 only when p already worked on more than 2 tasks in M1.
Current Attempt:
I have created Epsilon programs (program.eml and program.ecl) to perform the merge, and I am using the Epsilon playground for testing. My example was adapted to be pretty similar to the one used as standard in the playground.
Metamodels:
MM1 (left.emf)
MM2 (right.emf)
Target Metamodel (target.emf)
Models:
- M1 (left.flexmi)
- M2 (right.flexmi)
Epsilon Programs:
- program.eml
- program.ecl
Metamodel 1 - MM1 (left.emf):
@namespace(uri="psl", prefix="")
package psl;
class Project {
attr String title;
attr String description;
val Task[*] tasks;
@diagram(direction="right")
val Person[*] people;
}
class Task {
attr String title;
attr int start;
attr int duration;
@diagram(direction="right")
val Effort[*] effort;
}
class Person {
attr String name;
}
class Effort {
@diagram(direction="up")
ref Person person;
attr int percentage = 100;
}
Metamodel 2 - MM2 (right.emf)
@namespace(uri="psl", prefix="")
package psl;
class Project {
attr String title;
attr String description;
val Task[*] tasks;
@diagram(direction="right")
val Person[*] people;
}
class Task {
attr String title;
attr int start;
attr int duration;
@diagram(direction="right")
val Effort[*] effort;
}
class Person {
attr String name;
}
class Effort {
@diagram(direction="up")
ref Person person;
attr int percentage = 100;
}
Model 1 - M1 (left.flexmi)
<?nsuri psl?>
<project title="ACME">
<person name="Alice"/>
<person name="Bob"/>
<task title="Analysis" start="1" dur="3">
<effort person="Alice"/>
</task>
<task title="Design" start="4" dur="6">
<effort person="Bob"/>
</task>
<task title="Implementation" start="7" dur="3">
<effort person="Bob" perc="50"/>
<effort person="Alice" perc="50"/>
</task>
</project>
Model 2 - M2 (right.flexmi)
<?nsuri psl?>
<project title="ACME">
<person name="Alice"/>
<person name="Bob"/>
<task title="Testing" start="10" dur="3">
<effort person="Alice" perc="50"/>
</task>
</project>
Target metamodel (target.emf)
package psl;
class Project {
attr String title;
attr String description;
val Task[*] tasks;
@diagram(direction="right")
val Person[*] people;
}
class Task {
attr String title;
attr int start;
attr int duration;
@diagram(direction="right")
val Effort[*] effort;
}
class Person {
attr String name;
}
class Effort {
@diagram(direction="up")
ref Person person;
attr int percentage = 100;
}
program.eml
// This EML program merges two
// project plan models as follows:
// - Persons are merged based on name
// - Tasks are not merged
// Matched projects are merged
// into a single project
rule ProjectWithProject
merge l : Left!Project
with r : Right!Project
into m : Merged!Project {
m.title = l.title;
m.people ::= l.people + r.people;
m.tasks ::= l.tasks + r.tasks;
}
// Matched persons are merged
// into a single person
rule PersonWithPerson
merge l : Left!Person
with r : Right!Person
into m : Merged!Person {
m.name = l.name;
}
// Tasks are not merged
// They are copied from the left
// and the right model to the
// merged model
rule TaskWithTask
transform s : Source!Task
to t : Target!Task {
t.title = s.title;
t.start = s.start;
t.duration = s.duration;
t.effort ::= s.effort;
}
//merge efforts in the task ONLY when the people in the right worked in at least one task (i.e. has effort) in the left
rule EffortWithEffort
merge l : Left!Effort
with r : Right!Effort
into m : Merged!Effort {
m.person ::= l.person;
m.percentage = l.percentage;
}
// Persons and Efforts found in only one of the
// two models are copied across
// to the merged model
rule Person2Person
transform s : Source!Person
to t : Target!Person {
t.name = s.name;
}
rule Effort2Effort
transform s : Source!Effort
to t : Target!Effort {
t.person ::= s.person;
t.percentage = s.percentage;
}
program.ecl
// We match persons by name
rule PersonWithPerson
match l : Left!Person
with r : Right!Person {
compare: l.name = r.name
}
rule EffortWithEffort
match l : Left!Person
with r : Right!Person {
compare: l.tasks->collect(e | e.effort)
->flatten()
->excluding(l)
->collect(e | e.effort)
->flatten()
->count(r) >= 1
}
// We expect only one project
// in each model and therefore
// we match them unconditionally
rule ProjectWithProject
match l : Left!Project
with r : Right!Project {
compare: true
}
I've tried using the existing rules, but they are not achieving the desired outcome. How can I modify the Epsilon programs to get the expected merged model?
Any help or suggestions would be greatly appreciated!
Updating the Task2Task
transformation rule as follows should do the trick:
// Tasks are not merged
// They are copied from the left
// and the right model to the
// merged model
rule Task2Task
transform s : Source!Task
to t : Target!Task {
t.title = s.title;
t.start = s.start;
t.duration = s.duration;
// Persons participating in 2+ tasks
// are assigned to all tasks in the model
for (p in Source!Person.all) {
if (Source!Task.all.select(st|st.effort.exists(e|e.person = p)).size() >= 2) {
var e = new Merged!Effort;
t.effort.add(e);
e.person = p.equivalent();
}
}
}
A runnable version of the updated code is here: https://eclipse.dev/epsilon/playground/?53c08479