/* * SPDX-FileCopyrightText: 2024 The Refinery Authors * * SPDX-License-Identifier: EPL-2.0 */ package tools.refinery.language.scoping.imports; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.linking.impl.LinkingHelper; import org.eclipse.xtext.naming.IQualifiedNameConverter; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.util.IResourceScopeCache; import tools.refinery.language.model.problem.ImportStatement; import tools.refinery.language.model.problem.Problem; import tools.refinery.language.model.problem.ProblemPackage; import tools.refinery.language.naming.NamingUtil; import tools.refinery.language.resource.LoadOnDemandResourceDescriptionProvider; import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; import java.util.*; @Singleton public class ImportCollector { private static final String PREFIX = "tools.refinery.language.imports."; private static final String DIRECT_IMPORTS_KEY = PREFIX + "DIRECT_IMPORTS"; private static final String ALL_IMPORTS_KEY = PREFIX + "ALL_IMPORTS"; @Inject private IResourceScopeCache cache; @Inject private LinkingHelper linkingHelper; @Inject private IQualifiedNameConverter qualifiedNameConverter; @Inject private Provider loadOnDemandProvider; @Inject private ImportAdapterProvider importAdapterProvider; public ImportCollection getDirectImports(Resource resource) { return cache.get(DIRECT_IMPORTS_KEY, resource, () -> this.computeDirectImports(resource)); } protected ImportCollection computeDirectImports(Resource resource) { if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) { return ImportCollection.EMPTY; } var resourceSet = resource.getResourceSet(); if (resourceSet == null) { return ImportCollection.EMPTY; } var adapter = importAdapterProvider.getOrInstall(resourceSet); var collection = new ImportCollection(); collectAutomaticImports(collection, adapter); collectExplicitImports(problem, collection, adapter); collection.remove(resource.getURI()); return collection; } private void collectAutomaticImports(ImportCollection importCollection, ImportAdapter adapter) { for (var library : adapter.getLibraries()) { for (var qualifiedName : library.getAutomaticImports()) { var uri = adapter.resolveQualifiedName(qualifiedName); if (uri != null) { importCollection.add(NamedImport.implicit(uri, qualifiedName)); } } } } private void collectExplicitImports(Problem problem, ImportCollection collection, ImportAdapter adapter) { for (var statement : problem.getStatements()) { if (statement instanceof ImportStatement importStatement) { collectImportStatement(importStatement, collection, adapter); } } } private void collectImportStatement(ImportStatement importStatement, ImportCollection collection, ImportAdapter adapter) { var nodes = NodeModelUtils.findNodesForFeature(importStatement, ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE); var aliasString = importStatement.getAlias(); var alias = Strings.isNullOrEmpty(aliasString) ? QualifiedName.EMPTY : NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(aliasString)); var referredProblem = (EObject) importStatement.eGet(ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE, false); URI referencedUri = null; if (referredProblem != null && !referredProblem.eIsProxy()) { var resource = referredProblem.eResource(); if (resource != null) { referencedUri = resource.getURI(); } } for (var node : nodes) { var qualifiedNameString = linkingHelper.getCrossRefNodeAsString(node, true); if (Strings.isNullOrEmpty(qualifiedNameString)) { continue; } var qualifiedName = NamingUtil.stripRootPrefix( qualifiedNameConverter.toQualifiedName(qualifiedNameString)); var uri = referencedUri == null ? adapter.resolveQualifiedName(qualifiedName) : referencedUri; if (uri != null) { collection.add(NamedImport.explicit(uri, qualifiedName, List.of(alias))); } } } public ImportCollection getAllImports(Resource resource) { return cache.get(ALL_IMPORTS_KEY, resource, () -> this.computeAllImports(resource)); } protected ImportCollection computeAllImports(Resource resource) { var collection = new ImportCollection(); collection.addAll(getDirectImports(resource).toList()); var loadOnDemand = loadOnDemandProvider.get(); loadOnDemand.setContext(resource); var seen = new HashSet(); seen.add(resource.getURI()); var queue = new ArrayDeque<>(collection.toUriSet()); while (!queue.isEmpty()) { var uri = queue.removeFirst(); seen.add(uri); collection.add(new TransitiveImport(uri)); var resourceDescription = loadOnDemand.getResourceDescription(uri); if (resourceDescription == null) { continue; } var problemDescriptions = resourceDescription.getExportedObjectsByType(ProblemPackage.Literals.PROBLEM); for (var eObjectDescription : problemDescriptions) { for (var importedUri : getImports(eObjectDescription)) { if (!seen.contains(importedUri)) { queue.addLast(importedUri); } } } } collection.remove(resource.getURI()); return collection; } protected List getImports(IEObjectDescription eObjectDescription) { var importString = eObjectDescription.getUserData(ProblemResourceDescriptionStrategy.IMPORTS); if (importString == null || importString.isEmpty()) { return List.of(); } return Splitter.on(ProblemResourceDescriptionStrategy.IMPORTS_SEPARATOR).splitToStream(importString) .map(URI::createURI) .toList(); } }