mergeoclepsilonmodel-driven-developmentmde

Merge Project models through EML using complex comparison rules


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:

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!


Solution

  • 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