From 3f877387522e6b2cbd1bbd3060fe2b7b6061975f Mon Sep 17 00:00:00 2001 From: Sebastian Zarnekow Date: Mon, 7 Nov 2011 22:28:22 +0100 Subject: [PATCH] [xtext][validation] Improved validation of imported ePackage + according quickfixes --- .../eclipse/xtext/xtext/XtextValidator.java | 151 ++++++++++++++++-- 1 file changed, 134 insertions(+), 17 deletions(-) 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 c0b118662..bbe16aa57 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 @@ -19,6 +19,7 @@ import java.util.TreeSet; import org.eclipse.emf.codegen.util.CodeGenUtil; import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EDataType; @@ -28,9 +29,12 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.EValidator; import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.Diagnostician; +import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.EcoreValidator; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.AbstractMetamodelDeclaration; @@ -56,8 +60,14 @@ import org.eclipse.xtext.UnorderedGroup; import org.eclipse.xtext.XtextPackage; import org.eclipse.xtext.conversion.IValueConverterService; import org.eclipse.xtext.conversion.ValueConverterException; +import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.resource.ClasspathUriUtil; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.resource.XtextResourceSet; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.util.Triple; import org.eclipse.xtext.util.Tuples; @@ -89,10 +99,16 @@ public class XtextValidator extends AbstractDeclarativeValidator { public static final String EMPTY_ENUM_LITERAL= "org.eclipse.xtext.grammar.EmptyEnumLiteral"; public static final String INVALID_HIDDEN_TOKEN = "org.eclipse.xtext.grammar.InvalidHiddenToken"; public static final String INVALID_HIDDEN_TOKEN_FRAGMENT = "org.eclipse.xtext.grammar.InvalidHiddenTokenFragment"; + public static final String INVALID_PACKAGE_REFERENCE_INHERITED = "org.eclipse.xtext.grammar.InvalidPackageReference.inherited"; + public static final String INVALID_PACKAGE_REFERENCE_EXTERNAL = "org.eclipse.xtext.grammar.InvalidPackageReference.external"; + public static final String INVALID_PACKAGE_REFERENCE_NOT_ON_CLASSPATH = "org.eclipse.xtext.grammar.InvalidPackageReference.notOnClasspath"; @Inject private IValueConverterService valueConverter; + @Inject + private ResourceDescriptionsProvider resourceDescriptionsProvider; + private KeywordInspector keywordHidesTerminalInspector; @Override @@ -392,30 +408,131 @@ public class XtextValidator extends AbstractDeclarativeValidator { if (metamodel.getEPackage() == null) return; String nsURI = metamodel.getEPackage().getNsURI(); - List allGeneratedMetamodels = new ArrayList(); - Grammar grammar = GrammarUtil.getGrammar(metamodel); - Set visited = Sets.newHashSet(); - for (Grammar usedGrammar : grammar.getUsedGrammars()) - Iterables.addAll(allGeneratedMetamodels, getAllGeneratedMetamodels(usedGrammar, visited)); - if (allGeneratedMetamodels.isEmpty()) + List allGeneratedMetamodels = getInheritedGeneratedMetamodels(metamodel); + String text = getUsedUri(metamodel); + for (GeneratedMetamodel generatedMetamodel : allGeneratedMetamodels) { + EPackage generatedPackage = generatedMetamodel.getEPackage(); + if (generatedPackage != null && nsURI.equals((generatedPackage.getNsURI()))) { + if (!text.equals(nsURI)) { + error( + "Metamodels that have been generated by a super grammar must be referenced by nsURI: " + nsURI, + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_INHERITED, + nsURI); + return; + } + } + } + checkExternalPackage(metamodel, text); + } + + protected void checkExternalPackage(ReferencedMetamodel metamodelReference, String importURI) { + EPackage referencedPackage = metamodelReference.getEPackage(); + if (referencedPackage.eIsProxy() || isRuntime(metamodelReference)) return; + if (isRegisteredPackage(referencedPackage)) { + warning( + "The imported package is not on the classpath of this project which may lead to follow up errors.", + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_NOT_ON_CLASSPATH, + importURI); + return; + } + if (!importURI.equals(referencedPackage.getNsURI())) { + IResourceDescriptions descriptions = resourceDescriptionsProvider.getResourceDescriptions(metamodelReference.eResource()); + Iterable packagesInIndex = descriptions.getExportedObjects( + EcorePackage.Literals.EPACKAGE, + QualifiedName.create(referencedPackage.getNsURI()), + false); + if (!Iterables.isEmpty(packagesInIndex)) { + warning( + "Packages should be imported by their namespace URI.", + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_EXTERNAL, + referencedPackage.getNsURI()); + } else if (!ClasspathUriUtil.isClasspathUri(URI.createURI(importURI))) { + warning( + "The imported package is not on the classpath of this project which may lead to follow up errors.", + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_NOT_ON_CLASSPATH, + importURI); + return; + } else { + return; + } + } + Map> allReferences = EcoreUtil.CrossReferencer.find(Collections.singletonList(referencedPackage)); + Set allReferencedInstances = Sets.newHashSet(); + for(Setting setting: Iterables.concat(allReferences.values())) { + Object referenced = setting.get(true); + if (allReferencedInstances.add(referenced) && referenced instanceof EObject) { + EPackage transitive = EcoreUtil2.getContainerOfType((EObject)referenced, EPackage.class); + if (isRegisteredPackage(transitive)) { + if (referenced instanceof EDataType) + continue; + if (referenced == EcorePackage.Literals.EOBJECT) + continue; + IResourceDescriptions descriptions = resourceDescriptionsProvider.getResourceDescriptions(metamodelReference.eResource()); + Iterable packagesInIndex = descriptions.getExportedObjects(EcorePackage.Literals.EPACKAGE, QualifiedName.create(importURI), false); + if (!Iterables.isEmpty(packagesInIndex)) { + if (setting.getEObject().eResource().getURI().isPlatformResource()) + warning( + "The imported package refers to elements the package registry instead of using the instances from the workspace", + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_EXTERNAL, + referencedPackage.getNsURI()); + else + warning( + "The imported package refers to elements the package registry instead of using the instances from the workspace", + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_EXTERNAL); + return; + } else { + warning( + "The imported package refers to elements that are not on the classpath of this project. The package '" + transitive.getNsURI() + "' was loaded from the registry.", + XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE, + INVALID_PACKAGE_REFERENCE_NOT_ON_CLASSPATH, + referencedPackage.getNsURI()); + return; + } + } + } + } + } + + private boolean isRuntime(ReferencedMetamodel metamodelReference) { + ResourceSet resourceSet = metamodelReference.eResource().getResourceSet(); + if (resourceSet instanceof XtextResourceSet) { + Object context = ((XtextResourceSet) resourceSet).getClasspathURIContext(); + boolean result = context == null || context instanceof Class || context instanceof ClassLoader; + return result; + } + return false; + } + + protected boolean isRegisteredPackage(EPackage ePackage) { + return ePackage != null && (ePackage.eResource() == null || ePackage.getNsURI().equals(ePackage.eResource().getURI().toString())); + } + + protected String getUsedUri(ReferencedMetamodel metamodel) { List nodes = NodeModelUtils.findNodesForFeature(metamodel, XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE); if (nodes.size() != 1) throw new IllegalArgumentException(); String text = nodes.get(0).getText(); text = (String) valueConverter.toValue(text, "STRING", nodes.get(0)); - for (GeneratedMetamodel generatedMetamodel : allGeneratedMetamodels) { - EPackage generatedPackage = generatedMetamodel.getEPackage(); - if (generatedPackage != null && nsURI.equals((generatedPackage.getNsURI()))) { - // TODO provide a quickfix - if (!text.equals(nsURI)) { - error( - "Metamodels that have been generated by a super grammar must be referenced by nsURI: " + nsURI, - XtextPackage.Literals.ABSTRACT_METAMODEL_DECLARATION__EPACKAGE); - } - } - } + return text; + } + + protected List getInheritedGeneratedMetamodels(ReferencedMetamodel metamodel) { + List allGeneratedMetamodels = new ArrayList(); + Grammar grammar = GrammarUtil.getGrammar(metamodel); + Set visited = Sets.newHashSet(); + for (Grammar usedGrammar : grammar.getUsedGrammars()) + Iterables.addAll(allGeneratedMetamodels, getAllGeneratedMetamodels(usedGrammar, visited)); + if (allGeneratedMetamodels.isEmpty()) + return Collections.emptyList(); + return allGeneratedMetamodels; } private Iterable getAllGeneratedMetamodels(Grammar grammar, Set visited) {