From 4ed1dffe009e45aa4647ae5648667a36da056349 Mon Sep 17 00:00:00 2001 From: U003649 <38555688+andrewL-avlq@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:09:33 +0000 Subject: [PATCH 1/2] fix: utils on ProxyCompositeNode to adapt node arguments. NodeModelUtils typically calls Node.utils() and passes the node to the implementation method. ProxyCompositeNode is now fixed so that the loaded node rather than the proxy is passed to the implementation method. --- .../persistence/ProxyCompositeNode.java | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java index 4329809e9..47dfcc597 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java @@ -21,9 +21,11 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.WrappedException; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.nodemodel.BidiIterable; import org.eclipse.xtext.nodemodel.BidiTreeIterable; import org.eclipse.xtext.nodemodel.BidiTreeIterator; @@ -42,6 +44,7 @@ import org.eclipse.xtext.resource.persistence.StorageAwareResource; import org.eclipse.xtext.util.ITextRegion; import org.eclipse.xtext.util.ITextRegionWithLineInformation; +import org.eclipse.xtext.util.LineAndColumn; import com.avaloq.tools.ddk.annotations.SuppressFBWarnings; @@ -405,7 +408,59 @@ public void setTarget(final Notifier newTarget) { @Override public NodeModelUtils.Implementation utils() { - return delegate().utils(); + // return an implementation that adapts the proxy node to the delegate on each Node parameter + return new NodeModelUtils.Implementation() { + private final ICompositeNode loadedNode = delegate(); + + @SuppressWarnings("unchecked") + private N adapt(final N node) { + if (node == ProxyCompositeNode.this) { + // adapt() is only ever called with INodes and ICompositeNodes, so it is safe to cast to N here. + return (N) loadedNode; + } + return node; + } + + @Override + public ILeafNode findLeafNodeAtOffset(final INode node, final int leafNodeOffset) { + return loadedNode.utils().findLeafNodeAtOffset(adapt(node), leafNodeOffset); + } + + @Override + public LineAndColumn getLineAndColumn(final INode anyNode, final int documentOffset) { + return loadedNode.utils().getLineAndColumn(adapt(anyNode), documentOffset); + } + + @Override + public List findNodesForFeature(final EObject element, final INode node, final EStructuralFeature structuralFeature) { + return loadedNode.utils().findNodesForFeature(element, adapt(node), structuralFeature); + } + + @Override + public ICompositeNode findActualNodeEnclosing(final ICompositeNode node) { + return loadedNode.utils().findActualNodeEnclosing(adapt(node)); + } + + @Override + public EObject findActualSemanticObjectFor(final INode node) { + return loadedNode.utils().findActualSemanticObjectFor(adapt(node)); + } + + @Override + public String compactDump(final INode node, final boolean showHidden) { + return loadedNode.utils().compactDump(adapt(node), showHidden); + } + + @Override + public String getTokenText(final INode node) { + return loadedNode.utils().getTokenText(adapt(node)); + } + + @Override + public ParserRule getEntryParserRule(final INode node) { + return loadedNode.utils().getEntryParserRule(adapt(node)); + } + }; } } From 023b5e2a0a28f0dbc222c879c0b4f88b3910c770 Mon Sep 17 00:00:00 2001 From: U003649 <38555688+andrewL-avlq@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:28:43 +0000 Subject: [PATCH 2/2] fix: keep parse result set while loading node model. Removes the window where the parse result is not set on a resource while loading the node model. This prevents NPEs occurring if concurrent reads try to use the node model. --- .../persistence/DirectLinkingResourceStorageLoadable.java | 4 +++- .../ddk/xtext/resource/persistence/ProxyCompositeNode.java | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java index 166d56cc9..946450c7a 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java @@ -37,6 +37,7 @@ import org.eclipse.xtext.nodemodel.impl.SerializableNodeModel; import org.eclipse.xtext.nodemodel.serialization.DeserializationConversionContext; import org.eclipse.xtext.parser.ParseResult; +import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.persistence.ResourceStorageLoadable; import org.eclipse.xtext.resource.persistence.StorageAwareResource; @@ -319,7 +320,8 @@ private void addFakeModel(final StorageAwareResource resource) { protected void readNodeModel(final StorageAwareResource resource, final InputStream inputStream, final String content) throws IOException { DeserializationConversionContext deserializationContext = new ProxyAwareDeserializationConversionContext(resource, content); DataInputStream dataIn = new DataInputStream(inputStream); - SerializableNodeModel serializableNodeModel = new SerializableNodeModel(resource); + // use empty resource here so that we can leave the proxy node in place right up until the loaded model is set below. + SerializableNodeModel serializableNodeModel = new SerializableNodeModel(new XtextResource()); serializableNodeModel.readObjectData(dataIn, deserializationContext); resource.setParseResult(new ParseResult(resource.getContents().get(0), serializableNodeModel.root, deserializationContext.hasErrors())); } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java index 47dfcc597..9b91de312 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java @@ -118,10 +118,6 @@ static List uninstallProxyNodeModel(final Resource resource) { result = proxyNode.idToEObjectMap; } } - - if (resource instanceof XtextResource) { - ((XtextResource) resource).setParseResult(null); - } return result; }