private void addExactInstances(SNode current, SNode concept, Set<SNode> result, IScope scope) { if (current.getConceptFqName().equals(NameUtil.nodeFQName(concept))) { result.add(current); } for (SNode child : current.getChildren()) { addExactInstances(child, concept, result, scope); } }
private void addUsages(SNode current, Set<SNode> nodes, Set<SReference> result) { for (SReference ref : current.getReferences()) { if (nodes.contains(ref.getTargetNode())) { result.add(ref); } } for (SNode child : current.getChildren()) { addUsages(child, nodes, result); } }
public SNode createChildNode(Object parameterObject, SModel model, String pattern) { SNode childNode = SModelUtil_new.instantiateConceptDeclaration( NameUtil.nodeFQName(mySmartConcept), model, GlobalScope.getInstance()); String referentRole = SModelUtil.getGenuineLinkRole(mySmartReference); childNode.setReferenceTarget(referentRole, myReferentNode); NodeFactoryManager.setupNode( mySmartConcept, childNode, myCurrentChild, myParentNode, model, getScope()); return childNode; }
/** Unlinks s from the stack. */ void clean(SNode s) { s.item = null; // forget item s.waiter = null; // forget thread /* * At worst we may need to traverse entire stack to unlink * s. If there are multiple concurrent calls to clean, we * might not see s if another thread has already removed * it. But we can stop when we see any node known to * follow s. We use s.next unless it too is cancelled, in * which case we try the node one past. We don't check any * further because we don't want to doubly traverse just to * find sentinel. */ SNode past = s.next; if (past != null && past.isCancelled()) past = past.next; // Absorb cancelled nodes at head SNode p; while ((p = head) != null && p != past && p.isCancelled()) casHead(p, p.next); // Unsplice embedded nodes while (p != null && p != past) { SNode n = p.next; if (n != null && n.isCancelled()) p.casNext(n, n.next); else p = n; } }
public static SNode reverseList(SNode n) { SNode last = n; SNode dummy = new SNode(0); while (n != null) { SNode newNode = new SNode(n.key); newNode.next = dummy.next; dummy.next = newNode; n = n.next; } return dummy.next; }
public static GenerationDependencies fromIncremental( Map<SNode, SNode> currentToOriginalMap, RootDependenciesBuilder[] roots, String modelHash, String parametersHash, IOperationContext operationContext, IncrementalGenerationStrategy incrementalStrategy, int skippedCount, int fromCacheCount) { Map<String, List<String>> generatedFiles = getGeneratedFiles(currentToOriginalMap); List<GenerationRootDependencies> unchanged = null; List<GenerationRootDependencies> rootDependencies = new ArrayList<GenerationRootDependencies>(roots.length); Map<String, String> externalHashes = new HashMap<String, String>(); for (RootDependenciesBuilder l : roots) { SNode originalRoot = l.getOriginalRoot(); List<String> files = generatedFiles.get(originalRoot != null ? originalRoot.getId() : ""); if (files == null) { files = Collections.emptyList(); } GenerationRootDependencies dep; if (l.isUnchanged()) { dep = l.getSavedDependencies(); if (unchanged == null) { unchanged = new ArrayList<GenerationRootDependencies>(); } unchanged.add(dep); } else { dep = GenerationRootDependencies.fromData(l, files); } rootDependencies.add(dep); for (String modelReference : dep.getExternal()) { if (!externalHashes.containsKey(modelReference)) { SModelDescriptor sm = SModelRepository.getInstance() .getModelDescriptor(SModelReference.fromString(modelReference)); Map<String, String> hashes = incrementalStrategy.getModelHashes(sm, operationContext); String value = hashes != null ? hashes.get(ModelDigestHelper.FILE) : null; externalHashes.put(modelReference, value); } } } return new GenerationDependencies( rootDependencies, modelHash, parametersHash, externalHashes, unchanged == null ? Collections.<GenerationRootDependencies>emptyList() : unchanged, skippedCount, fromCacheCount); }
private boolean canPasteBefore(EditorCell selectedCell, List<SNode> pasteNodes) { if (!selectedCell.isFirstPositionInBigCell()) return false; SNode anchor = selectedCell.getSNode(); if (anchor.getParent() == null) return false; NodeAndRole nodeAndRole = new NodePaster(pasteNodes).getActualAnchorNode(anchor, anchor.getRoleInParent()); if (nodeAndRole == null) return false; EditorCell targetCell = selectedCell.getEditor().findNodeCell(nodeAndRole.myNode); return targetCell != null && targetCell.getFirstLeaf(CellConditions.SELECTABLE) == selectedCell && new NodePaster(pasteNodes).canPasteRelative(nodeAndRole.myNode); }
private static List<INodeSubstituteAction> createPrimaryChildSubstituteActions( SNode parentNode, SNode currentChild, SNode childConcept, IChildNodeSetter childSetter, IOperationContext context) { if (childConcept == null) { return Collections.emptyList(); } final IScope scope = context.getScope(); String childConceptFqName = NameUtil.nodeFQName(childConcept); Set<String> concepts = new HashSet<String>(); for (Language l : SModelOperations.getLanguages(parentNode.getModel(), scope)) { concepts.addAll( LanguageHierarchyCache.getInstance() .getDefaultSubstitutableDescendantsOf(childConceptFqName, l)); } List<INodeSubstituteAction> actions = new ArrayList<INodeSubstituteAction>(); for (String fqName : concepts) { SNode applicableConcept = SModelUtil.findConceptDeclaration(fqName, scope); assert applicableConcept != null : "No concept " + fqName; actions.addAll( createDefaultActions(applicableConcept, parentNode, currentChild, childSetter, context)); } return actions; }
public static String getFileName(SNode outputRootNode) { // TODO refactor, use DefaultFileGenerator.getFileName String extension = TextGenManager.instance().getExtension(outputRootNode); return (extension == null) ? outputRootNode.getName() : outputRootNode.getName() + "." + extension; }
public Set<SNode> findDescendants(SNode node, Set<SNode> descendantsKnownInModel) { if (!myFindUsagesSupported) return new HashSet<SNode>(); boolean changed = false; if (myModelDescriptor instanceof EditableSModelDescriptor) { changed = ((EditableSModelDescriptor) myModelDescriptor).isChanged(); } boolean atLeastRootsLoaded = myModelDescriptor.getLoadingState().compareTo(ModelLoadingState.ROOTS_LOADED) >= 0; if (atLeastRootsLoaded && !changed && !descendantsKnownInModel.isEmpty()) return descendantsKnownInModel; if (myNeedSearchForStrings && !myModelRootManager.containsString(myModelDescriptor, node.getId())) return descendantsKnownInModel; SModel model = myModelDescriptor.getSModel(); Set<SNode> result = new HashSet<SNode>(); if (model != null) { for (SNode root : model.roots()) { addDescendants(root, node, result); } } descendantsKnownInModel.clear(); descendantsKnownInModel.addAll(result); return descendantsKnownInModel; }
/** * Spins/blocks until node s is matched by a fulfill operation. * * @param s the waiting node * @param timed true if timed wait * @param nanos timeout value * @return matched node, or s if cancelled */ SNode awaitFulfill(SNode s, boolean timed, long nanos) { /* * When a node/thread is about to block, it sets its waiter * field and then rechecks state at least one more time * before actually parking, thus covering race vs * fulfiller noticing that waiter is non-null so should be * woken. * * When invoked by nodes that appear at the point of call * to be at the head of the stack, calls to park are * preceded by spins to avoid blocking when producers and * consumers are arriving very close in time. This can * happen enough to bother only on multiprocessors. * * The order of checks for returning out of main loop * reflects fact that interrupts have precedence over * normal returns, which have precedence over * timeouts. (So, on timeout, one last check for match is * done before giving up.) Except that calls from untimed * SynchronousQueue.{poll/offer} don't check interrupts * and don't wait at all, so are trapped in transfer * method rather than calling awaitFulfill. */ long lastTime = timed ? System.nanoTime() : 0; Thread w = Thread.currentThread(); SNode h = head; int spins = (shouldSpin(s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0); for (; ; ) { if (w.isInterrupted()) s.tryCancel(); SNode m = s.match; if (m != null) return m; if (timed) { long now = System.nanoTime(); nanos -= now - lastTime; lastTime = now; if (nanos <= 0) { s.tryCancel(); continue; } } if (spins > 0) spins = shouldSpin(s) ? (spins - 1) : 0; else if (s.waiter == null) s.waiter = w; // establish waiter so can park next iter else if (!timed) LockSupport.park(this); else if (nanos > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanos); } }
/** Delete an entity, and its brain if there is one. Do not ask directly this method. */ public void delete() { // System.out.println("Actually deleting " + this); if (deleted) return; deleted = true; if (myBrain != null) { if (myBrain instanceof AbstractAgent) getStructure().getAgent().doKillAgent((AbstractAgent) myBrain); else myBrain.delete(); } super.delete(); }
public boolean canExecute(EditorContext context) { EditorCell selectedCell = getCellToPasteTo((EditorCell) context.getSelectedCell()); if (selectedCell == null) { return false; } SNode selectedNode = selectedCell.getSNode(); if (selectedNode == null || jetbrains.mps.util.SNodeOperations.isDisposed(selectedNode)) { return false; } List<SNode> pasteNodes = CopyPasteUtil.getNodesFromClipboard(selectedNode.getModel()); if (pasteNodes == null || pasteNodes.isEmpty()) { return CopyPasteUtil.isConversionAvailable(selectedNode.getModel(), selectedNode); } if (!new NodePaster(pasteNodes).canPaste(selectedCell)) { LOG.debug("Couldn't paste node here"); return false; } return true; }
public Set<SReference> findUsages(Set<SNode> nodes) { if (!myFindUsagesSupported) return Collections.emptySet(); if (myNeedSearchForStrings) { Set<String> strings = new HashSet<String>(); for (SNode node : nodes) { strings.add(quoteSpecialXMLCharacters(node.getId())); } if (!myModelRootManager.containsSomeString(myModelDescriptor, strings)) return Collections.emptySet(); } SModel model = myModelDescriptor.getSModel(); if (model == null) return Collections.emptySet(); Set<SReference> result = new HashSet<SReference>(); for (SNode root : model.roots()) { addUsages(root, nodes, result); } return result; }
private void addDescendants(SNode current, SNode node, Set<SNode> result) { if (SNodeUtil.isInstanceOfConceptDeclaration(current)) { for (SNode interfaceConcept : SNodeUtil.getConceptDeclaration_Implements(current)) { if (interfaceConcept != null && interfaceConcept == node) { result.add(current); break; } } if (SNodeUtil.getConceptDeclaration_Extends(current) == node) { result.add(current); } } else if (SNodeUtil.isInstanceOfInterfaceConceptDeclaration(current)) { for (SNode interfaceConcept : SNodeUtil.getInterfaceConceptDeclaration_Extends(current)) { if (interfaceConcept != null && interfaceConcept == node) { result.add(interfaceConcept); break; } } } for (SNode child : current.getChildren()) { // are there any "inner" concepts? addDescendants(child, node, result); } }
/** * Returns whether the given node belongs to the repository (or to one of its parent * repositories). */ public static boolean isAccessible(@NotNull SNode node, @NotNull SRepository inRepository) { SModel model = node.getModel(); if (model == null) return false; SRepository mrep = model.getRepository(); if (mrep == null) return false; mrep.getModelAccess().checkReadAccess(); if (inRepository == mrep) { return true; } // FIXME this is a hack to deal with incomplete story of repository relationship. // We have at least two repositories, ProjectRepository and MPSModuleRepository, and with // repository.getParent() dropped, there are no chance // to figure out node coming from MPSModuleRepository is visible in ProjectRepository // (EditorComponent.editNode() passes here project's repository // but node might come from an MPSModuleRepository (e.g. temp node from console), and they don't // match. // Here, assume node is accessible even if repositories are different if its module is identical // in both. inRepository.getModelAccess().checkReadAccess(); return model.getModule() == inRepository.getModule(model.getModule().getModuleId()); }
/** Puts or takes an item. */ Object transfer(Object e, boolean timed, long nanos) { /* * Basic algorithm is to loop trying one of three actions: * * 1. If apparently empty or already containing nodes of same * mode, try to push node on stack and wait for a match, * returning it, or null if cancelled. * * 2. If apparently containing node of complementary mode, * try to push a fulfilling node on to stack, match * with corresponding waiting node, pop both from * stack, and return matched item. The matching or * unlinking might not actually be necessary because of * other threads performing action 3: * * 3. If top of stack already holds another fulfilling node, * help it out by doing its match and/or pop * operations, and then continue. The code for helping * is essentially the same as for fulfilling, except * that it doesn't return the item. */ SNode s = null; // constructed/reused as needed int mode = (e == null) ? REQUEST : DATA; for (; ; ) { SNode h = head; if (h == null || h.mode == mode) { // empty or same-mode if (timed && nanos <= 0) { // can't wait if (h != null && h.isCancelled()) casHead(h, h.next); // pop cancelled node else return null; } else if (casHead(h, s = snode(s, e, h, mode))) { SNode m = awaitFulfill(s, timed, nanos); if (m == s) { // wait was cancelled clean(s); return null; } if ((h = head) != null && h.next == s) casHead(h, s.next); // help s's fulfiller return (mode == REQUEST) ? m.item : s.item; } } else if (!isFulfilling(h.mode)) { // try to fulfill if (h.isCancelled()) // already cancelled casHead(h, h.next); // pop and retry else if (casHead(h, s = snode(s, e, h, FULFILLING | mode))) { for (; ; ) { // loop until matched or waiters disappear SNode m = s.next; // m is s's match if (m == null) { // all waiters are gone casHead(s, null); // pop fulfill node s = null; // use new node next time break; // restart main loop } SNode mn = m.next; if (m.tryMatch(s)) { casHead(s, mn); // pop both s and m return (mode == REQUEST) ? m.item : s.item; } else // lost match s.casNext(m, mn); // help unlink } } } else { // help a fulfiller SNode m = h.next; // m is h's match if (m == null) // waiter is gone casHead(h, null); // pop fulfilling node else { SNode mn = m.next; if (m.tryMatch(h)) // help match casHead(h, mn); // pop both h and m else // lost match h.casNext(m, mn); // help unlink } } } }
/** * Creates or resets fields of a node. Called only from transfer where the node to push on stack * is lazily created and reused when possible to help reduce intervals between reads and CASes * of head and to avoid surges of garbage when CASes to push nodes fail due to contention. */ static SNode snode(SNode s, Object e, SNode next, int mode) { if (s == null) s = new SNode(e); s.mode = mode; s.next = next; return s; }
private static List<INodeSubstituteAction> createActions_internal( SNode parentNode, SNode currentChild, SNode childConcept, IChildNodeSetter childSetter, IOperationContext context) { List<INodeSubstituteAction> resultActions = new ArrayList<INodeSubstituteAction>(); if (childConcept == null) { return resultActions; } // special case if (childConcept == SModelUtil.getBaseConcept()) { if ((currentChild == null || currentChild.getConcept().getId().equals(SNodeUtil.concept_BaseConcept))) { resultActions = new ArrayList<INodeSubstituteAction>(); ISearchScope conceptsSearchScope = SModelSearchUtil.createConceptsFromModelLanguagesScope( parentNode.getModel(), true, context.getScope()); List<SNode> allVisibleConcepts = conceptsSearchScope.getNodes(); for (final SNode visibleConcept : allVisibleConcepts) { resultActions.add( new DefaultChildNodeSubstituteAction( visibleConcept, parentNode, currentChild, childSetter, context.getScope()) { public String getMatchingText(String pattern) { return getMatchingText(pattern, true, true); } public String getVisibleMatchingText(String pattern) { return getMatchingText(pattern); } public String getDescriptionText(String pattern) { String fqName = NameUtil.nodeFQName(visibleConcept); return "lang: " + NameUtil.compactNamespace(NameUtil.namespaceFromConceptFQName(fqName)); } public Icon getIconFor(String pattern) { return getIconFor(pattern, true); } }); } return resultActions; } // pretend we are going to substitute more concrete concept childConcept = ChildSubstituteActionsUtil.getRefinedChildConcept(currentChild); } Language primaryLanguage = SModelUtil.getDeclaringLanguage(childConcept); if (primaryLanguage == null) { LOG.error( "Couldn't build actions : couldn't get declaring language for concept " + org.jetbrains.mps.openapi.model.SNodeUtil.getDebugText(childConcept)); return resultActions; } List<SNode> allBuilders = ChildSubstituteActionsUtil.getActionsBuilders( parentNode, currentChild, childConcept, childSetter, context); if (!ChildSubstituteActionsUtil.containsRemoveDefaults(allBuilders)) { resultActions.addAll( createPrimaryChildSubstituteActions( parentNode, currentChild, childConcept, childSetter, context)); } for (SNode builder : allBuilders) { List<INodeSubstituteAction> addActions = ChildSubstituteActionsUtil.invokeActionFactory( builder, parentNode, currentChild, childConcept, childSetter, context); resultActions.addAll(addActions); } for (SNode builder : allBuilders) { resultActions = ChildSubstituteActionsUtil.applyActionFilter( builder, resultActions, parentNode, currentChild, childConcept, context); } if (childSetter instanceof DefaultChildNodeSetter || childSetter instanceof AbstractCellMenuPart_ReplaceNode_CustomNodeConcept && currentChild != null) { SNode linkDeclaration; if (childSetter instanceof DefaultChildNodeSetter) { linkDeclaration = ((DefaultChildNodeSetter) childSetter).myLinkDeclaration; } else { linkDeclaration = currentChild.getRoleLink(); } Iterator<INodeSubstituteAction> it = resultActions.iterator(); while (it.hasNext()) { INodeSubstituteAction action = it.next(); SNode conceptNode = action.getOutputConcept(); if (conceptNode == null) { continue; } if (!ModelConstraintsManager.canBeParent(parentNode, conceptNode, linkDeclaration, context) || !ModelConstraintsManager.canBeAncestor(parentNode, conceptNode, context)) { it.remove(); } } } return resultActions; }
public void editNode(final SNode node, final IOperationContext context, final boolean focus) { ModelAccess.assertLegalWrite(); NavigationSupport.getInstance() .openNode(context, node, focus, !(node.getModel() != null && node.getModel().isRoot(node))); }
private static List<INodeSubstituteAction> createSmartReferenceActions( final SNode smartConcept, final SNode smartReference, final SNode parentNode, final SNode currentChild, final IChildNodeSetter childSetter, final IOperationContext context) { if (parentNode == null) { return null; } // try to create referent-search-scope SNode linkDeclaration = null; int index = 0; if (currentChild != null) { linkDeclaration = currentChild.getRoleLink(); index = parentNode.getChildren(currentChild.getRoleInParent()).indexOf(currentChild); } // TODO generate wrapping setter to have access to original link // if(childSetter instanceof WrappingSetter) { // childSetter = ((WrappingSetter)childSetter).unwrap(); // } if (linkDeclaration == null && childSetter instanceof DefaultChildNodeSetter) { linkDeclaration = ((DefaultChildNodeSetter) childSetter).getLinkDeclaration(); } // TODO restore (when wrapping setter is created) // if (linkDeclaration == null) { // return null; // } ReferenceDescriptor refDescriptor = ModelConstraintsUtil.getSmartReferenceDescriptor( parentNode, linkDeclaration == null ? null : SModelUtil.getLinkDeclarationRole(linkDeclaration), index, smartConcept); if (refDescriptor == null) return null; Scope searchScope = refDescriptor.getScope(); if (searchScope == null) return null; // create smart actions final String targetConcept = NameUtil.nodeFQName(SModelUtil.getLinkDeclarationTarget(smartReference)); List<INodeSubstituteAction> actions = new ArrayList<INodeSubstituteAction>(); IReferencePresentation presentation = refDescriptor.getReferencePresentation(); Iterable<SNode> referentNodes = searchScope.getAvailableElements(null); for (SNode referentNode : referentNodes) { if (referentNode == null || !referentNode .getConcept() .isSubConceptOf(SConceptRepository.getInstance().getConcept(targetConcept))) continue; actions.add( new SmartRefChildNodeSubstituteAction( referentNode, parentNode, currentChild, childSetter, context.getScope(), smartConcept, smartReference, presentation)); } return actions; }
// todo move to snode class public static boolean isInstanceOf(@Nullable SNode node, @NotNull SAbstractConcept concept) { if (node == null) return false; SConcept c = node.getConcept(); return c.isSubConceptOf(concept); }
private void addInstances(SNode current, SNode concept, Set<SNode> result, IScope scope) { if (current.isInstanceOfConcept(NameUtil.nodeFQName(concept))) result.add(current); for (SNode child : current.getChildren()) { addInstances(child, concept, result, scope); } }
public void execute(final EditorContext context) { LOG.assertInCommand(); final EditorComponent editorComponent = (EditorComponent) context.getEditorComponent(); EditorCell pasteTargetCell = getCellToPasteTo(editorComponent.getSelectedCell()); final CellInfo pasteTargetCellInfo = pasteTargetCell.getCellInfo(); final SNode nodeSelected = pasteTargetCell.getSNode(); final SNodePointer selectedNodePointer = new SNodePointer(nodeSelected); final SModel model = nodeSelected.getModel(); // sometimes model is not in repository (paste in merge dialog) final boolean inRepository = model.getModelDescriptor() == selectedNodePointer.getModel(); PasteNodeData data = CopyPasteUtil.getPasteNodeDataFromClipboard(model); if (data == null || data.getNodes().isEmpty()) { data = CopyPasteUtil.getConvertedFromClipboard( model, context.getOperationContext().getProject()); if (data == null || data.getNodes().isEmpty()) return; } final PasteNodeData pasteNodeData = data; SwingUtilities.invokeLater( new Runnable() { public void run() { final Runnable addImportsRunnable = CopyPasteUtil.addImportsWithDialog( pasteNodeData, model, context.getOperationContext()); ModelAccess.instance() .runCommandInEDT( new Runnable() { public void run() { if (addImportsRunnable != null) { addImportsRunnable.run(); } SNode selectedNode = inRepository ? selectedNodePointer.getNode() : nodeSelected; if (jetbrains.mps.util.SNodeOperations.isDisposed(selectedNode)) { StringBuilder errorText = new StringBuilder( "Selected node is disposed: " + selectedNode.toString()); SModelReference modelReference = selectedNodePointer.getModelReference(); if (modelReference != null) { SModelDescriptor modelDescriptor = SModelRepository.getInstance().getModelDescriptor(modelReference); if (modelDescriptor != null) { SModel sModel = modelDescriptor.getSModel(); errorText.append(", sModel.isDisposed(): " + sModel.isDisposed()); SNode node = sModel.getNodeById(selectedNodePointer.getNodeId()); if (node != null) { errorText.append( ", node != null, node.isDisposed(): " + jetbrains.mps.util.SNodeOperations.isDisposed(node)); } else { errorText.append(", node == null"); } } } LOG.error(errorText.toString()); return; } EditorCell selectedCell = pasteTargetCellInfo.findCell(editorComponent); assert selectedCell != null; List<SNode> pasteNodes = pasteNodeData.getNodes(); if (canPasteBefore(selectedCell, pasteNodes)) { new NodePaster(pasteNodes) .pasteRelative(selectedNode, PastePlaceHint.BEFORE_ANCHOR); } else { new NodePaster(pasteNodes).paste(selectedCell); } Set<SReference> requireResolveReferences = new HashSet<SReference>(); for (SReference ref : pasteNodeData.getRequireResolveReferences()) { // ref can be detached from model while using copy/paste handlers if (ref.getSourceNode() == null || ref.getSourceNode().getModel() == null) continue; requireResolveReferences.add(ref); } ResolverComponent.getInstance() .resolveScopesOnly( requireResolveReferences, context.getOperationContext()); // set selection editorComponent.flushEvents(); EditorCell nodeCell = editorComponent.findNodeCell(pasteNodes.get(0)); if (nodeCell == null) return; // after 'set reference'? EditorCell_Label labelCell = nodeCell.findChild(CellFinders.byClass(EditorCell_Label.class, true)); if (labelCell != null) { editorComponent.changeSelection(labelCell); } if (pasteNodes.size() == 1) { editorComponent.pushSelection(nodeCell); } else { SNode firstNodeToSelect = pasteNodes.get(0); SNode lastNodeToSelect = null; for (int i = pasteNodes.size() - 1; i > 0 && lastNodeToSelect == null; i--) { if (pasteNodes.get(i).getParent() == firstNodeToSelect.getParent()) { lastNodeToSelect = pasteNodes.get(i); } } if (lastNodeToSelect != null) { SelectionManager selectionManager = editorComponent.getSelectionManager(); selectionManager.pushSelection( selectionManager.createRangeSelection( firstNodeToSelect, lastNodeToSelect)); } } } }, context.getOperationContext().getProject()); } }); }
/** Replaces a node with another, preserving attributes */ public static SNode replaceWithAnother(@NotNull SNode node, SNode replacer) { SNode nodeParent = node.getParent(); if (nodeParent == null) { SModel model = node.getModel(); if (model != null) { node.delete(); model.addRootNode(replacer); } return replacer; } SContainmentLink role = node.getContainmentLink(); assert role != null; if (replacer != null) { // old and new child can have the same node Id // thus it is important to remove old child first SNode anchor = node.getNextSibling(); nodeParent.removeChild(node); SNode replacerParent = replacer.getParent(); if (replacerParent != null) { replacerParent.removeChild(replacer); } nodeParent.insertChildBefore(role, replacer, anchor); } else { nodeParent.removeChild(node); } return replacer; }