From a69606b307f9a7f9431945098e7a971bec4c0fcb Mon Sep 17 00:00:00 2001 From: Sebastian Zarnekow Date: Tue, 22 Nov 2011 16:41:47 +0100 Subject: [PATCH] [xtext][grammar] Fix: Grammar linking is broken for imported packages for a clean build see https://bugs.eclipse.org/bugs/show_bug.cgi?id=364446 --- .../org/eclipse/xtext/XtextRuntimeModule.java | 23 +++++ .../resource/DerivedStateAwareResource.java | 53 +++++++++++- .../eclipse/xtext/xtext/GrammarResource.java | 83 +++++++++++++++++++ .../org/eclipse/xtext/xtext/XtextLinker.java | 12 ++- .../xtext/xtext/XtextLinkingService.java | 26 ++++-- .../eclipse/xtext/xtext/XtextValidator.java | 2 + .../ecoreInference/EClassifierInfos.java | 2 + .../Xtext2EcoreInterpretationContext.java | 11 ++- .../Xtext2EcoreTransformer.java | 20 ++--- .../grammarinheritance/ToEcoreTrafoTest.java | 4 +- 10 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/GrammarResource.java diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/XtextRuntimeModule.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/XtextRuntimeModule.java index cd4e308ac..90a1831cf 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/XtextRuntimeModule.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/XtextRuntimeModule.java @@ -17,13 +17,18 @@ import org.eclipse.xtext.naming.IQualifiedNameConverter; import org.eclipse.xtext.parser.antlr.IReferableElementsUnloader; import org.eclipse.xtext.parsetree.reconstr.ITokenSerializer.ICrossReferenceSerializer; import org.eclipse.xtext.parsetree.reconstr.ITransientValueService; +import org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager; import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; +import org.eclipse.xtext.resource.IDerivedStateComputer; import org.eclipse.xtext.resource.IFragmentProvider; import org.eclipse.xtext.resource.ILocationInFileProvider; +import org.eclipse.xtext.resource.IResourceDescription; +import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.scoping.IGlobalScopeProvider; import org.eclipse.xtext.scoping.IScopeProvider; import org.eclipse.xtext.scoping.impl.DefaultGlobalScopeProvider; import org.eclipse.xtext.validation.IDiagnosticConverter; +import org.eclipse.xtext.xtext.GrammarResource; import org.eclipse.xtext.xtext.XtextCrossReferenceSerializer; import org.eclipse.xtext.xtext.XtextDiagnosticConverter; import org.eclipse.xtext.xtext.XtextFormatter; @@ -132,4 +137,22 @@ public class XtextRuntimeModule extends AbstractXtextRuntimeModule { return DefaultGlobalScopeProvider.class; } + @Override + public Class bindXtextResource() { + return GrammarResource.class; + } + + /** + * @since 2.2 + */ + public Class bindIDerivedStateComputer() { + return GrammarResource.LinkingTrigger.class; + } + + /** + * @since 2.2 + */ + public Class bindIResourceDescriptionManager() { + return DerivedStateAwareResourceDescriptionManager.class; + } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java index cd3385bf0..75f6b3d60 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java @@ -11,12 +11,13 @@ import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.linking.lazy.LazyLinkingResource; import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.util.IResourceScopeCache; +import org.eclipse.xtext.util.OnChangeEvictingCache; import com.google.inject.Inject; /** - * - * Adds a hook for late initialization to be used to create derived state + * Adds a hook for late initialization to be used to create derived state. * * @author Sven Efftinge - Initial contribution and API * @since 2.1 @@ -33,6 +34,12 @@ public class DerivedStateAwareResource extends LazyLinkingResource { protected volatile boolean fullyInitialized = false; protected volatile boolean isInitializing = false; + /** + * {@inheritDoc} + *

+ * As soon as an external client tries to access the content of the resource, + * the {@link IDerivedStateComputer derived state} will be added to the content of this resource. + */ @Override public synchronized EList getContents() { if (isLoaded && !isLoading && !isInitializing && !isUpdating && !fullyInitialized) { @@ -53,6 +60,48 @@ public class DerivedStateAwareResource extends LazyLinkingResource { } super.updateInternalState(oldParseResult, newParseResult); } + + /** + * Overridden to make sure that the cache is initialized during {@link #isLoading() loading}. + */ + @Override + protected void updateInternalState(IParseResult newParseResult) { + super.updateInternalState(newParseResult); + // make sure that the cache adapter is installed on this resource + IResourceScopeCache cache = getCache(); + if (cache instanceof OnChangeEvictingCache) { + ((OnChangeEvictingCache) cache).getOrCreate(this); + } + } + + /** + * {@inheritDoc} + *

+ * Overridden to make sure that we do not initialize a resource + * just to compute the root URI fragment for the parse result. + */ + @Override + protected String getURIFragmentRootSegment(EObject eObject) { + if (unloadingContents == null) { + IParseResult parseResult = getParseResult(); + if (parseResult != null && eObject == parseResult.getRootASTElement()) { + return "0"; + } + } + return super.getURIFragmentRootSegment(eObject); + } + + /** + * {@inheritDoc} + *

+ * Not specialized because we want to obtain a fully working root instance + * when the resource is queried with the root fragment. + */ + @Override + protected EObject getEObjectForURIFragmentRootSegment(String uriFragmentRootSegment) { + return super.getEObjectForURIFragmentRootSegment(uriFragmentRootSegment); + } + public void discardDerivedState() { if (fullyInitialized && !isInitializing) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/GrammarResource.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/GrammarResource.java new file mode 100644 index 000000000..11632259d --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/GrammarResource.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * 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 + *******************************************************************************/ +package org.eclipse.xtext.xtext; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.resource.DerivedStateAwareResource; +import org.eclipse.xtext.resource.IDerivedStateComputer; + +/** + * Resource implementation that instantiates the infered packages as part of the + * derived state computation. + * + * @author Sebastian Zarnekow - Initial contribution and API + */ +public class GrammarResource extends DerivedStateAwareResource { + + /** + * Overridden to do only the clean-part of the linking but not + * the actual linking. This is deferred until someone wants to access + * the content of the resource. + */ + @Override + protected void doLinking() { + IParseResult parseResult = getParseResult(); + if (parseResult == null || parseResult.getRootASTElement() == null) + return; + + XtextLinker castedLinker = (XtextLinker) getLinker(); + castedLinker.discardGeneratedPackages(parseResult.getRootASTElement()); + } + + /** + * Performs the actual linking. + */ + protected void superDoLinking() { + super.doLinking(); + } + + /** + * Overridden to make sure the errors are up-to-date when someone wants to access them. + */ + @Override + public EList getErrors() { + // trigger derived state computation + getContents(); + return super.getErrors(); + } + + /** + * Overridden to make sure the warnings are up-to-date when someone wants to access them. + */ + @Override + public EList getWarnings() { + // trigger derived state computation + getContents(); + return super.getWarnings(); + } + + /** + * Triggers the ecore inference as soon as someone wants to access the contents + * of a {@link GrammarResource}. + */ + public static class LinkingTrigger implements IDerivedStateComputer { + + public void installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) { + if (preLinkingPhase) + return; + GrammarResource castedResource = (GrammarResource)resource; + castedResource.superDoLinking(); + } + + public void discardDerivedState(DerivedStateAwareResource resource) { + } + + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java index 9fb79aaeb..104907b46 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java @@ -178,9 +178,15 @@ public class XtextLinker extends Linker { @Override protected void beforeModelLinked(EObject model, IDiagnosticConsumer diagnosticsConsumer) { - if (model instanceof Grammar) { + discardGeneratedPackages(model); + super.beforeModelLinked(model, diagnosticsConsumer); + cache.getOrCreate(model.eResource()).ignoreNotifications(); + } + + void discardGeneratedPackages(EObject root) { + if (root instanceof Grammar) { // unload generated metamodels as they will be recreated during linking - for (AbstractMetamodelDeclaration metamodelDeclaration : ((Grammar) model).getMetamodelDeclarations()) { + for (AbstractMetamodelDeclaration metamodelDeclaration : ((Grammar) root).getMetamodelDeclarations()) { if (metamodelDeclaration instanceof GeneratedMetamodel) { EPackage ePackage = ((GeneratedMetamodel) metamodelDeclaration).getEPackage(); if (ePackage != null) { @@ -197,8 +203,6 @@ public class XtextLinker extends Linker { } } } - super.beforeModelLinked(model, diagnosticsConsumer); - cache.getOrCreate(model.eResource()).ignoreNotifications(); } @Override diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinkingService.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinkingService.java index 802d2a126..626498a0e 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinkingService.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinkingService.java @@ -41,10 +41,13 @@ import org.eclipse.xtext.nodemodel.BidiIterator; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.parser.IParseResult; import org.eclipse.xtext.resource.ClasspathUriResolutionException; import org.eclipse.xtext.resource.ClasspathUriUtil; +import org.eclipse.xtext.resource.DerivedStateAwareResource; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider; import org.eclipse.xtext.scoping.IGlobalScopeProvider; import org.eclipse.xtext.scoping.IScope; @@ -95,13 +98,22 @@ public class XtextLinkingService extends DefaultLinkingService { String grammarName = (String) valueConverterService.toValue("", "GrammarID", node); if (grammarName != null) { final ResourceSet resourceSet = grammar.eResource().getResourceSet(); - for(Resource resource: resourceSet.getResources()) { - if (!resource.getContents().isEmpty()) { - EObject content = resource.getContents().get(0); - if (content instanceof Grammar) { - Grammar otherGrammar = (Grammar) content; - if (grammarName.equals(otherGrammar.getName())) - return Collections.singletonList(otherGrammar); + List resources = resourceSet.getResources(); + for(int i = 0; i < resources.size(); i++) { + Resource resource = resources.get(i); + EObject rootElement = null; + if (resource instanceof XtextResource) { + IParseResult parseResult = ((XtextResource) resource).getParseResult(); + rootElement = parseResult.getRootASTElement(); + } else if (!resource.getContents().isEmpty()) { + rootElement = resource.getContents().get(0); + } + if (rootElement instanceof Grammar) { + Grammar otherGrammar = (Grammar) rootElement; + if (grammarName.equals(otherGrammar.getName())) { + if (resource instanceof DerivedStateAwareResource) + resource.getContents(); + return Collections.singletonList(otherGrammar); } } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java index bbe16aa57..e01317f9f 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java @@ -890,6 +890,8 @@ public class XtextValidator extends AbstractDeclarativeValidator { @Override public Boolean caseRuleCall(RuleCall object) { + if (object.getRule() == null) + return assignedActionAllowed; assignedActionAllowed = assignedActionAllowed || doSwitch(object.getRule()) && !GrammarUtil.isOptionalCardinality(object); return assignedActionAllowed; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/EClassifierInfos.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/EClassifierInfos.java index 257a73982..3ab58c2bd 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/EClassifierInfos.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/EClassifierInfos.java @@ -127,6 +127,8 @@ public class EClassifierInfos { } public EClassifierInfo getInfoOrNull(EClassifier eClassifier) { + if (eClassifier == null) + return null; for (EClassifierInfo info : infoMap.values()) { if (info.getEClassifier().equals(eClassifier)) return info; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreInterpretationContext.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreInterpretationContext.java index 2cb2a7d88..662bb9a3e 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreInterpretationContext.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreInterpretationContext.java @@ -40,7 +40,7 @@ public class Xtext2EcoreInterpretationContext { private final Collection currentTypes = Sets.newLinkedHashSet(); - boolean isRuleCallAllowed = true; + private boolean isRuleCallAllowed = true; private Xtext2EcoreInterpretationContext(EClassifierInfos classifierInfos) { super(); @@ -128,7 +128,7 @@ public class Xtext2EcoreInterpretationContext { if (result == null) { final ICompositeNode node = NodeModelUtils.getNode(terminal); if (node != null) { - throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, "Cannot find type for '" + node.getText() + "'.", terminal); + throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, "Cannot find type for '" + node.getText().trim() + "'.", terminal); } throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, "Cannot find type for " + terminal.eClass().getName(), terminal); } @@ -139,8 +139,11 @@ public class Xtext2EcoreInterpretationContext { throws TransformationException { final EClassifierInfo featureTypeInfo = eClassifierInfos.getInfoOrNull(type); if (featureTypeInfo == null) { - throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, "Cannot resolve type " + type.getName(), - parserElement); + String typeName = "null"; + if (type != null) + typeName = type.getName(); + throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, + "Cannot resolve type " + typeName, parserElement); } return featureTypeInfo; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java index 10be158c0..91834dff1 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java @@ -12,7 +12,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -57,6 +56,7 @@ import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.util.XtextSwitch; +import org.eclipse.xtext.xtext.GrammarResource; import com.google.common.base.Function; import com.google.common.base.Predicates; @@ -147,15 +147,16 @@ public class Xtext2EcoreTransformer { public void removeGeneratedPackages() { final ResourceSet resourceSet = grammar.eResource().getResourceSet(); - final Iterator resourceIter = resourceSet.getResources().iterator(); + final List resources = resourceSet.getResources(); final Collection packages = getGeneratedPackages(); - // TODO check against grammar - while (resourceIter.hasNext()) { - Resource r = resourceIter.next(); - CONTENT: for (EObject content : r.getContents()) { - if (content instanceof EPackage && packages.contains(content) || generatedEPackages != null && generatedEPackages.containsValue(content)) { - clearPackage(r, (EPackage) content); - break CONTENT; + for(int i = 0; i < resources.size(); i++) { + Resource r = resources.get(i); + if (!(r instanceof GrammarResource)) { + CONTENT: for (EObject content : r.getContents()) { + if (content instanceof EPackage && packages.contains(content) || generatedEPackages != null && generatedEPackages.containsValue(content)) { + clearPackage(r, (EPackage) content); + break CONTENT; + } } } } @@ -781,7 +782,6 @@ public class Xtext2EcoreTransformer { } else if (eClassifier instanceof EDataType) { EDataType eDataType = (EDataType) eClassifier; - // TODO: Enums EClassifierInfo info = EClassifierInfo.createEDataTypeInfo(eDataType, generated); target.addInfo(metaModel, eClassifier.getName(), info); } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/grammarinheritance/ToEcoreTrafoTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/grammarinheritance/ToEcoreTrafoTest.java index 7dbe3bc58..9e0d6d4a7 100755 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/grammarinheritance/ToEcoreTrafoTest.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/grammarinheritance/ToEcoreTrafoTest.java @@ -57,7 +57,7 @@ public class ToEcoreTrafoTest extends AbstractXtextTests { public void testConcreteLanguageToMetamodel() throws Exception { XtextResource r = getResource("classpath:/" + ConcreteTestLanguage.class.getName().replace('.', '/') + ".xtext"); - Grammar element = (Grammar) r.getParseResult().getRootASTElement(); + Grammar element = (Grammar) r.getContents().get(0); List lexerRules = GrammarUtil.allTerminalRules(element); assertEquals(8, lexerRules.size()); List list = Xtext2EcoreTransformer.doGetGeneratedPackages(element); @@ -74,7 +74,7 @@ public class ToEcoreTrafoTest extends AbstractXtextTests { public void testConcreteLanguageToMetamodel1() throws Exception { XtextResource r = getResource("classpath:/" + ConcreteTestLanguage.class.getName().replace('.', '/') + ".xtext"); - EObject element = r.getParseResult().getRootASTElement(); + EObject element = r.getContents().get(0); Grammar g = (Grammar) element; List mms = Lists.newArrayList( Iterables.filter(g.getMetamodelDeclarations(), GeneratedMetamodel.class));