Skip to main content
summaryrefslogtreecommitdiffstats
blob: bccac31e4c193decfb247898e567308e81722b09 (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
/*******************************************************************************
 * Copyright (c) 2016-2017, Thales Global Services S.A.S.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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 com.google.common.base.Preconditions
import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
import java.util.List
import java.util.Map
import org.eclipse.emf.diffmerge.patch.AttributeEntry
import org.eclipse.emf.diffmerge.patch.ElementEntry
import org.eclipse.emf.diffmerge.patch.ReferenceEntry
import org.eclipse.emf.diffmerge.patch.api.ChangeDirection
import org.eclipse.emf.diffmerge.patch.api.ModelPatch
import org.eclipse.emf.diffmerge.patch.api.ModelPatchDiagnosticElement
import org.eclipse.emf.diffmerge.patch.api.ModelPatchEntry
import org.eclipse.emf.diffmerge.patch.api.PatchApplication
import org.eclipse.emf.diffmerge.patch.api.PatchApplicationDiagnostic
import org.eclipse.emf.diffmerge.patch.runtime.identifier.IdentifiedEMFObjectLocator
import org.eclipse.emf.diffmerge.patch.runtime.modelaccess.EMFModelAccess
import org.eclipse.emf.ecore.EAttribute
import org.eclipse.emf.ecore.EClassifier
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EReference
import org.eclipse.emf.ecore.EStructuralFeature
import org.eclipse.emf.ecore.resource.ResourceSet
import org.eclipse.emf.ecore.util.EcoreUtil
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtend.lib.annotations.Data

public class EMFPatchApplication implements PatchApplication {
  @Accessors(PUBLIC_GETTER)
  val EMFModelAccess modelAccess
  @Accessors(PUBLIC_GETTER)
  val ResourceSet input
  @Accessors(PUBLIC_GETTER,PROTECTED_SETTER)
  IdentifiedEMFObjectLocator locator
  val ModelPatch modelPatch
  val PatchApplicationDiagnostic diagnostics
  val Map<String, EObject> storage
  val Multimap<FeatureHolder, EObject> addedOpposites = HashMultimap.create
  val Multimap<FeatureHolder, EObject> removedOpposites = HashMultimap.create

  protected new(ModelPatch modelPatch, EMFModelAccess modelAccess, ResourceSet input) {
    Preconditions.checkNotNull(modelPatch, "Model patch cannot be null")
    Preconditions.checkNotNull(modelAccess, "Model access cannot be null")
    Preconditions.checkNotNull(input, "Input cannot be null")

    this.modelPatch = modelPatch
    this.modelAccess = modelAccess
    this.input = input
    this.diagnostics = new PatchApplicationDiagnostic
    this.storage = newHashMap
    this.locator = new IdentifiedEMFObjectLocator()
  }

  protected def void apply() {
    val List<ModelPatchEntry> entries = modelPatch.entries
    for (ModelPatchEntry entry : entries) {
      try {
        val contextId = entry.context.identifier

        switch (entry.entryType) {
          case ELEMENT:  applyElementEntry(contextId, entry)
          case REFERENCE: applyReferenceEntry(contextId, entry)
          case ATTRIBUTE: applyAttributeEntry(contextId, entry)
        }
      } catch (Exception ex) {
        val diagnosticElement = new ModelPatchDiagnosticElement()
        diagnosticElement.setCaughtException(ex)
        diagnosticElement.setProblematicEntry(entry)
        diagnosticElement.setMessage(ex.message)
        diagnostics.addElement(diagnosticElement)
      }
    }
  }

  def private void applyElementEntry(String elementId, ModelPatchEntry entry) {
    val elementEntry = (entry as ElementEntry)
    val typeId = elementEntry.type.identifier
    val locatedType = locator.locateEClassifier(typeId)
    if (entry.direction == ChangeDirection.ADD) {
      // create object and put into storage
      if(!locatedType.present){
        throw new IllegalStateException('''Could not find EClassifier''')
      }
      val type = locatedType.get
      val existingId = findEObjectById(elementId)
      if(existingId.present){
        throw new IllegalStateException('''Cannot create element with existing id''')
      }
      val createdElement = createElement(elementId, type)
      storage.put(elementId, createdElement)
    } else {
      // locate element and remove from model
      val element = findEObjectById(elementId)
      if (element.isPresent()) {
        modelAccess.remove(element.get())
      }
      // if the element is not in the model, delete it from the storage
      val eObject = storage.remove(elementId)
      if (eObject == null) {
        throw new IllegalStateException('''Could not find element in storage''')
      }
    }
  }

  def private void applyReferenceEntry(String contextId, ModelPatchEntry entry) {
    // Find the requested element
    val locatedContext = findEObjectById(contextId)
    val referenceEntry = (entry as ReferenceEntry)
    // Find the reference
    val featureId = referenceEntry.feature.identifier
    val locatedEStructuralFeature = locator.locateEStructuralFeature(featureId)
    // owner = the element with the reference
    if(!locatedContext.present){
            throw new IllegalStateException('''Could not find owner object''')
        }
    val owner = locatedContext.get
    // feature = the reference to modify
    if(!locatedEStructuralFeature.present){
      throw new IllegalStateException('''Could not find feature''')
    }
    val feature = locatedEStructuralFeature.get
    // The feature should be a reference for a ReferenceEntry
    if (!(feature instanceof EReference)) {
      throw new IllegalStateException('''Feature is not a reference''')
    }
    val reference = (feature as EReference)
    val targetId = referenceEntry.target.identifier
    var target = storage.get(targetId)
    if (target == null) {
      val locatedTargetObject = findEObjectById(targetId)
      target = locatedTargetObject.orNull
    }
    if (target == null) {
      throw new IllegalStateException('''Could not find target object''')
    }
    val index = referenceEntry.index
    if (referenceEntry.direction == ChangeDirection.ADD) {
      setOrAdd(owner, reference, target, index)
    } else {
      unsetOrRemove(owner, reference, target, index)
      // put removed element to the storage
      if (reference.isContainment()) {
        storage.put(targetId, target)
      }
    }
  }

  def private void applyAttributeEntry(String elementId, ModelPatchEntry entry) {
    val locatedEObject = findEObjectById(elementId)
    val attributeEntry = (entry as AttributeEntry)
    val featureId = attributeEntry.feature.identifier
    val locatedEStructuralFeature = locator.locateEStructuralFeature(featureId)
    if (!locatedEObject.isPresent()) {
      throw new IllegalStateException('''Could not find owner object''')
    }
    if (!locatedEStructuralFeature.isPresent()) {
      throw new IllegalStateException('''Could not find feature''')
    }
    val owner = locatedEObject.get()
    val feature = locatedEStructuralFeature.get()
    var Object value = null
    if (feature instanceof EAttribute) {
      val attributeType = ((feature as EAttribute)).getEAttributeType()
      value = EcoreUtil.createFromString(attributeType, attributeEntry.value)
    } else {
      throw new IllegalStateException('''Feature is not an attribute''')
    }
    val index = attributeEntry.index
    if (entry.direction == ChangeDirection.ADD) {
      setOrAdd(owner, feature, value, index)
    } else {
      unsetOrRemove(owner, feature, value, index)
    }
  }

  def private EObject createElement(String elementId, EClassifier type) {
    val eObject = modelAccess.create(type)
    EcoreUtil.setID(eObject, elementId)
    return eObject
  }

  def private void setOrAdd(EObject owner, EStructuralFeature feature, Object value, Optional<Integer> index) {
    val holder = new FeatureHolder(owner, feature)
    if (addedOpposites.containsKey(holder)
      && addedOpposites.get(holder).contains(value)
    ) {
      modelAccess.changeIndex(owner, feature, value, index)
    } else {
      modelAccess.add(owner, feature, value, index)

      if (feature instanceof EReference) {
        val opposite = feature.EOpposite
        if (opposite != null) {
          val oppositeHolder = new FeatureHolder(value as EObject, opposite)
          addedOpposites.put(oppositeHolder, owner)
        }
      }
    }
  }

  def private void unsetOrRemove(EObject owner, EStructuralFeature feature, Object value, Optional<Integer> index) {
    val holder = new FeatureHolder(owner, feature)
    if (removedOpposites.containsKey(holder)
      && removedOpposites.get(holder).contains(value)
    ) {
      return
    } else {
      modelAccess.remove(owner, feature, value, index)
      if (feature instanceof EReference) {
        val opposite = feature.EOpposite
        if (opposite != null) {
          val oppositeHolder = new FeatureHolder(value as EObject, opposite)
          removedOpposites.put(oppositeHolder, owner)
        }
      }
    }
  }

  def private Optional<EObject> findEObjectById(String identifier) {
    Preconditions.checkNotNull(identifier, "Identifier cannot be null")
    var eObject = storage.get(identifier)
    if (eObject == null) {
      val locatedEObject = locator.locateEObject(identifier)
      if (locatedEObject.isPresent()) {
        return locatedEObject
      }
    }
    return Optional.fromNullable(eObject)
  }

  override getDiagnostics() {
    return diagnostics
  }

  override getModelPatch() {
    return modelPatch
  }

}

@Data
class FeatureHolder {
  EObject eObject
  EStructuralFeature feature
}

Back to the top