mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 16:28:56 +00:00
[serializer/performance] avoid redundant traversal of node model
Signed-off-by: Moritz Eysholdt <moritz.eysholdt@typefox.io>
This commit is contained in:
parent
40016bd0dd
commit
d59869f704
3 changed files with 197 additions and 82 deletions
|
@ -9,7 +9,6 @@ package org.eclipse.xtext.serializer.sequencer;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -34,9 +33,9 @@ import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstra
|
|||
import org.eclipse.xtext.serializer.analysis.ISemanticSequencerNfaProvider.ISemState;
|
||||
import org.eclipse.xtext.serializer.analysis.SerializationContext;
|
||||
import org.eclipse.xtext.serializer.sequencer.ISemanticNodeProvider.INodesForEObjectProvider;
|
||||
import org.eclipse.xtext.serializer.sequencer.ISemanticNodeProvider.ISemanticNode;
|
||||
import org.eclipse.xtext.util.EmfFormatter;
|
||||
import org.eclipse.xtext.util.Pair;
|
||||
import org.eclipse.xtext.util.Triple;
|
||||
import org.eclipse.xtext.util.Tuples;
|
||||
import org.eclipse.xtext.util.formallang.Nfa;
|
||||
import org.eclipse.xtext.util.formallang.NfaUtil;
|
||||
|
@ -101,16 +100,19 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
public class SerializableObject {
|
||||
protected final EObject eObject;
|
||||
protected final ISerializationContext context;
|
||||
protected List<INode>[] nodes;
|
||||
protected List<ISemanticNode>[] nodes;
|
||||
protected final ISemanticNode firstNode;
|
||||
protected boolean[] optional;
|
||||
protected Map<Pair<AbstractElement, Integer>, Boolean> valid = Maps.newHashMap();
|
||||
protected Object[] values;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SerializableObject(ISerializationContext context, EObject eObject, INodesForEObjectProvider nodeProvider) {
|
||||
public SerializableObject(ISerializationContext context, EObject eObject,
|
||||
INodesForEObjectProvider nodeProvider) {
|
||||
super();
|
||||
this.eObject = eObject;
|
||||
this.context = context;
|
||||
this.firstNode = nodeProvider.getFirstSemanticNode();
|
||||
EClass clazz = eObject.eClass();
|
||||
values = new Object[clazz.getFeatureCount()];
|
||||
nodes = new List[clazz.getFeatureCount()];
|
||||
|
@ -119,8 +121,8 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
int featureID = eObject.eClass().getFeatureID(feature);
|
||||
if (feature.isMany())
|
||||
switch (transientValues.isListTransient(eObject, feature)) {
|
||||
case NO:
|
||||
List<INode> nodes1 = Lists.newArrayList();
|
||||
case NO:
|
||||
List<ISemanticNode> nodes1 = Lists.newArrayList();
|
||||
List<?> values1;
|
||||
if (feature instanceof EReference && ((EReference) feature).isResolveProxies()) {
|
||||
values1 = ((InternalEList<?>) eObject.eGet(feature)).basicList();
|
||||
|
@ -128,49 +130,49 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
values1 = (List<?>) eObject.eGet(feature);
|
||||
}
|
||||
for (int i = 0; i < values1.size(); i++)
|
||||
nodes1.add(nodeProvider.getNodeForMultiValue(feature, i, i, values1.get(i)));
|
||||
nodes1.add(nodeProvider.getSemanticNodeForMultiValue(feature, i, i, values1.get(i)));
|
||||
values[featureID] = values1;
|
||||
nodes[featureID] = nodes1;
|
||||
break;
|
||||
case SOME:
|
||||
List<INode> nodes2 = Lists.newArrayList();
|
||||
List<?> values2;
|
||||
if (feature instanceof EReference && ((EReference) feature).isResolveProxies()) {
|
||||
values2 = ((InternalEList<?>) eObject.eGet(feature)).basicList();
|
||||
} else {
|
||||
values2 = (List<?>) eObject.eGet(feature);
|
||||
case SOME:
|
||||
List<ISemanticNode> nodes2 = Lists.newArrayList();
|
||||
List<?> values2;
|
||||
if (feature instanceof EReference && ((EReference) feature).isResolveProxies()) {
|
||||
values2 = ((InternalEList<?>) eObject.eGet(feature)).basicList();
|
||||
} else {
|
||||
values2 = (List<?>) eObject.eGet(feature);
|
||||
}
|
||||
List<Object> values3 = Lists.newArrayList();
|
||||
for (int i = 0, j = 0; i < values2.size(); i++)
|
||||
if (!transientValues.isValueInListTransient(eObject, i, feature)) {
|
||||
Object value = values2.get(i);
|
||||
ISemanticNode node = nodeProvider.getSemanticNodeForMultiValue(feature, i, j++, value);
|
||||
values3.add(value);
|
||||
nodes2.add(node);
|
||||
}
|
||||
List<Object> values3 = Lists.newArrayList();
|
||||
for (int i = 0, j = 0; i < values2.size(); i++)
|
||||
if (!transientValues.isValueInListTransient(eObject, i, feature)) {
|
||||
Object value = values2.get(i);
|
||||
INode node = nodeProvider.getNodeForMultiValue(feature, i, j++, value);
|
||||
values3.add(value);
|
||||
nodes2.add(node);
|
||||
}
|
||||
values[featureID] = values3;
|
||||
nodes[featureID] = nodes2;
|
||||
break;
|
||||
case YES:
|
||||
values[featureID] = INVALID;
|
||||
values[featureID] = values3;
|
||||
nodes[featureID] = nodes2;
|
||||
break;
|
||||
case YES:
|
||||
values[featureID] = INVALID;
|
||||
}
|
||||
else
|
||||
switch (transientValues.isValueTransient(eObject, feature)) {
|
||||
case PREFERABLY:
|
||||
optional[featureID] = true;
|
||||
Object value1 = eObject.eGet(feature, false);
|
||||
values[featureID] = value1;
|
||||
nodes[featureID] = Collections
|
||||
.singletonList(nodeProvider.getNodeForSingelValue(feature, value1));
|
||||
break;
|
||||
case NO:
|
||||
Object value2 = eObject.eGet(feature, false);
|
||||
values[featureID] = value2;
|
||||
nodes[featureID] = Collections
|
||||
.singletonList(nodeProvider.getNodeForSingelValue(feature, value2));
|
||||
break;
|
||||
case YES:
|
||||
values[featureID] = INVALID;
|
||||
case PREFERABLY:
|
||||
optional[featureID] = true;
|
||||
Object value1 = eObject.eGet(feature, false);
|
||||
values[featureID] = value1;
|
||||
nodes[featureID] = Collections
|
||||
.singletonList(nodeProvider.getSemanticNodeForSingelValue(feature, value1));
|
||||
break;
|
||||
case NO:
|
||||
Object value2 = eObject.eGet(feature, false);
|
||||
values[featureID] = value2;
|
||||
nodes[featureID] = Collections
|
||||
.singletonList(nodeProvider.getSemanticNodeForSingelValue(feature, value2));
|
||||
break;
|
||||
case YES:
|
||||
values[featureID] = INVALID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,8 +181,12 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
return eObject;
|
||||
}
|
||||
|
||||
public INode getNode(int featureID, int index) {
|
||||
List<INode> featureNodes = nodes[featureID];
|
||||
public ISemanticNode getFirstNode() {
|
||||
return firstNode;
|
||||
}
|
||||
|
||||
public ISemanticNode getNode(int featureID, int index) {
|
||||
List<ISemanticNode> featureNodes = nodes[featureID];
|
||||
if (featureNodes != null && index >= 0 && index < featureNodes.size())
|
||||
return featureNodes.get(index);
|
||||
return null;
|
||||
|
@ -234,7 +240,8 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
if (valid.get(key) == Boolean.TRUE)
|
||||
return true;
|
||||
|
||||
INode node = getNode(state.getFeatureID(), index);
|
||||
ISemanticNode semanticNode = getNode(state.getFeatureID(), index);
|
||||
INode node = semanticNode == null ? null : semanticNode.getNode();
|
||||
Multimap<AbstractElement, ISerializationContext> assignments = ArrayListMultimap.create();
|
||||
for (AbstractElement ele : candidates)
|
||||
assignments.put(ele, context);
|
||||
|
@ -269,7 +276,7 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
protected static class TraceItem {
|
||||
protected int index;
|
||||
protected int[] nextIndex;
|
||||
protected INode node;
|
||||
protected ISemanticNode node;
|
||||
protected SerializableObject obj;
|
||||
protected TraceItem parent;
|
||||
protected ISemState state;
|
||||
|
@ -335,20 +342,22 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
}
|
||||
|
||||
public AbstractElement getNextGrammarElement() {
|
||||
Iterator<Triple<INode, AbstractElement, EObject>> it;
|
||||
if (obj != null && (parent == null || parent.parent == null))
|
||||
it = new SemanticNodeIterator(obj.getEObject());
|
||||
else if (node == null)
|
||||
return null;
|
||||
else
|
||||
it = new SemanticNodeIterator(node);
|
||||
if (it.hasNext())
|
||||
return it.next().getSecond();
|
||||
ISemanticNode sem = null;
|
||||
if (obj != null && (parent == null || parent.parent == null)) {
|
||||
sem = obj.getFirstNode();
|
||||
} else if (node != null) {
|
||||
sem = node.getFollower();
|
||||
}
|
||||
if (sem != null) {
|
||||
return sem.getGrammarElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public INode getNode() {
|
||||
return node;
|
||||
if (node == null)
|
||||
return null;
|
||||
return node.getNode();
|
||||
}
|
||||
|
||||
public SerializableObject getObj() {
|
||||
|
@ -483,7 +492,8 @@ public class BacktrackingSemanticSequencer extends AbstractSemanticSequencer {
|
|||
return r;
|
||||
}
|
||||
});
|
||||
SequenceFeeder feeder = feederProvider.create(context, obj, nodes, masterSequencer, sequenceAcceptor, errorAcceptor);
|
||||
SequenceFeeder feeder = feederProvider.create(context, obj, nodes, masterSequencer, sequenceAcceptor,
|
||||
errorAcceptor);
|
||||
if (trace != null) {
|
||||
for (TraceItem ti : trace)
|
||||
if (ti.getState() != null && ti.getState().getFeature() != null)
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.eclipse.xtext.serializer.sequencer;
|
|||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.EStructuralFeature;
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.nodemodel.ICompositeNode;
|
||||
import org.eclipse.xtext.nodemodel.INode;
|
||||
|
||||
|
@ -20,7 +21,21 @@ import com.google.inject.ImplementedBy;
|
|||
@ImplementedBy(SemanticNodeProvider.class)
|
||||
public interface ISemanticNodeProvider {
|
||||
|
||||
public interface ISemanticNode {
|
||||
INode getNode();
|
||||
|
||||
ISemanticNode getFollower();
|
||||
|
||||
AbstractElement getGrammarElement();
|
||||
}
|
||||
|
||||
public interface INodesForEObjectProvider {
|
||||
ISemanticNode getSemanticNodeForMultiValue(EStructuralFeature feature, int indexInFeature, int indexInNonTransient, Object value);
|
||||
|
||||
ISemanticNode getSemanticNodeForSingelValue(EStructuralFeature feature, Object value);
|
||||
|
||||
ISemanticNode getFirstSemanticNode();
|
||||
|
||||
INode getNodeForMultiValue(EStructuralFeature feature, int indexInFeature, int indexInNonTransient, Object value);
|
||||
|
||||
INode getNodeForSingelValue(EStructuralFeature feature, Object value);
|
||||
|
@ -37,6 +52,22 @@ public interface ISemanticNodeProvider {
|
|||
public INode getNodeForSingelValue(EStructuralFeature feature, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISemanticNode getSemanticNodeForMultiValue(EStructuralFeature feature, int indexInFeature,
|
||||
int indexInNonTransient, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISemanticNode getSemanticNodeForSingelValue(EStructuralFeature feature, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISemanticNode getFirstSemanticNode() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public INodesForEObjectProvider NULL_NODES_PROVIDER = new NullNodesForEObjectProvider();
|
||||
|
|
|
@ -14,8 +14,10 @@ import org.eclipse.emf.ecore.EClass;
|
|||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.EReference;
|
||||
import org.eclipse.emf.ecore.EStructuralFeature;
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.Action;
|
||||
import org.eclipse.xtext.Assignment;
|
||||
import org.eclipse.xtext.CrossReference;
|
||||
import org.eclipse.xtext.GrammarUtil;
|
||||
import org.eclipse.xtext.nodemodel.BidiTreeIterator;
|
||||
import org.eclipse.xtext.nodemodel.ICompositeNode;
|
||||
|
@ -30,11 +32,45 @@ import com.google.common.collect.Maps;
|
|||
*/
|
||||
public class SemanticNodeProvider implements ISemanticNodeProvider {
|
||||
|
||||
public static class SemanticNode implements ISemanticNode {
|
||||
private final INode node;
|
||||
private final AbstractElement grammarElement;
|
||||
private SemanticNode follower = null;
|
||||
|
||||
public SemanticNode(INode node) {
|
||||
this.node = node;
|
||||
EObject ge = node.getGrammarElement();
|
||||
if (ge instanceof CrossReference)
|
||||
this.grammarElement = ((CrossReference) ge).getTerminal();
|
||||
else if (ge instanceof AbstractElement)
|
||||
this.grammarElement = (AbstractElement) ge;
|
||||
else
|
||||
this.grammarElement = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public INode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISemanticNode getFollower() {
|
||||
return follower;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractElement getGrammarElement() {
|
||||
return grammarElement;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NodesForEObjectProvider implements INodesForEObjectProvider {
|
||||
|
||||
protected final Object[] childrenByFeatureIDAndIndex;
|
||||
|
||||
protected Map<EObject, INode> childrenBySemanticChild = null;
|
||||
protected Map<EObject, SemanticNode> childrenBySemanticChild = null;
|
||||
|
||||
protected SemanticNode first = null;
|
||||
|
||||
protected final ICompositeNode node;
|
||||
|
||||
|
@ -48,22 +84,29 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
collectNodesForFeatures();
|
||||
}
|
||||
|
||||
protected void add(String featureName, INode child) {
|
||||
protected SemanticNode add(String featureName, INode child, SemanticNode last) {
|
||||
if (featureName == null)
|
||||
return;
|
||||
return last;
|
||||
EClass eClass = this.semanticObject.eClass();
|
||||
EStructuralFeature feature = eClass.getEStructuralFeature(featureName);
|
||||
if (feature == null)
|
||||
return;
|
||||
return last;
|
||||
SemanticNode sem = new SemanticNode(child);
|
||||
if (last != null) {
|
||||
last.follower = sem;
|
||||
}
|
||||
if (this.first == null) {
|
||||
this.first = sem;
|
||||
}
|
||||
int id = eClass.getFeatureID(feature);
|
||||
if (feature.isMany()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<INode> nodes = (List<INode>) childrenByFeatureIDAndIndex[id];
|
||||
List<SemanticNode> nodes = (List<SemanticNode>) childrenByFeatureIDAndIndex[id];
|
||||
if (nodes == null)
|
||||
childrenByFeatureIDAndIndex[id] = nodes = Lists.<INode> newArrayList();
|
||||
nodes.add(child);
|
||||
childrenByFeatureIDAndIndex[id] = nodes = Lists.<SemanticNode>newArrayList();
|
||||
nodes.add(sem);
|
||||
} else {
|
||||
childrenByFeatureIDAndIndex[id] = child;
|
||||
childrenByFeatureIDAndIndex[id] = sem;
|
||||
}
|
||||
if (feature instanceof EReference) {
|
||||
EReference reference = (EReference) feature;
|
||||
|
@ -72,15 +115,18 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
if (semanitcChild != null) {
|
||||
if (this.childrenBySemanticChild == null)
|
||||
this.childrenBySemanticChild = Maps.newHashMap();
|
||||
this.childrenBySemanticChild.put(semanitcChild, child);
|
||||
this.childrenBySemanticChild.put(semanitcChild, sem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sem;
|
||||
}
|
||||
|
||||
// this implementation should be synonym to
|
||||
// org.eclipse.xtext.nodemodel.util.NodeModelUtils.findNodesForFeature(EObject, INode, EStructuralFeature)
|
||||
// this implementation should be synonym to
|
||||
// org.eclipse.xtext.nodemodel.util.NodeModelUtils.findNodesForFeature(EObject,
|
||||
// INode, EStructuralFeature)
|
||||
protected void collectNodesForFeatures() {
|
||||
SemanticNode last = null;
|
||||
BidiTreeIterator<INode> iterator = node.getAsTreeIterable().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
INode child = iterator.next();
|
||||
|
@ -90,11 +136,14 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
Action action = (Action) grammarElement;
|
||||
if (child.getSemanticElement() == this.semanticObject) {
|
||||
child = iterator.next();
|
||||
add(action.getFeature(), child);
|
||||
last = add(action.getFeature(), child, last);
|
||||
} else {
|
||||
// navigate the action's left side (first child) until we find an assignment (a rule call)
|
||||
// the assignment will tell us about the feature to which we assigned
|
||||
// the semantic object that has been created by the action
|
||||
// navigate the action's left side (first child)
|
||||
// until we find an assignment (a rule call)
|
||||
// the assignment will tell us about the feature to
|
||||
// which we assigned
|
||||
// the semantic object that has been created by the
|
||||
// action
|
||||
INode firstChild = ((ICompositeNode) child).getFirstChild();
|
||||
while (firstChild.getGrammarElement() instanceof Action) {
|
||||
firstChild = ((ICompositeNode) firstChild).getFirstChild();
|
||||
|
@ -102,13 +151,13 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
EObject firstChildGrammarElement = firstChild.getGrammarElement();
|
||||
Assignment assignment = GrammarUtil.containingAssignment(firstChildGrammarElement);
|
||||
if (assignment != null)
|
||||
add(assignment.getFeature(), child);
|
||||
last = add(assignment.getFeature(), child, last);
|
||||
}
|
||||
iterator.prune();
|
||||
} else if (child != node) {
|
||||
Assignment assignment = GrammarUtil.containingAssignment(grammarElement);
|
||||
if (assignment != null) {
|
||||
add(assignment.getFeature(), child);
|
||||
last = add(assignment.getFeature(), child, last);
|
||||
iterator.prune();
|
||||
}
|
||||
}
|
||||
|
@ -117,26 +166,27 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public INode getNodeForMultiValue(EStructuralFeature feat, int indexInFeat, int indexInNonTransient, Object val) {
|
||||
public SemanticNode getSemanticNodeForMultiValue(EStructuralFeature feat, int indexInFeat,
|
||||
int indexInNonTransient, Object val) {
|
||||
if (childrenBySemanticChild != null && feat instanceof EReference && ((EReference) feat).isContainment()) {
|
||||
INode candiadate = this.childrenBySemanticChild.get(val);
|
||||
SemanticNode candiadate = this.childrenBySemanticChild.get(val);
|
||||
if (candiadate != null)
|
||||
return candiadate;
|
||||
}
|
||||
Object object = this.childrenByFeatureIDAndIndex[semanticObject.eClass().getFeatureID(feat)];
|
||||
if (feat.isMany() && object instanceof List<?>) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<INode> nodes = (List<INode>) object;
|
||||
List<SemanticNode> nodes = (List<SemanticNode>) object;
|
||||
if (indexInNonTransient >= 0 && indexInNonTransient < nodes.size())
|
||||
return nodes.get(indexInNonTransient);
|
||||
} else if (object instanceof INode)
|
||||
return (INode) object;
|
||||
} else if (object instanceof SemanticNode)
|
||||
return (SemanticNode) object;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public INode getNodeForSingelValue(EStructuralFeature feature, Object value) {
|
||||
return getNodeForMultiValue(feature, 0, 0, value);
|
||||
public SemanticNode getSemanticNodeForSingelValue(EStructuralFeature feature, Object value) {
|
||||
return getSemanticNodeForMultiValue(feature, 0, 0, value);
|
||||
}
|
||||
|
||||
protected EObject getSemanticChild(INode node) {
|
||||
|
@ -151,6 +201,29 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public INode getNodeForMultiValue(EStructuralFeature feature, int indexInFeature, int indexInNonTransient,
|
||||
Object value) {
|
||||
SemanticNode semantic = getSemanticNodeForMultiValue(feature, indexInFeature, indexInNonTransient, value);
|
||||
if (semantic == null)
|
||||
return null;
|
||||
return semantic.getNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public INode getNodeForSingelValue(EStructuralFeature feature, Object value) {
|
||||
SemanticNode semantic = getSemanticNodeForSingelValue(feature, value);
|
||||
if (semantic == null)
|
||||
return null;
|
||||
return semantic.getNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISemanticNode getFirstSemanticNode() {
|
||||
return first;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected NodesForEObjectProvider createNodesForEObjectProvider(EObject semanticObject, ICompositeNode node) {
|
||||
|
@ -158,7 +231,8 @@ public class SemanticNodeProvider implements ISemanticNodeProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public INodesForEObjectProvider getNodesForSemanticObject(EObject semanticObject, ICompositeNode suggestedComposite) {
|
||||
public INodesForEObjectProvider getNodesForSemanticObject(EObject semanticObject,
|
||||
ICompositeNode suggestedComposite) {
|
||||
if (suggestedComposite != null)
|
||||
return createNodesForEObjectProvider(semanticObject, suggestedComposite);
|
||||
ICompositeNode actualComposite = NodeModelUtils.findActualNodeFor(semanticObject);
|
||||
|
|
Loading…
Reference in a new issue