aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language/src/main/java
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-03 01:39:11 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-03 03:38:59 +0100
commitd5654f1ae03bec95c08e69a19a116c9825a27098 (patch)
treea12b59bbf54352cc51d1ae9bafef0eb10b8d28b4 /subprojects/language/src/main/java
parentrefactor(language): name disambiguation (diff)
downloadrefinery-d5654f1ae03bec95c08e69a19a116c9825a27098.tar.gz
refinery-d5654f1ae03bec95c08e69a19a116c9825a27098.tar.zst
refinery-d5654f1ae03bec95c08e69a19a116c9825a27098.zip
feat(language): import resolution
Diffstat (limited to 'subprojects/language/src/main/java')
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java1
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java41
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java54
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java20
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java49
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java12
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/state/DerivedVariableComputer.java (renamed from subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java)2
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/state/ImplicitVariableScope.java (renamed from subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java)2
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/state/NodeNameCollector.java (renamed from subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java)2
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java (renamed from subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java)2
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.java62
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java37
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java84
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java65
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java12
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java76
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java138
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java22
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java11
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java7
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java16
21 files changed, 637 insertions, 78 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
index 19816da4..a846e265 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
@@ -32,6 +32,7 @@ import tools.refinery.language.naming.ProblemQualifiedNameConverter;
32import tools.refinery.language.naming.ProblemQualifiedNameProvider; 32import tools.refinery.language.naming.ProblemQualifiedNameProvider;
33import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; 33import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser;
34import tools.refinery.language.resource.*; 34import tools.refinery.language.resource.*;
35import tools.refinery.language.resource.state.ProblemDerivedStateComputer;
35import tools.refinery.language.scoping.ProblemGlobalScopeProvider; 36import tools.refinery.language.scoping.ProblemGlobalScopeProvider;
36import tools.refinery.language.scoping.ProblemLocalScopeProvider; 37import tools.refinery.language.scoping.ProblemLocalScopeProvider;
37import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; 38import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java b/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java
new file mode 100644
index 00000000..0fb4cd2c
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.library;
7
8import org.eclipse.emf.common.util.URI;
9import org.eclipse.xtext.naming.QualifiedName;
10
11import java.util.List;
12import java.util.Optional;
13
14public class BuiltinLibrary implements RefineryLibrary {
15 public static final QualifiedName BUILTIN_LIBRARY_NAME = QualifiedName.create("builtin");
16 public static final URI BUILTIN_LIBRARY_URI = getLibraryUri(BUILTIN_LIBRARY_NAME).orElseThrow(
17 () -> new IllegalStateException("Builtin library was not found"));
18
19 @Override
20 public List<QualifiedName> getAutomaticImports() {
21 return List.of(BUILTIN_LIBRARY_NAME);
22 }
23
24 @Override
25 public Optional<URI> resolveQualifiedName(QualifiedName qualifiedName) {
26 if (qualifiedName.startsWith(BUILTIN_LIBRARY_NAME)) {
27 return getLibraryUri(qualifiedName);
28 }
29 return Optional.empty();
30 }
31
32 private static Optional<URI> getLibraryUri(QualifiedName qualifiedName) {
33 var libraryPath = String.join("/", qualifiedName.getSegments());
34 var libraryResource = BuiltinLibrary.class.getClassLoader()
35 .getResource("tools/refinery/language/library/%s.refinery".formatted(libraryPath));
36 if (libraryResource == null) {
37 return Optional.empty();
38 }
39 return Optional.of(URI.createURI(libraryResource.toString()));
40 }
41}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java
new file mode 100644
index 00000000..0efca199
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.library;
7
8import org.eclipse.emf.common.util.URI;
9import org.eclipse.xtext.naming.QualifiedName;
10import tools.refinery.language.scoping.imports.NamedImport;
11
12import java.util.LinkedHashMap;
13import java.util.List;
14import java.util.Optional;
15import java.util.ServiceLoader;
16
17public final class RefineryLibraries {
18 private static final ServiceLoader<RefineryLibrary> SERVICE_LOADER = ServiceLoader.load(RefineryLibrary.class);
19 private static final List<NamedImport> AUTOMATIC_IMPORTS;
20
21 static {
22 var imports = new LinkedHashMap<QualifiedName, URI>();
23 for (var service : SERVICE_LOADER) {
24 for (var qualifiedName : service.getAutomaticImports()) {
25 var uri = service.resolveQualifiedName(qualifiedName).orElseThrow(
26 () -> new IllegalStateException("Automatic import %s was not found".formatted(qualifiedName)));
27 if (imports.put(qualifiedName, uri) != null) {
28 throw new IllegalStateException("Duplicate automatic import " + qualifiedName);
29 }
30 }
31 }
32 AUTOMATIC_IMPORTS = imports.entrySet().stream()
33 .map(entry -> NamedImport.implicit(entry.getValue(), entry.getKey()))
34 .toList();
35 }
36
37 private RefineryLibraries() {
38 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
39 }
40
41 public static List<NamedImport> getAutomaticImports() {
42 return AUTOMATIC_IMPORTS;
43 }
44
45 public static Optional<URI> resolveQualifiedName(QualifiedName qualifiedName) {
46 for (var service : SERVICE_LOADER) {
47 var result = service.resolveQualifiedName(qualifiedName);
48 if (result.isPresent()) {
49 return result;
50 }
51 }
52 return Optional.empty();
53 }
54}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java
new file mode 100644
index 00000000..9db2900e
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.library;
7
8import org.eclipse.emf.common.util.URI;
9import org.eclipse.xtext.naming.QualifiedName;
10
11import java.util.List;
12import java.util.Optional;
13
14public interface RefineryLibrary {
15 default List<QualifiedName> getAutomaticImports() {
16 return List.of();
17 }
18
19 Optional<URI> resolveQualifiedName(QualifiedName qualifiedName);
20}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java b/subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java
new file mode 100644
index 00000000..373a32f2
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.resource;
7
8import com.google.inject.Inject;
9import org.eclipse.emf.common.util.URI;
10import org.eclipse.emf.ecore.resource.Resource;
11import org.eclipse.xtext.EcoreUtil2;
12import org.eclipse.xtext.resource.IResourceDescription;
13import org.eclipse.xtext.resource.IResourceDescriptions;
14import org.eclipse.xtext.resource.IResourceDescriptionsProvider;
15import org.eclipse.xtext.scoping.impl.GlobalResourceDescriptionProvider;
16
17public class LoadOnDemandResourceDescriptionProvider {
18 @Inject
19 private IResourceDescriptionsProvider resourceDescriptionsProvider;
20
21 @Inject
22 private GlobalResourceDescriptionProvider globalResourceDescriptionProvider;
23
24 private Resource context;
25 private IResourceDescriptions resourceDescriptions;
26
27 public void setContext(Resource context) {
28 if (this.context != null) {
29 throw new IllegalStateException("Context was already set");
30 }
31 this.context = context;
32 resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(context.getResourceSet());
33 }
34
35 public IResourceDescription getResourceDescription(URI uri) {
36 if (this.context == null) {
37 throw new IllegalStateException("Context was not set");
38 }
39 var resourceDescription = resourceDescriptions.getResourceDescription(uri);
40 if (resourceDescription != null) {
41 return resourceDescription;
42 }
43 var importedResource = EcoreUtil2.getResource(context, uri.toString());
44 if (importedResource == null) {
45 return null;
46 }
47 return globalResourceDescriptionProvider.getResourceDescription(importedResource);
48 }
49}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
index 76fd5852..a2ec7ed0 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
@@ -16,11 +16,13 @@ import org.eclipse.xtext.resource.EObjectDescription;
16import org.eclipse.xtext.resource.IEObjectDescription; 16import org.eclipse.xtext.resource.IEObjectDescription;
17import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; 17import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;
18import org.eclipse.xtext.util.IAcceptor; 18import org.eclipse.xtext.util.IAcceptor;
19import tools.refinery.language.scoping.imports.ImportCollector;
19import tools.refinery.language.model.problem.*; 20import tools.refinery.language.model.problem.*;
20import tools.refinery.language.naming.NamingUtil; 21import tools.refinery.language.naming.NamingUtil;
21import tools.refinery.language.utils.ProblemUtil; 22import tools.refinery.language.utils.ProblemUtil;
22 23
23import java.util.Map; 24import java.util.Map;
25import java.util.stream.Collectors;
24 26
25@Singleton 27@Singleton
26public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { 28public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy {
@@ -35,12 +37,17 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
35 public static final String SHADOWING_KEY_RELATION = "relation"; 37 public static final String SHADOWING_KEY_RELATION = "relation";
36 public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME"; 38 public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME";
37 public static final String PREFERRED_NAME_TRUE = "true"; 39 public static final String PREFERRED_NAME_TRUE = "true";
40 public static final String IMPORTS = DATA_PREFIX + "IMPORTS";
41 public static final String IMPORTS_SEPARATOR = "|";
38 public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; 42 public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION";
39 public static final String COLOR_RELATION_TRUE = "true"; 43 public static final String COLOR_RELATION_TRUE = "true";
40 44
41 @Inject 45 @Inject
42 private IQualifiedNameConverter qualifiedNameConverter; 46 private IQualifiedNameConverter qualifiedNameConverter;
43 47
48 @Inject
49 private ImportCollector importCollector;
50
44 @Override 51 @Override
45 public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) { 52 public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) {
46 if (!shouldExport(eObject)) { 53 if (!shouldExport(eObject)) {
@@ -115,6 +122,11 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
115 var builder = ImmutableMap.<String, String>builder(); 122 var builder = ImmutableMap.<String, String>builder();
116 if (eObject instanceof Problem) { 123 if (eObject instanceof Problem) {
117 builder.put(SHADOWING_KEY, SHADOWING_KEY_PROBLEM); 124 builder.put(SHADOWING_KEY, SHADOWING_KEY_PROBLEM);
125 var explicitImports = importCollector.getDirectImports(eObject.eResource());
126 var importsString = explicitImports.toList().stream()
127 .map(importEntry -> importEntry.uri().toString())
128 .collect(Collectors.joining(IMPORTS_SEPARATOR));
129 builder.put(IMPORTS, importsString);
118 } else if (eObject instanceof Node) { 130 } else if (eObject instanceof Node) {
119 builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE); 131 builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE);
120 } else if (eObject instanceof Relation relation) { 132 } else if (eObject instanceof Relation relation) {
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/DerivedVariableComputer.java
index 07c5da41..f0baf35f 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/DerivedVariableComputer.java
@@ -3,7 +3,7 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.resource; 6package tools.refinery.language.resource.state;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Singleton; 9import com.google.inject.Singleton;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ImplicitVariableScope.java
index e97c8287..e25887ad 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ImplicitVariableScope.java
@@ -3,7 +3,7 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.resource; 6package tools.refinery.language.resource.state;
7 7
8import org.eclipse.emf.ecore.EObject; 8import org.eclipse.emf.ecore.EObject;
9import org.eclipse.xtext.linking.impl.LinkingHelper; 9import org.eclipse.xtext.linking.impl.LinkingHelper;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/NodeNameCollector.java
index e5deca4d..de4a607c 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/NodeNameCollector.java
@@ -3,7 +3,7 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.resource; 6package tools.refinery.language.resource.state;
7 7
8import com.google.common.collect.ImmutableSet; 8import com.google.common.collect.ImmutableSet;
9import com.google.inject.Inject; 9import com.google.inject.Inject;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java
index 31eb55a6..d905aa9a 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java
@@ -3,7 +3,7 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.resource; 6package tools.refinery.language.resource.state;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Provider; 9import com.google.inject.Provider;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.java
new file mode 100644
index 00000000..0fddcaf9
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.java
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping;
7
8import com.google.common.collect.Iterables;
9import org.eclipse.emf.ecore.EClass;
10import org.eclipse.emf.ecore.EObject;
11import org.eclipse.xtext.naming.QualifiedName;
12import org.eclipse.xtext.resource.IEObjectDescription;
13import org.eclipse.xtext.resource.ISelectable;
14
15import java.util.Collection;
16import java.util.List;
17
18class CompositeSelectable implements ISelectable {
19 private static final CompositeSelectable EMPTY = new CompositeSelectable(List.of());
20
21 private final List<? extends ISelectable> children;
22
23 private CompositeSelectable(List<? extends ISelectable> children) {
24
25 this.children = children;
26 }
27
28 @Override
29 public boolean isEmpty() {
30 return children.isEmpty();
31 }
32
33 @Override
34 public Iterable<IEObjectDescription> getExportedObjects() {
35 return Iterables.concat(Iterables.transform(children, ISelectable::getExportedObjects));
36 }
37
38 @Override
39 public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) {
40 return Iterables.concat(Iterables.transform(children, child -> child.getExportedObjects(type, name,
41 ignoreCase)));
42 }
43
44 @Override
45 public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) {
46 return Iterables.concat(Iterables.transform(children, child -> child.getExportedObjectsByType(type)));
47 }
48
49 @Override
50 public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) {
51 return Iterables.concat(Iterables.transform(children, child -> child.getExportedObjectsByObject(object)));
52 }
53
54 public static ISelectable of(Collection<? extends ISelectable> children) {
55 var filteredChildren = children.stream().filter(selectable -> !selectable.isEmpty()).toList();
56 return switch (filteredChildren.size()) {
57 case 0 -> EMPTY;
58 case 1 -> filteredChildren.getFirst();
59 default -> new CompositeSelectable(filteredChildren);
60 };
61 }
62}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java
index 0c7828d8..09fe4716 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java
@@ -12,18 +12,21 @@ import org.eclipse.xtext.naming.QualifiedName;
12import org.eclipse.xtext.resource.IEObjectDescription; 12import org.eclipse.xtext.resource.IEObjectDescription;
13import org.eclipse.xtext.resource.ISelectable; 13import org.eclipse.xtext.resource.ISelectable;
14import org.eclipse.xtext.resource.impl.AliasedEObjectDescription; 14import org.eclipse.xtext.resource.impl.AliasedEObjectDescription;
15import org.jetbrains.annotations.NotNull;
16 15
17import java.util.Iterator; 16import java.util.Iterator;
17import java.util.List;
18import java.util.NoSuchElementException; 18import java.util.NoSuchElementException;
19 19
20public class NormalizedSelectable implements ISelectable { 20class NormalizedSelectable implements ISelectable {
21 private final ISelectable delegateSelectable; 21 private final ISelectable delegateSelectable;
22 private final QualifiedName originalPrefix; 22 private final QualifiedName originalPrefix;
23 private final QualifiedName normalizedPrefix; 23 private final QualifiedName normalizedPrefix;
24 24
25 private NormalizedSelectable(ISelectable delegateSelectable, QualifiedName originalPrefix, 25 public NormalizedSelectable(ISelectable delegateSelectable, QualifiedName originalPrefix,
26 QualifiedName normalizedPrefix) { 26 QualifiedName normalizedPrefix) {
27 if (originalPrefix.equals(QualifiedName.EMPTY)) {
28 throw new IllegalArgumentException("Cannot normalize empty qualified name prefix");
29 }
27 this.delegateSelectable = delegateSelectable; 30 this.delegateSelectable = delegateSelectable;
28 this.originalPrefix = originalPrefix; 31 this.originalPrefix = originalPrefix;
29 this.normalizedPrefix = normalizedPrefix; 32 this.normalizedPrefix = normalizedPrefix;
@@ -37,41 +40,36 @@ public class NormalizedSelectable implements ISelectable {
37 @Override 40 @Override
38 public Iterable<IEObjectDescription> getExportedObjects() { 41 public Iterable<IEObjectDescription> getExportedObjects() {
39 var delegateIterable = delegateSelectable.getExportedObjects(); 42 var delegateIterable = delegateSelectable.getExportedObjects();
40 var aliasedIterable = getAliasedElements(delegateIterable); 43 return getAliasedElements(delegateIterable);
41 return Iterables.concat(delegateIterable, aliasedIterable);
42 } 44 }
43 45
44 @Override 46 @Override
45 public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) { 47 public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) {
46 var delegateIterable = delegateSelectable.getExportedObjects(type, name, ignoreCase);
47 boolean startsWith = ignoreCase ? name.startsWithIgnoreCase(normalizedPrefix) : 48 boolean startsWith = ignoreCase ? name.startsWithIgnoreCase(normalizedPrefix) :
48 name.startsWith(normalizedPrefix); 49 name.startsWith(normalizedPrefix);
49 if (startsWith && name.getSegmentCount() > normalizedPrefix.getSegmentCount()) { 50 if (startsWith && name.getSegmentCount() > normalizedPrefix.getSegmentCount()) {
50 var originalName = originalPrefix.append(name.skipFirst(normalizedPrefix.getSegmentCount())); 51 var originalName = originalPrefix.append(name.skipFirst(normalizedPrefix.getSegmentCount()));
51 var originalIterable = Iterables.transform( 52 return Iterables.transform(
52 delegateSelectable.getExportedObjects(type, originalName, ignoreCase), 53 delegateSelectable.getExportedObjects(type, originalName, ignoreCase),
53 description -> { 54 description -> {
54 var normalizedName = normalizedPrefix.append( 55 var normalizedName = normalizedPrefix.append(
55 description.getName().skipFirst(originalPrefix.getSegmentCount())); 56 description.getName().skipFirst(originalPrefix.getSegmentCount()));
56 return new AliasedEObjectDescription(normalizedName, description); 57 return new AliasedEObjectDescription(normalizedName, description);
57 }); 58 });
58 return Iterables.concat(originalIterable, delegateIterable);
59 } 59 }
60 return delegateIterable; 60 return List.of();
61 } 61 }
62 62
63 @Override 63 @Override
64 public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { 64 public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) {
65 var delegateIterable = delegateSelectable.getExportedObjectsByType(type); 65 var delegateIterable = delegateSelectable.getExportedObjectsByType(type);
66 var aliasedIterable = getAliasedElements(delegateIterable); 66 return getAliasedElements(delegateIterable);
67 return Iterables.concat(delegateIterable, aliasedIterable);
68 } 67 }
69 68
70 @Override 69 @Override
71 public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { 70 public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) {
72 var delegateIterable = delegateSelectable.getExportedObjectsByObject(object); 71 var delegateIterable = delegateSelectable.getExportedObjectsByObject(object);
73 var aliasedIterable = getAliasedElements(delegateIterable); 72 return getAliasedElements(delegateIterable);
74 return Iterables.concat(delegateIterable, aliasedIterable);
75 } 73 }
76 74
77 private Iterable<IEObjectDescription> getAliasedElements(Iterable<IEObjectDescription> delegateIterable) { 75 private Iterable<IEObjectDescription> getAliasedElements(Iterable<IEObjectDescription> delegateIterable) {
@@ -108,15 +106,4 @@ public class NormalizedSelectable implements ISelectable {
108 } 106 }
109 }; 107 };
110 } 108 }
111
112 public static ISelectable of(@NotNull ISelectable delegateSelectable, @NotNull QualifiedName originalPrefix,
113 @NotNull QualifiedName normalizedPrefix) {
114 if (originalPrefix.equals(normalizedPrefix)) {
115 return delegateSelectable;
116 }
117 if (originalPrefix.equals(QualifiedName.EMPTY)) {
118 throw new IllegalArgumentException("Cannot normalize empty qualified name prefix");
119 }
120 return new NormalizedSelectable(delegateSelectable, originalPrefix, normalizedPrefix);
121 }
122} 109}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java
index 37a67c0c..dad4e5d0 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java
@@ -6,35 +6,85 @@
6package tools.refinery.language.scoping; 6package tools.refinery.language.scoping;
7 7
8import com.google.common.base.Predicate; 8import com.google.common.base.Predicate;
9import org.eclipse.emf.common.util.URI; 9import com.google.inject.Inject;
10import com.google.inject.Provider;
10import org.eclipse.emf.ecore.EClass; 11import org.eclipse.emf.ecore.EClass;
11import org.eclipse.emf.ecore.resource.Resource; 12import org.eclipse.emf.ecore.resource.Resource;
12import org.eclipse.xtext.naming.QualifiedName; 13import org.eclipse.xtext.naming.QualifiedName;
13import org.eclipse.xtext.resource.IEObjectDescription; 14import org.eclipse.xtext.resource.IEObjectDescription;
14import org.eclipse.xtext.resource.IResourceDescriptions;
15import org.eclipse.xtext.resource.ISelectable; 15import org.eclipse.xtext.resource.ISelectable;
16import org.eclipse.xtext.scoping.IScope; 16import org.eclipse.xtext.scoping.IScope;
17import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; 17import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeProvider;
18import org.eclipse.xtext.scoping.impl.SelectableBasedScope; 18import org.eclipse.xtext.scoping.impl.SelectableBasedScope;
19import tools.refinery.language.utils.ProblemUtil; 19import org.eclipse.xtext.util.IResourceScopeCache;
20import tools.refinery.language.scoping.imports.ImportCollector;
21import tools.refinery.language.resource.LoadOnDemandResourceDescriptionProvider;
22import tools.refinery.language.scoping.imports.NamedImport;
20 23
21import java.util.LinkedHashSet; 24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.List;
22 27
23public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { 28public class ProblemGlobalScopeProvider extends AbstractGlobalScopeProvider {
29 private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemGlobalScopeProvider.CACHE_KEY";
30
31 @Inject
32 private ImportCollector importCollector;
33
34 @Inject
35 private Provider<LoadOnDemandResourceDescriptionProvider> loadOnDemandProvider;
36
37 @Inject
38 private IResourceScopeCache cache;
39
40 // {@link com.google.common.base.Predicate} required by Xtext API.
41 @SuppressWarnings("squid:S4738")
24 @Override 42 @Override
25 protected LinkedHashSet<URI> getImportedUris(Resource resource) { 43 protected IScope getScope(Resource resource, boolean ignoreCase, EClass type,
26 LinkedHashSet<URI> importedUris = new LinkedHashSet<>(); 44 Predicate<IEObjectDescription> filter) {
27 importedUris.add(ProblemUtil.BUILTIN_LIBRARY_URI); 45 var loadedImports = cache.get(CACHE_KEY, resource, () -> computeLoadedImports(resource));
28 return importedUris; 46 var qualifiedScope = createScope(IScope.NULLSCOPE, loadedImports.qualifiedImports(), type, filter, ignoreCase);
47 var implicitScope = createScope(qualifiedScope, loadedImports.implicitImports(), type, filter, ignoreCase);
48 return createScope(implicitScope, loadedImports.explicitImports(), type, filter, ignoreCase);
29 } 49 }
30 50
31 @Override 51 protected LoadedImports computeLoadedImports(Resource resource) {
32 protected IScope createLazyResourceScope(IScope parent, URI uri, IResourceDescriptions descriptions, EClass type, 52 var imports = importCollector.getAllImports(resource);
33 Predicate<IEObjectDescription> filter, boolean ignoreCase) { 53 var loadOnDemand = loadOnDemandProvider.get();
34 ISelectable description = descriptions.getResourceDescription(uri); 54 loadOnDemand.setContext(resource);
35 if (description != null && ProblemUtil.BUILTIN_LIBRARY_URI.equals(uri)) { 55 var qualifiedImports = new ArrayList<ISelectable>();
36 description = NormalizedSelectable.of(description, QualifiedName.create("builtin"), QualifiedName.EMPTY); 56 var implicitImports = new ArrayList<ISelectable>();
57 var explicitImports = new ArrayList<ISelectable>();
58 for (var importEntry : imports.toList()) {
59 var uri = importEntry.uri();
60 var resourceDescription = loadOnDemand.getResourceDescription(uri);
61 if (resourceDescription == null) {
62 continue;
63 }
64 qualifiedImports.add(resourceDescription);
65 if (importEntry instanceof NamedImport namedImport) {
66 var qualifiedName = namedImport.qualifiedName();
67 if (namedImport.alsoImplicit()) {
68 implicitImports.add(new NormalizedSelectable(resourceDescription, qualifiedName,
69 QualifiedName.EMPTY));
70 }
71 for (var alias : namedImport.aliases()) {
72 explicitImports.add(new NormalizedSelectable(resourceDescription, qualifiedName, alias));
73 }
74 }
37 } 75 }
38 return SelectableBasedScope.createScope(parent, description, filter, type, ignoreCase); 76 return new LoadedImports(qualifiedImports, implicitImports, explicitImports);
77 }
78
79 // {@link com.google.common.base.Predicate} required by Xtext API.
80 @SuppressWarnings("squid:S4738")
81 protected IScope createScope(IScope parent, Collection<? extends ISelectable> children, EClass type,
82 Predicate<IEObjectDescription> filter, boolean ignoreCase) {
83 var selectable = CompositeSelectable.of(children);
84 return SelectableBasedScope.createScope(parent, selectable, filter, type, ignoreCase);
85 }
86
87 protected record LoadedImports(List<ISelectable> qualifiedImports, List<ISelectable> implicitImports,
88 List<ISelectable> explicitImports) {
39 } 89 }
40} 90}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
index 9be32636..3e00b87e 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
@@ -6,37 +6,72 @@
6package tools.refinery.language.scoping; 6package tools.refinery.language.scoping;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import org.eclipse.emf.ecore.EObject;
10import org.eclipse.emf.ecore.EReference;
9import org.eclipse.emf.ecore.resource.Resource; 11import org.eclipse.emf.ecore.resource.Resource;
10import org.eclipse.xtext.naming.IQualifiedNameProvider; 12import org.eclipse.xtext.naming.IQualifiedNameProvider;
11import org.eclipse.xtext.naming.QualifiedName; 13import org.eclipse.xtext.naming.QualifiedName;
14import org.eclipse.xtext.resource.IResourceDescription;
12import org.eclipse.xtext.resource.IResourceDescriptionsProvider; 15import org.eclipse.xtext.resource.IResourceDescriptionsProvider;
13import org.eclipse.xtext.resource.ISelectable; 16import org.eclipse.xtext.resource.ISelectable;
14import org.eclipse.xtext.scoping.impl.SimpleLocalScopeProvider; 17import org.eclipse.xtext.scoping.IScope;
18import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeDelegatingScopeProvider;
19import org.eclipse.xtext.scoping.impl.SelectableBasedScope;
20import org.eclipse.xtext.util.IResourceScopeCache;
15import tools.refinery.language.naming.NamingUtil; 21import tools.refinery.language.naming.NamingUtil;
16 22
17public class ProblemLocalScopeProvider extends SimpleLocalScopeProvider { 23public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScopeProvider {
24 private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemLocalScopeProvider.CACHE_KEY";
25
18 @Inject 26 @Inject
19 private IQualifiedNameProvider qualifiedNameProvider; 27 private IQualifiedNameProvider qualifiedNameProvider;
20 28
21 @Inject 29 @Inject
22 private IResourceDescriptionsProvider resourceDescriptionsProvider; 30 private IResourceDescriptionsProvider resourceDescriptionsProvider;
23 31
32 @Inject
33 private IResourceScopeCache cache;
34
24 @Override 35 @Override
25 protected ISelectable getAllDescriptions(Resource resource) { 36 public IScope getScope(EObject context, EReference reference) {
37 var resource = context.eResource();
38 if (resource == null) {
39 return IScope.NULLSCOPE;
40 }
41 var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource));
42 if (localImports.resourceDescription() == null) {
43 return IScope.NULLSCOPE;
44 }
45 var globalScope = getGlobalScope(resource, reference);
46 var type = reference.getEReferenceType();
47 boolean ignoreCase = isIgnoreCase(reference);
48 var scope = SelectableBasedScope.createScope(globalScope, localImports.resourceDescription(), type,
49 ignoreCase);
50 if (localImports.normalizedSelectable() == null) {
51 return scope;
52 }
53 return SelectableBasedScope.createScope(scope, localImports.normalizedSelectable(), type, ignoreCase);
54 }
55
56 protected LocalImports computeLocalImports(Resource resource) {
26 // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. 57 // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects.
27 var resourceDescriptions = resourceDescriptionsProvider 58 var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet());
28 .getResourceDescriptions(resource.getResourceSet());
29 var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); 59 var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI());
30 if (resourceDescription != null && !resource.getContents().isEmpty()) { 60 if (resourceDescription == null) {
31 var rootElement = resource.getContents().getFirst(); 61 return new LocalImports(null, null);
32 if (rootElement != null) { 62 }
33 var rootName = NamingUtil.stripRootPrefix(qualifiedNameProvider.getFullyQualifiedName(rootElement)); 63 var rootElement = resource.getContents().getFirst();
34 if (rootName == null) { 64 if (rootElement == null) {
35 return resourceDescription; 65 return new LocalImports(resourceDescription, null);
36 }
37 return NormalizedSelectable.of(resourceDescription, rootName, QualifiedName.EMPTY);
38 }
39 } 66 }
40 return resourceDescription; 67 var rootName = NamingUtil.stripRootPrefix(qualifiedNameProvider.getFullyQualifiedName(rootElement));
68 if (rootName == null) {
69 return new LocalImports(resourceDescription, null);
70 }
71 var normalizedSelectable = new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY);
72 return new LocalImports(resourceDescription, normalizedSelectable);
73 }
74
75 protected record LocalImports(IResourceDescription resourceDescription, ISelectable normalizedSelectable) {
41 } 76 }
42} 77}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java
new file mode 100644
index 00000000..e2f0d3d8
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping.imports;
7
8import org.eclipse.emf.common.util.URI;
9
10public sealed interface Import permits NamedImport, TransitiveImport {
11 URI uri();
12}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java
new file mode 100644
index 00000000..63171138
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java
@@ -0,0 +1,76 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping.imports;
7
8import org.eclipse.emf.common.util.URI;
9import org.jetbrains.annotations.NotNull;
10import org.jetbrains.annotations.Nullable;
11
12import java.util.*;
13
14public class ImportCollection {
15 public static ImportCollection EMPTY = new ImportCollection() {
16 @Override
17 public void add(Import importEntry) {
18 throw new UnsupportedOperationException("Read-only collection");
19 }
20
21 @Override
22 public void remove(URI uri) {
23 throw new UnsupportedOperationException("Read-only collection");
24 }
25 };
26
27 private final Map<URI, Import> importMap = new HashMap<>();
28
29 public void add(Import importEntry) {
30 importMap.compute(importEntry.uri(), (ignored, originalEntry) -> merge(originalEntry, importEntry));
31 }
32
33 public void addAll(Iterable<? extends Import> imports) {
34 imports.forEach(this::add);
35 }
36
37 public void remove(URI uri) {
38 importMap.remove(uri);
39 }
40
41 public List<Import> toList() {
42 return List.copyOf(importMap.values());
43 }
44
45 public Set<URI> toUriSet() {
46 return new LinkedHashSet<>(importMap.keySet());
47 }
48
49 @NotNull
50 private static Import merge(@Nullable Import left, @NotNull Import right) {
51 if (left == null) {
52 return right;
53 }
54 if (!left.uri().equals(right.uri())) {
55 throw new IllegalArgumentException("Expected URIs '%s' and '%s' to be equal".formatted(
56 left.uri(), right.uri()));
57 }
58 return switch (left) {
59 case TransitiveImport transitiveLeft ->
60 right instanceof TransitiveImport ? left : merge(right, transitiveLeft);
61 case NamedImport namedLeft -> switch (right) {
62 case TransitiveImport ignored -> namedLeft;
63 case NamedImport namedRight -> {
64 if (!namedLeft.qualifiedName().equals(namedRight.qualifiedName())) {
65 throw new IllegalArgumentException("Expected qualified names '%s' and '%s' to be equal"
66 .formatted(namedLeft.qualifiedName(), namedRight.qualifiedName()));
67 }
68 var mergedAliases = new LinkedHashSet<>(namedLeft.aliases());
69 mergedAliases.addAll(namedRight.aliases());
70 yield new NamedImport(namedLeft.uri(), namedLeft.qualifiedName(),
71 List.copyOf(mergedAliases), namedLeft.alsoImplicit() || namedRight.alsoImplicit());
72 }
73 };
74 };
75 }
76}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java
new file mode 100644
index 00000000..cea99f0a
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java
@@ -0,0 +1,138 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping.imports;
7
8import com.google.common.base.Splitter;
9import com.google.common.base.Strings;
10import com.google.inject.Inject;
11import com.google.inject.Provider;
12import com.google.inject.Singleton;
13import org.eclipse.emf.common.util.URI;
14import org.eclipse.emf.ecore.resource.Resource;
15import org.eclipse.xtext.linking.impl.LinkingHelper;
16import org.eclipse.xtext.naming.IQualifiedNameConverter;
17import org.eclipse.xtext.naming.QualifiedName;
18import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
19import org.eclipse.xtext.resource.IEObjectDescription;
20import org.eclipse.xtext.util.IResourceScopeCache;
21import tools.refinery.language.library.RefineryLibraries;
22import tools.refinery.language.model.problem.ImportStatement;
23import tools.refinery.language.model.problem.Problem;
24import tools.refinery.language.model.problem.ProblemPackage;
25import tools.refinery.language.naming.NamingUtil;
26import tools.refinery.language.resource.LoadOnDemandResourceDescriptionProvider;
27import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
28
29import java.util.*;
30
31@Singleton
32public class ImportCollector {
33 private static final String PREFIX = "tools.refinery.language.imports.";
34 private static final String DIRECT_IMPORTS_KEY = PREFIX + "DIRECT_IMPORTS";
35 private static final String ALL_IMPORTS_KEY = PREFIX + "ALL_IMPORTS";
36
37 @Inject
38 private IResourceScopeCache cache;
39
40 @Inject
41 private LinkingHelper linkingHelper;
42
43 @Inject
44 private IQualifiedNameConverter qualifiedNameConverter;
45
46 @Inject
47 private Provider<LoadOnDemandResourceDescriptionProvider> loadOnDemandProvider;
48
49 public ImportCollection getDirectImports(Resource resource) {
50 return cache.get(DIRECT_IMPORTS_KEY, resource, () -> this.computeDirectImports(resource));
51 }
52
53 protected ImportCollection computeDirectImports(Resource resource) {
54 if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) {
55 return ImportCollection.EMPTY;
56 }
57 Map<QualifiedName, Set<QualifiedName>> aliasesMap = new LinkedHashMap<>();
58 for (var statement : problem.getStatements()) {
59 if (statement instanceof ImportStatement importStatement) {
60 collectImportStatement(importStatement, aliasesMap);
61 }
62 }
63 var collection = new ImportCollection();
64 collection.addAll(RefineryLibraries.getAutomaticImports());
65 for (var entry : aliasesMap.entrySet()) {
66 var qualifiedName = entry.getKey();
67 RefineryLibraries.resolveQualifiedName(qualifiedName).ifPresent(uri -> {
68 if (!uri.equals(resource.getURI())) {
69 var aliases = entry.getValue();
70 collection.add(NamedImport.explicit(uri, qualifiedName, List.copyOf(aliases)));
71 }
72 });
73 }
74 collection.remove(resource.getURI());
75 return collection;
76 }
77
78 private void collectImportStatement(ImportStatement importStatement, Map<QualifiedName, Set<QualifiedName>> aliasesMap) {
79 var nodes = NodeModelUtils.findNodesForFeature(importStatement,
80 ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE);
81 var aliasString = importStatement.getAlias();
82 var alias = Strings.isNullOrEmpty(aliasString) ? QualifiedName.EMPTY :
83 NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(aliasString));
84 for (var node : nodes) {
85 var qualifiedNameString = linkingHelper.getCrossRefNodeAsString(node, true);
86 if (Strings.isNullOrEmpty(qualifiedNameString)) {
87 continue;
88 }
89 var qualifiedName = NamingUtil.stripRootPrefix(
90 qualifiedNameConverter.toQualifiedName(qualifiedNameString));
91 var aliases = aliasesMap.computeIfAbsent(qualifiedName, ignored -> new LinkedHashSet<>());
92 aliases.add(alias);
93 }
94 }
95
96 public ImportCollection getAllImports(Resource resource) {
97 return cache.get(ALL_IMPORTS_KEY, resource, () -> this.computeAllImports(resource));
98 }
99
100 protected ImportCollection computeAllImports(Resource resource) {
101 var collection = new ImportCollection();
102 collection.addAll(getDirectImports(resource).toList());
103 var loadOnDemand = loadOnDemandProvider.get();
104 loadOnDemand.setContext(resource);
105 var seen = new HashSet<URI>();
106 seen.add(resource.getURI());
107 var queue = new ArrayDeque<>(collection.toUriSet());
108 while (!queue.isEmpty()) {
109 var uri = queue.removeFirst();
110 seen.add(uri);
111 collection.add(new TransitiveImport(uri));
112 var resourceDescription = loadOnDemand.getResourceDescription(uri);
113 if (resourceDescription == null) {
114 continue;
115 }
116 var problemDescriptions = resourceDescription.getExportedObjectsByType(ProblemPackage.Literals.PROBLEM);
117 for (var eObjectDescription : problemDescriptions) {
118 for (var importedUri : getImports(eObjectDescription)) {
119 if (!seen.contains(importedUri)) {
120 queue.addLast(importedUri);
121 }
122 }
123 }
124 }
125 collection.remove(resource.getURI());
126 return collection;
127 }
128
129 protected List<URI> getImports(IEObjectDescription eObjectDescription) {
130 var importString = eObjectDescription.getUserData(ProblemResourceDescriptionStrategy.IMPORTS);
131 if (importString == null) {
132 return List.of();
133 }
134 return Splitter.on(ProblemResourceDescriptionStrategy.IMPORTS_SEPARATOR).splitToStream(importString)
135 .map(URI::createURI)
136 .toList();
137 }
138}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java
new file mode 100644
index 00000000..f5e89605
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping.imports;
7
8import org.eclipse.emf.common.util.URI;
9import org.eclipse.xtext.naming.QualifiedName;
10
11import java.util.List;
12
13public record NamedImport(URI uri, QualifiedName qualifiedName, List<QualifiedName> aliases,
14 boolean alsoImplicit) implements Import {
15 public static NamedImport implicit(URI uri, QualifiedName qualifiedName) {
16 return new NamedImport(uri, qualifiedName, List.of(), true);
17 }
18
19 public static NamedImport explicit(URI uri, QualifiedName qualifiedName, List<QualifiedName> aliases) {
20 return new NamedImport(uri, qualifiedName, aliases, false);
21 }
22}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java
new file mode 100644
index 00000000..6f5ceaa6
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping.imports;
7
8import org.eclipse.emf.common.util.URI;
9
10public record TransitiveImport(URI uri) implements Import {
11}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
index 59e26561..0bd1e50b 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -11,6 +11,7 @@ import org.eclipse.emf.ecore.EObject;
11import org.eclipse.emf.ecore.resource.Resource; 11import org.eclipse.emf.ecore.resource.Resource;
12import org.eclipse.xtext.util.IResourceScopeCache; 12import org.eclipse.xtext.util.IResourceScopeCache;
13import org.eclipse.xtext.util.Tuples; 13import org.eclipse.xtext.util.Tuples;
14import tools.refinery.language.library.BuiltinLibrary;
14import tools.refinery.language.model.problem.*; 15import tools.refinery.language.model.problem.*;
15 16
16import java.util.*; 17import java.util.*;
@@ -27,8 +28,8 @@ public class ProblemDesugarer {
27 28
28 private Optional<Problem> doGetBuiltinProblem(Resource resource) { 29 private Optional<Problem> doGetBuiltinProblem(Resource resource) {
29 return Optional.ofNullable(resource).map(Resource::getResourceSet) 30 return Optional.ofNullable(resource).map(Resource::getResourceSet)
30 .map(resourceSet -> resourceSet.getResource(ProblemUtil.BUILTIN_LIBRARY_URI, true)) 31 .map(resourceSet -> resourceSet.getResource(BuiltinLibrary.BUILTIN_LIBRARY_URI, true))
31 .map(Resource::getContents).filter(contents -> !contents.isEmpty()).map(contents -> contents.get(0)) 32 .map(Resource::getContents).filter(contents -> !contents.isEmpty()).map(List::getFirst)
32 .filter(Problem.class::isInstance).map(Problem.class::cast); 33 .filter(Problem.class::isInstance).map(Problem.class::cast);
33 } 34 }
34 35
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
index 0f87c04b..23ff55e7 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
@@ -5,16 +5,13 @@
5 */ 5 */
6package tools.refinery.language.utils; 6package tools.refinery.language.utils;
7 7
8import org.eclipse.emf.common.util.URI;
9import org.eclipse.emf.ecore.EObject; 8import org.eclipse.emf.ecore.EObject;
10import org.eclipse.emf.ecore.util.EcoreUtil; 9import org.eclipse.emf.ecore.util.EcoreUtil;
11import org.eclipse.xtext.EcoreUtil2; 10import org.eclipse.xtext.EcoreUtil2;
11import tools.refinery.language.library.BuiltinLibrary;
12import tools.refinery.language.model.problem.*; 12import tools.refinery.language.model.problem.*;
13 13
14public final class ProblemUtil { 14public final class ProblemUtil {
15 public static final String BUILTIN_LIBRARY_NAME = "builtin";
16 public static final URI BUILTIN_LIBRARY_URI = getLibraryUri();
17
18 private ProblemUtil() { 15 private ProblemUtil() {
19 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 16 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
20 } 17 }
@@ -23,7 +20,7 @@ public final class ProblemUtil {
23 if (eObject != null) { 20 if (eObject != null) {
24 var eResource = eObject.eResource(); 21 var eResource = eObject.eResource();
25 if (eResource != null) { 22 if (eResource != null) {
26 return ProblemUtil.BUILTIN_LIBRARY_URI.equals(eResource.getURI()); 23 return BuiltinLibrary.BUILTIN_LIBRARY_URI.equals(eResource.getURI());
27 } 24 }
28 } 25 }
29 return false; 26 return false;
@@ -131,13 +128,4 @@ public final class ProblemUtil {
131 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); 128 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class);
132 return problem != null && problem.getKind() == ModuleKind.MODULE; 129 return problem != null && problem.getKind() == ModuleKind.MODULE;
133 } 130 }
134
135 private static URI getLibraryUri() {
136 var libraryResource = ProblemUtil.class.getClassLoader()
137 .getResource("tools/refinery/language/%s.problem".formatted(BUILTIN_LIBRARY_NAME));
138 if (libraryResource == null) {
139 throw new AssertionError("Library '%s' was not found".formatted(BUILTIN_LIBRARY_NAME));
140 }
141 return URI.createURI(libraryResource.toString());
142 }
143} 131}