Skip to main content
summaryrefslogtreecommitdiffstats
blob: cfab380ac2167274cd201290b8354e04abecc9ea (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
/*******************************************************************************
 * Copyright (c) 2016-2017, Thales Global Services S.A.S.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Abel Hegedus, Tamas Borbas, Balazs Grill, Peter Lunk, Daniel Segesdi (IncQuery Labs Ltd.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.emf.diffmerge.patch.runtime

import com.google.common.base.Optional
import java.util.Comparator
import java.util.Set
import org.eclipse.emf.common.util.EList
import org.eclipse.emf.diffmerge.api.IComparison
import org.eclipse.emf.diffmerge.api.Role
import org.eclipse.emf.diffmerge.api.diff.IDifference
import org.eclipse.emf.diffmerge.api.diff.IPresenceDifference
import org.eclipse.emf.diffmerge.diffdata.EAttributeValuePresence
import org.eclipse.emf.diffmerge.diffdata.EElementPresence
import org.eclipse.emf.diffmerge.diffdata.EReferenceValuePresence
import org.eclipse.emf.diffmerge.patch.api.ChangeDirection
import org.eclipse.emf.diffmerge.patch.api.Identifiable
import org.eclipse.emf.diffmerge.patch.api.ModelPatch
import org.eclipse.emf.diffmerge.patch.api.ModelPatchBuilder
import org.eclipse.emf.diffmerge.patch.api.ModelPatchEntry
import org.eclipse.emf.diffmerge.patch.api.ModelPatchEntryBuilder
import org.eclipse.emf.diffmerge.patch.api.ModelPatchException
import org.eclipse.emf.diffmerge.patch.runtime.identifier.EMFIdentifierProvider
import org.eclipse.emf.ecore.EAttribute
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EReference
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.emf.ecore.util.EcoreUtil
import org.eclipse.xtend.lib.annotations.Accessors

class ModelPatchRecorder {
  @Accessors
  extension EMFIdentifierProvider identifierProvider = new EMFIdentifierProvider

  private ModelPatchBuilder attributeRemoveBuilder
  private ModelPatchBuilder referenceRemoveBuilder
  private ModelPatchBuilder containmentRemoveBuilder
  private ModelPatchBuilder secondContainmentRemoveBuilder
  private ModelPatchBuilder elementRemoveBuilder

  private ModelPatchBuilder elementAddBuilder
  private ModelPatchBuilder containmentAddBuilder
  private ModelPatchBuilder referenceAddBuilder
  private ModelPatchBuilder attributeAddBuilder

  private ModelPatchBuilder modelPatchBuilder

  private Set<ModelPatchEntry> addedEntries = newHashSet();

  def ModelPatch generateModelPatch(IComparison comparison) {
    val differences = (<IDifference>newArrayList => [
      it += comparison.getDifferences(Role.REFERENCE)
      it += comparison.getDifferences(Role.TARGET)
    ])
    return differences.generateModelPatch
  }

  def ModelPatch generateModelPatch(Iterable<? extends IDifference> diffs) {
    val differences = <IDifference>newArrayList(diffs).sortInplace(DiffComparator.INSTANCE)
    initModelPatchBuilders()
    differences.forEach[
      processDifference
    ]

    val modelPatch = modelPatchBuilder.appendNewEntries(attributeRemoveBuilder)
                    .appendNewEntries(referenceRemoveBuilder)
                    .appendNewEntries(containmentRemoveBuilder)
                    .appendNewEntries(secondContainmentRemoveBuilder)
                    .appendNewEntries(elementRemoveBuilder)
                    .appendNewEntries(elementAddBuilder)
                    .appendNewEntries(containmentAddBuilder)
                    .appendNewEntries(referenceAddBuilder)
                    .appendNewEntries(attributeAddBuilder)
                    .build

    return modelPatch
  }

  protected def initModelPatchBuilders() {
    attributeRemoveBuilder = ModelPatchBuilder.create
    referenceRemoveBuilder = ModelPatchBuilder.create
    containmentRemoveBuilder = ModelPatchBuilder.create
    secondContainmentRemoveBuilder = ModelPatchBuilder.create
    elementRemoveBuilder = ModelPatchBuilder.create
    elementAddBuilder = ModelPatchBuilder.create
    containmentAddBuilder = ModelPatchBuilder.create
    referenceAddBuilder = ModelPatchBuilder.create
    attributeAddBuilder = ModelPatchBuilder.create
    modelPatchBuilder = ModelPatchBuilder.create
  }

  private def void processDifference(IDifference diff) {
    if(diff instanceof IPresenceDifference) {
      val mergeDestination = Role.REFERENCE
      // which model contains the presence
      val presenceRole = diff.presenceRole
      val direction = presenceRole.changeDirection

      processDifference(diff, mergeDestination, presenceRole, direction)
    }
  }

  private dispatch def void processDifference(IDifference diff, Role mergeDestination, Role presenceRole, ChangeDirection direction) {
    throw new ModelPatchException('''Unsupported IDifference descendant: «diff.class.name»''')
  }

  private dispatch def void processDifference(EAttributeValuePresence diff, Role mergeDestination, Role presenceRole, ChangeDirection direction) {
    val owner = diff.elementMatch.get(mergeDestination)
    val feature = diff.feature
    val value = diff.value
    val eAttributeType = (feature as EAttribute).getEAttributeType()

    val ownerId = new Identifiable(owner.id)
    val featureId = new Identifiable(feature.identify)
    val stringValue = EcoreUtil.convertToString(eAttributeType, value);

    val presenceOwner = diff.elementMatch.get(presenceRole)
    val index = presenceOwner.indexOf(value, feature)

    val entryBuilder = ModelPatchBuilder.entryBuilder(ownerId, direction) => [
      it.feature = featureId
      it.value = stringValue
      it.index = index
    ]
    val attributeEntry = entryBuilder.buildAttributeEntry

    if(direction == ChangeDirection.ADD) {
      if(!feature.isMany) {
        val oldValue = owner.eGet(feature)
        if(oldValue !== null){
          // create REMOVE if it already has value
          val oldStringValue = EcoreUtil.convertToString(eAttributeType, oldValue);
          val removeEntry = (entryBuilder => [
            it.direction = ChangeDirection.REMOVE
            it.value = oldStringValue
          ]).buildAttributeEntry
          attributeRemoveBuilder.addNewEntry(removeEntry)
        }
      }
      attributeAddBuilder.addNewEntry(attributeEntry)
    } else {
      attributeRemoveBuilder.addNewEntry(attributeEntry)
    }
  }

  private def Optional<Integer> indexOf(EObject presenceOwner, Object value, EStructuralFeature feature) {
    if(feature.isMany) {
      val values = presenceOwner.eGet(feature) as EList
      val index = values.indexOf(value)
      if(index < 0) {
        throw new IllegalStateException("The object is not found in the containing list.")
      } else {
        return Optional.of(index)
      }
    }
    return Optional.absent
  }

  private dispatch def void processDifference(EReferenceValuePresence diff, Role mergeDestination, Role presenceRole, ChangeDirection direction) {
    val owner = diff.elementMatch.get(mergeDestination)
    val feature = diff.feature as EReference
    val value = if(diff.valueMatch !== null){
        diff.valueMatch.get(presenceRole)
    } else {
        diff.value
    }

    val ownerId = new Identifiable(owner.id)
    val featureId = new Identifiable(feature.identify)
    val targetId = new Identifiable(getId(value))

    val presenceOwner = diff.elementMatch.get(presenceRole)
    val index = presenceOwner.indexOf(value, feature)

    val entryBuilder = ModelPatchBuilder.entryBuilder(ownerId, direction) => [
        it.feature = featureId
        it.target = targetId
        it.index = index
    ]
    val referenceEntry = entryBuilder.buildReferenceEntry

    if(direction == ChangeDirection.ADD) {
      if(!feature.isMany) {
        val oldValue = owner.eGet(feature)
        if(oldValue !== null) {
          if(oldValue instanceof EObject){
            val oldValueId = new Identifiable(oldValue.id)
            // create REMOVE if it already has value
            val removeEntry = (entryBuilder => [
              it.direction = ChangeDirection.REMOVE
              it.target = oldValueId
            ]).buildReferenceEntry
            if(feature.isContainment) {
              secondContainmentRemoveBuilder.addNewEntry(removeEntry)
            } else {
              referenceRemoveBuilder.addNewEntry(removeEntry)
            }
          }
        }
      }
      if(feature.isContainment) {
        containmentAddBuilder.addNewEntry(referenceEntry)
      } else {
        referenceAddBuilder.addNewEntry(referenceEntry)
      }
    } else {
      if(feature.isContainment) {
        secondContainmentRemoveBuilder.addNewEntry(referenceEntry)
      } else {
        referenceRemoveBuilder.addNewEntry(referenceEntry)
      }
    }
  }

  private dispatch def void processDifference(EElementPresence diff, Role mergeDestination, Role presenceRole, ChangeDirection direction) {
    val element = diff.element
    var parent = diff.ownerMatch.get(mergeDestination)
    val ownershipDifference = diff.elementMatch.getOwnershipDifference(presenceRole)
    val containmentHasPresence = ownershipDifference !== null
    var EReference containmentFeature = null
    if(ownershipDifference !== null){
      containmentFeature = ownershipDifference.feature
    } else {
      // in some cases, there is no ownershipDifference
      // this happens when only one role is allowed and
      // the current container feature can be used
      containmentFeature = element.eContainingFeature as EReference
      parent = element.eContainer
    }

    val elementId = new Identifiable(element.id)
    val typeId = new Identifiable(element.eClass.identify)
    val parentId = new Identifiable(parent.id)
    val containmentFeatureId = new Identifiable(containmentFeature.identify)

    val index = element.eContainer.indexOf(element, containmentFeature)

    if(direction == ChangeDirection.ADD){
      val elementEntry = (ModelPatchBuilder.entryBuilder(elementId, direction) => [it.type = typeId]).buildElementEntry
      elementAddBuilder.addNewEntry(elementEntry)

      val entryBuilder = ModelPatchBuilder.entryBuilder(parentId, direction) => [
        it.feature = containmentFeatureId
        it.target = elementId
        it.index = index
      ]
      val referenceEntry = entryBuilder.buildReferenceEntry
      if(!containmentFeature.isMany){
        val oldValue = parent.eGet(containmentFeature)
        if(oldValue !== null && oldValue != element) {
          if(oldValue instanceof EObject){
            val oldValueId = new Identifiable(oldValue.id)
            // create REMOVE if it already has value
            val removeEntry = (entryBuilder => [
              it.direction = ChangeDirection.REMOVE
              it.target = oldValueId
            ]).buildReferenceEntry
            containmentRemoveBuilder.addNewEntry(removeEntry)
          }
        }
      }
      if(!containmentHasPresence) {
        containmentAddBuilder.addNewEntry(referenceEntry)
      }
      entryBuilder => [
        it.direction = ChangeDirection.ADD
        it.context = elementId
        it.index = Optional.absent
      ]
      // We need to save the attributes (create add entries)
      element.saveAllAttribute(attributeAddBuilder, entryBuilder)

      // We need to save the non-containment outward references (create add entries)
      element.saveAllNonContainmentRef(referenceAddBuilder, entryBuilder)

    } else {
      val elementAsContextEntryBuilder = ModelPatchBuilder.entryBuilder(elementId, direction)
      // We need to save the attributes (create remove entries)
      element.saveAllAttribute(attributeRemoveBuilder, elementAsContextEntryBuilder)

      // We need to save the non-containment outward references (create remove entries)
      element.saveAllNonContainmentRef(referenceRemoveBuilder, elementAsContextEntryBuilder)

      if(!containmentHasPresence) {
        // We need to create a new entry to remove the containment
        val removeableContainmentEntry = (ModelPatchBuilder.entryBuilder(parentId, direction) => [
          it.feature = containmentFeatureId
          it.target = elementId
          it.index = index
        ]).buildReferenceEntry
        containmentRemoveBuilder.addNewEntry(removeableContainmentEntry)
      }
      val elementRemove = (elementAsContextEntryBuilder => [
        it.type = typeId
      ]).buildElementEntry
      elementRemoveBuilder.addNewEntry(elementRemove)
    }
  }

  private def void saveAllAttribute(EObject element, ModelPatchBuilder patchBuilder, ModelPatchEntryBuilder entryBuilder) {
    for(eAttr : element.eClass.EAllAttributes.filter[!it.isID]) {
      if(element.eIsSet(eAttr)) {
        val value = element.eGet(eAttr)
        if(value instanceof EList) {
          for(v : value) {
            val index = element.indexOf(v, eAttr)
            val addedAttributeEntry = (entryBuilder => [
              it.feature = new Identifiable(eAttr.identify)
              it.value = EcoreUtil.convertToString(eAttr.getEAttributeType(), v);
              it.index = index
            ]).buildAttributeEntry
            patchBuilder.addNewEntry(addedAttributeEntry)
          }
        } else {
          val index = element.indexOf(value, eAttr)
          val addedAttributeEntry = (entryBuilder => [
            it.feature = new Identifiable(eAttr.identify)
            it.value = EcoreUtil.convertToString(eAttr.getEAttributeType(), value);
            it.index = index
          ]).buildAttributeEntry
          patchBuilder.addNewEntry(addedAttributeEntry)
        }
      }
    }
  }
  private def void saveAllNonContainmentRef(EObject element, ModelPatchBuilder patchBuilder, ModelPatchEntryBuilder entryBuilder) {
    for(eRef : element.eClass.EAllReferences.filter[!it.isContainment && !it.derived && it.changeable && (it.EOpposite === null || it.EOpposite.isDerived || !it.EOpposite.isContainment)]) {
      val value = element.eGet(eRef)
      if(value !== null) {
        if(value instanceof EObject) {
          val index = element.indexOf(value, eRef)
          val removeableReferenceEntry = (entryBuilder => [
            it.feature = new Identifiable(eRef.identify)
            it.target = new Identifiable(value.id)
            it.index = index
          ]).buildReferenceEntry
          patchBuilder.addNewEntry(removeableReferenceEntry)
        } else if(value instanceof EList) {
          for(v : value) {
            if(v instanceof EObject) {
              val index = element.indexOf(v, eRef)
              val referenceEntry = (entryBuilder => [
                it.feature = new Identifiable(eRef.identify)
                it.target = new Identifiable(v.id)
                it.index = index
              ]).buildReferenceEntry
              patchBuilder.addNewEntry(referenceEntry)
            }
          }
        }
      }
    }
  }

  private def addNewEntry(ModelPatchBuilder builder, ModelPatchEntry entry) {
    if(!addedEntries.contains(entry)){
      addedEntries.add(entry)
      return builder.addEntry(entry)
    }
    return builder
  }

  private def appendNewEntries(ModelPatchBuilder target, ModelPatchBuilder source) {
    target.append(source)
    return target
  }

  private def ChangeDirection getChangeDirection(Role scopeDiff) {
    if(scopeDiff == Role.TARGET){
      return ChangeDirection.ADD
    }
    return ChangeDirection.REMOVE
  }

  private def String getId(EObject eObject) {
    return identifierProvider.identifyEObject(eObject)
  }

  protected static class DiffComparator implements Comparator<IDifference> {
    public static val ADD_ATTRIBUTE_PRIORITY = 7
    public static val ADD_REFERENCE_PRIORITY = 6
    public static val ADD_ELEMENT_PRIORITY = 5
    public static val ADD_CONTAINMENT_PRIORITY = 4
    public static val REMOVE_CONTAINMENT_PRIORITY = 3
    public static val REMOVE_ELEMENT_PRIORITY = 2
    public static val REMOVE_REFERENCE_PRIORITY = 1
    public static val REMOVE_ATTRIBUTE_PRIORITY = 0

    public static def getINSTANCE() {
      return new DiffComparator()
    }

    override compare(IDifference o1, IDifference o2) {
      val result = o1.priority-o2.priority
      if(result != 0) {
        return result
      }
      return o1.compareTo(o2)
    }

    dispatch def int compareTo(IDifference o1, IDifference o2) {
      return 0
    }
    dispatch def int compareTo(EElementPresence o1, EElementPresence o2) {
      if(o1.priority == ADD_ELEMENT_PRIORITY) {
        // If differences are additions
        return o1.element.deepness-o2.element.deepness
      } else {
        // If differences are deletions
        return o2.element.deepness-o1.element.deepness
      }
    }
    dispatch def int compareTo(EReferenceValuePresence o1, EReferenceValuePresence o2) {
      if(o1.priority == ADD_CONTAINMENT_PRIORITY) {
        // If differences are deletions
        return o1.valueMatch.target.deepness-o2.valueMatch.target.deepness
      } else if(o1.priority == REMOVE_CONTAINMENT_PRIORITY) {
        // If differences are additions
        return o2.valueMatch.reference.deepness-o1.valueMatch.reference.deepness
      }
      return 0
    }

    private def int getDeepness(EObject eObject) {
      if(eObject.eContainer === null) {
        return 0
      }
      return eObject.eContainer.deepness+1
    }

    dispatch def int getPriority(IDifference diff) {
      throw new ModelPatchException("Unsupported difference type")
    }
    dispatch def int getPriority(EAttributeValuePresence diff) {
      if(Role.TARGET == diff.presenceRole) {
        return ADD_ATTRIBUTE_PRIORITY
      }
      return REMOVE_ATTRIBUTE_PRIORITY
    }
    dispatch def int getPriority(EElementPresence diff) {
      if(Role.TARGET == diff.presenceRole) {
        return ADD_ELEMENT_PRIORITY
      }
      return REMOVE_ELEMENT_PRIORITY
    }
    dispatch def int getPriority(EReferenceValuePresence diff) {
      if(Role.TARGET == diff.presenceRole) {
        if((diff.feature as EReference).isContainment) {
          return ADD_CONTAINMENT_PRIORITY
        }
        return ADD_REFERENCE_PRIORITY
      }
      if((diff.feature as EReference).isContainment) {
        return REMOVE_CONTAINMENT_PRIORITY
      }
      return REMOVE_REFERENCE_PRIORITY
    }
  }
}

Back to the top