1

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!

howlger
35.1k11 gold badges73 silver badges112 bronze badges
asked Jul 18, 2023 at 11:59

1 Answer 1

2
+50

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

answered Jul 20, 2023 at 19:36
Sign up to request clarification or add additional context in comments.

2 Comments

Do you know if it's possible to execute the same task without iterating over all Person instances? I think it will be slow with huge models, right?
You could map persons to tasks in a pre block and then use the map to do the lookup in the rule.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.