aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-04 22:06:56 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-04 22:06:56 +0100
commit4dd2b4e1f45bf940d969181ae9b9176fa353c92e (patch)
tree72e315d726014966d8c701686962bfb5fbdea5a7
parentfeat(language): import resolution (diff)
downloadrefinery-4dd2b4e1f45bf940d969181ae9b9176fa353c92e.tar.gz
refinery-4dd2b4e1f45bf940d969181ae9b9176fa353c92e.tar.zst
refinery-4dd2b4e1f45bf940d969181ae9b9176fa353c92e.zip
feat: filesystem-level import resolution
Modules without an explicitly declared name get a name automatically inferred from their path.
-rw-r--r--subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java1
-rw-r--r--subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java30
-rw-r--r--subprojects/language-model/problem.aird2
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java29
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/library/ClasspathBasedLibrary.java98
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/library/PathLibrary.java90
-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.java7
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java34
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java27
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java8
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java230
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java42
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java6
-rw-r--r--subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary1
-rw-r--r--subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery4
17 files changed, 556 insertions, 112 deletions
diff --git a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
index b33fce23..b1c28df0 100644
--- a/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
+++ b/subprojects/generator-cli/src/main/java/tools/refinery/generator/cli/commands/GenerateCommand.java
@@ -58,6 +58,7 @@ public class GenerateCommand {
58 } 58 }
59 59
60 public void run() throws IOException { 60 public void run() throws IOException {
61 loader.extraPath(System.getProperty("user.dir"));
61 var problem = isStandardStream(inputPath) ? loader.loadStream(System.in) : loader.loadFile(inputPath); 62 var problem = isStandardStream(inputPath) ? loader.loadStream(System.in) : loader.loadFile(inputPath);
62 problem = loader.loadScopeConstraints(problem, scopes, overrideScopes); 63 problem = loader.loadScopeConstraints(problem, scopes, overrideScopes);
63 var generator = generatorFactory.createGenerator(problem); 64 var generator = generatorFactory.createGenerator(problem);
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
index 20ea8132..e44dddc0 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-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 */
@@ -19,6 +19,7 @@ import org.eclipse.xtext.validation.IResourceValidator;
19import tools.refinery.language.model.problem.Problem; 19import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.model.problem.Relation; 20import tools.refinery.language.model.problem.Relation;
21import tools.refinery.language.model.problem.ScopeDeclaration; 21import tools.refinery.language.model.problem.ScopeDeclaration;
22import tools.refinery.language.scoping.imports.ImportAdapter;
22import tools.refinery.store.util.CancellationToken; 23import tools.refinery.store.util.CancellationToken;
23 24
24import java.io.ByteArrayOutputStream; 25import java.io.ByteArrayOutputStream;
@@ -26,11 +27,14 @@ import java.io.File;
26import java.io.IOException; 27import java.io.IOException;
27import java.io.InputStream; 28import java.io.InputStream;
28import java.nio.charset.StandardCharsets; 29import java.nio.charset.StandardCharsets;
30import java.nio.file.Path;
29import java.util.ArrayList; 31import java.util.ArrayList;
30import java.util.HashSet; 32import java.util.HashSet;
31import java.util.List; 33import java.util.List;
32import java.util.Map; 34import java.util.Map;
33 35
36// This class is used as a fluent builder.
37@SuppressWarnings("UnusedReturnValue")
34public class ProblemLoader { 38public class ProblemLoader {
35 private String fileExtension; 39 private String fileExtension;
36 40
@@ -45,6 +49,8 @@ public class ProblemLoader {
45 49
46 private CancellationToken cancellationToken = CancellationToken.NONE; 50 private CancellationToken cancellationToken = CancellationToken.NONE;
47 51
52 private final List<Path> extraPaths = new ArrayList<>();
53
48 @Inject 54 @Inject
49 public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) { 55 public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) {
50 this.fileExtension = fileExtensionProvider.getPrimaryFileExtension(); 56 this.fileExtension = fileExtensionProvider.getPrimaryFileExtension();
@@ -55,6 +61,15 @@ public class ProblemLoader {
55 return this; 61 return this;
56 } 62 }
57 63
64 public ProblemLoader extraPath(String path) {
65 return extraPath(Path.of(path));
66 }
67
68 public ProblemLoader extraPath(Path path) {
69 extraPaths.add(path.toAbsolutePath().normalize());
70 return this;
71 }
72
58 public Problem loadString(String problemString, URI uri) throws IOException { 73 public Problem loadString(String problemString, URI uri) throws IOException {
59 try (var stream = new LazyStringInputStream(problemString)) { 74 try (var stream = new LazyStringInputStream(problemString)) {
60 return loadStream(stream, uri); 75 return loadStream(stream, uri);
@@ -66,8 +81,8 @@ public class ProblemLoader {
66 } 81 }
67 82
68 public Problem loadStream(InputStream inputStream, URI uri) throws IOException { 83 public Problem loadStream(InputStream inputStream, URI uri) throws IOException {
69 var resourceSet = resourceSetProvider.get(); 84 var resourceSet = createResourceSet();
70 var resourceUri = uri == null ? URI.createFileURI("__synthetic." + fileExtension) : uri; 85 var resourceUri = uri == null ? URI.createURI("__synthetic." + fileExtension) : uri;
71 var resource = resourceFactory.createResource(resourceUri); 86 var resource = resourceFactory.createResource(resourceUri);
72 resourceSet.getResources().add(resource); 87 resourceSet.getResources().add(resource);
73 resource.load(inputStream, Map.of()); 88 resource.load(inputStream, Map.of());
@@ -87,13 +102,20 @@ public class ProblemLoader {
87 } 102 }
88 103
89 public Problem loadUri(URI uri) throws IOException { 104 public Problem loadUri(URI uri) throws IOException {
90 var resourceSet = resourceSetProvider.get(); 105 var resourceSet = createResourceSet();
91 var resource = resourceFactory.createResource(uri); 106 var resource = resourceFactory.createResource(uri);
92 resourceSet.getResources().add(resource); 107 resourceSet.getResources().add(resource);
93 resource.load(Map.of()); 108 resource.load(Map.of());
94 return loadResource(resource); 109 return loadResource(resource);
95 } 110 }
96 111
112 private XtextResourceSet createResourceSet() {
113 var resourceSet = resourceSetProvider.get();
114 var adapter = ImportAdapter.getOrInstall(resourceSet);
115 adapter.getLibraryPaths().addAll(0, extraPaths);
116 return resourceSet;
117 }
118
97 public Problem loadResource(Resource resource) { 119 public Problem loadResource(Resource resource) {
98 var issues = resourceValidator.validate(resource, CheckMode.ALL, () -> { 120 var issues = resourceValidator.validate(resource, CheckMode.ALL, () -> {
99 cancellationToken.checkCancelled(); 121 cancellationToken.checkCancelled();
diff --git a/subprojects/language-model/problem.aird b/subprojects/language-model/problem.aird
index 6583d364..ed3a4b58 100644
--- a/subprojects/language-model/problem.aird
+++ b/subprojects/language-model/problem.aird
@@ -7,7 +7,7 @@
7 <semanticResources>build/resources/main/model/problem.genmodel</semanticResources> 7 <semanticResources>build/resources/main/model/problem.genmodel</semanticResources>
8 <ownedViews xmi:type="viewpoint:DView" uid="_CsAAYKA4EeuqkpDnuik1sg"> 8 <ownedViews xmi:type="viewpoint:DView" uid="_CsAAYKA4EeuqkpDnuik1sg">
9 <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/> 9 <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/>
10 <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_CsYa4KA4EeuqkpDnuik1sg" name="declarations" repPath="#_CsUwgKA4EeuqkpDnuik1sg" changeId="1706722245720"> 10 <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_CsYa4KA4EeuqkpDnuik1sg" name="declarations" repPath="#_CsUwgKA4EeuqkpDnuik1sg" changeId="1707054359028">
11 <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/> 11 <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/>
12 <target xmi:type="ecore:EPackage" href="src/main/resources/model/problem.ecore#/"/> 12 <target xmi:type="ecore:EPackage" href="src/main/resources/model/problem.ecore#/"/>
13 </ownedRepresentationDescriptors> 13 </ownedRepresentationDescriptors>
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
index 1bd419d8..dad867b8 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
@@ -19,6 +19,7 @@ import org.eclipse.xtext.resource.XtextResource;
19import org.eclipse.xtext.resource.XtextResourceSet; 19import org.eclipse.xtext.resource.XtextResourceSet;
20import org.eclipse.xtext.scoping.IScopeProvider; 20import org.eclipse.xtext.scoping.IScopeProvider;
21import tools.refinery.language.model.problem.*; 21import tools.refinery.language.model.problem.*;
22import tools.refinery.language.scoping.imports.ImportAdapter;
22import tools.refinery.language.utils.ProblemDesugarer; 23import tools.refinery.language.utils.ProblemDesugarer;
23import tools.refinery.language.utils.ProblemUtil; 24import tools.refinery.language.utils.ProblemUtil;
24import tools.refinery.store.model.Model; 25import tools.refinery.store.model.Model;
@@ -79,7 +80,7 @@ public class SolutionSerializer {
79 } 80 }
80 81
81 public Problem serializeSolution(ProblemTrace trace, Model model) { 82 public Problem serializeSolution(ProblemTrace trace, Model model) {
82 var uri = URI.createFileURI("__synthetic" + fileExtension); 83 var uri = URI.createURI("__synthetic." + fileExtension);
83 return serializeSolution(trace, model, uri); 84 return serializeSolution(trace, model, uri);
84 } 85 }
85 86
@@ -133,6 +134,7 @@ public class SolutionSerializer {
133 134
134 private Problem copyProblem(Problem originalProblem, URI uri) { 135 private Problem copyProblem(Problem originalProblem, URI uri) {
135 var newResourceSet = resourceSetProvider.get(); 136 var newResourceSet = resourceSetProvider.get();
137 ImportAdapter.copySettings(originalProblem, newResourceSet);
136 if (!fileExtension.equals(uri.fileExtension())) { 138 if (!fileExtension.equals(uri.fileExtension())) {
137 uri = uri.appendFileExtension(fileExtension); 139 uri = uri.appendFileExtension(fileExtension);
138 } 140 }
@@ -153,6 +155,7 @@ public class SolutionSerializer {
153 throw new IllegalStateException("Failed to copy problem", e); 155 throw new IllegalStateException("Failed to copy problem", e);
154 } 156 }
155 var contents = newResource.getContents(); 157 var contents = newResource.getContents();
158 EcoreUtil.resolveAll(newResourceSet);
156 if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) { 159 if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) {
157 return newProblem; 160 return newProblem;
158 } 161 }
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
index 0fb4cd2c..d88d0299 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java
@@ -9,33 +9,14 @@ import org.eclipse.emf.common.util.URI;
9import org.eclipse.xtext.naming.QualifiedName; 9import org.eclipse.xtext.naming.QualifiedName;
10 10
11import java.util.List; 11import java.util.List;
12import java.util.Optional;
13 12
14public class BuiltinLibrary implements RefineryLibrary { 13public class BuiltinLibrary extends ClasspathBasedLibrary {
15 public static final QualifiedName BUILTIN_LIBRARY_NAME = QualifiedName.create("builtin"); 14 public static final QualifiedName BUILTIN_LIBRARY_NAME = QualifiedName.create("builtin");
16 public static final URI BUILTIN_LIBRARY_URI = getLibraryUri(BUILTIN_LIBRARY_NAME).orElseThrow( 15 public static final URI BUILTIN_LIBRARY_URI = ClasspathBasedLibrary.getLibraryUri(
16 BuiltinLibrary.class, BUILTIN_LIBRARY_NAME).orElseThrow(
17 () -> new IllegalStateException("Builtin library was not found")); 17 () -> new IllegalStateException("Builtin library was not found"));
18 18
19 @Override 19 public BuiltinLibrary() {
20 public List<QualifiedName> getAutomaticImports() { 20 super(BUILTIN_LIBRARY_NAME, List.of(BUILTIN_LIBRARY_NAME));
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 } 21 }
41} 22}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/ClasspathBasedLibrary.java b/subprojects/language/src/main/java/tools/refinery/language/library/ClasspathBasedLibrary.java
new file mode 100644
index 00000000..4b748c64
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/ClasspathBasedLibrary.java
@@ -0,0 +1,98 @@
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.nio.file.Path;
12import java.util.*;
13
14public abstract class ClasspathBasedLibrary implements RefineryLibrary {
15 private final QualifiedName prefix;
16 private final List<QualifiedName> automaticImports;
17 private final URI rootUri;
18
19 protected ClasspathBasedLibrary(QualifiedName prefix, List<QualifiedName> automaticImports) {
20 this.prefix = prefix;
21 this.automaticImports = List.copyOf(automaticImports);
22 var context = this.getClass();
23 var contextPath = context.getCanonicalName().replace('.', '/') + ".class";
24 var contextResource = context.getClassLoader().getResource(contextPath);
25 if (contextResource == null) {
26 throw new IllegalStateException("Failed to find library context");
27 }
28 var contextUri = URI.createURI(contextResource.toString());
29 var segments = Arrays.copyOf(contextUri.segments(), contextUri.segmentCount() - 1);
30 rootUri = URI.createHierarchicalURI(contextUri.scheme(), contextUri.authority(), contextUri.device(),
31 segments, null, null);
32 }
33
34 protected ClasspathBasedLibrary(QualifiedName prefix) {
35 this(prefix, List.of());
36 }
37
38 @Override
39 public List<QualifiedName> getAutomaticImports() {
40 return automaticImports;
41 }
42
43 @Override
44 public Optional<URI> resolveQualifiedName(QualifiedName qualifiedName, List<Path> libraryPaths) {
45 if (qualifiedName.startsWith(prefix)) {
46 return getLibraryUri(this.getClass(), qualifiedName);
47 }
48 return Optional.empty();
49 }
50
51 @Override
52 public Optional<QualifiedName> getQualifiedName(URI uri, List<Path> libraryPaths) {
53 if (!uri.isHierarchical() ||
54 !Objects.equals(rootUri.scheme(), uri.scheme()) ||
55 !Objects.equals(rootUri.authority(), uri.authority()) ||
56 !Objects.equals(rootUri.device(), uri.device()) ||
57 rootUri.segmentCount() >= uri.segmentCount()) {
58 return Optional.empty();
59 }
60 int rootSegmentCount = rootUri.segmentCount();
61 int uriSegmentCount = uri.segmentCount();
62 if (!uri.segment(uriSegmentCount - 1).endsWith(RefineryLibrary.EXTENSION)) {
63 return Optional.empty();
64 }
65 var segments = new ArrayList<String>();
66 int i = 0;
67 while (i < rootSegmentCount) {
68 if (!rootUri.segment(i).equals(uri.segment(i))) {
69 return Optional.empty();
70 }
71 i++;
72 }
73 while (i < uriSegmentCount) {
74 var segment = uri.segment(i);
75 if (i == uriSegmentCount - 1) {
76 segment = segment.substring(0, segment.length() - RefineryLibrary.EXTENSION.length());
77 }
78 segments.add(segment);
79 i++;
80 }
81 var qualifiedName = QualifiedName.create(segments);
82 if (!qualifiedName.startsWith(prefix)) {
83 return Optional.empty();
84 }
85 return Optional.of(qualifiedName);
86 }
87
88 public static Optional<URI> getLibraryUri(Class<?> context, QualifiedName qualifiedName) {
89 var packagePath = context.getPackageName().replace('.', '/');
90 var libraryPath = String.join("/", qualifiedName.getSegments());
91 var resourceName = "%s/%s%s".formatted(packagePath, libraryPath, RefineryLibrary.EXTENSION);
92 var resource = context.getClassLoader().getResource(resourceName);
93 if (resource == null) {
94 return Optional.empty();
95 }
96 return Optional.of(URI.createURI(resource.toString()));
97 }
98}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/PathLibrary.java b/subprojects/language/src/main/java/tools/refinery/language/library/PathLibrary.java
new file mode 100644
index 00000000..c6f994df
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/PathLibrary.java
@@ -0,0 +1,90 @@
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.nio.file.Files;
12import java.nio.file.Path;
13import java.util.ArrayList;
14import java.util.List;
15import java.util.Optional;
16
17public final class PathLibrary implements RefineryLibrary {
18 @Override
19 public Optional<URI> resolveQualifiedName(QualifiedName qualifiedName, List<Path> libraryPaths) {
20 if (libraryPaths.isEmpty()) {
21 return Optional.empty();
22 }
23 if (qualifiedName.getSegmentCount() == 0) {
24 return Optional.empty();
25 }
26 var relativePath = qualifiedNameToRelativePath(qualifiedName);
27 for (var library : libraryPaths) {
28 var absoluteResolvedPath = library.resolve(relativePath).toAbsolutePath().normalize();
29 if (absoluteResolvedPath.startsWith(library) && Files.exists(absoluteResolvedPath)) {
30 var uri = URI.createFileURI(absoluteResolvedPath.toString());
31 return Optional.of(uri);
32 }
33 }
34 return Optional.empty();
35 }
36
37 private static Path qualifiedNameToRelativePath(QualifiedName qualifiedName) {
38 int segmentCount = qualifiedName.getSegmentCount();
39 String first = null;
40 var rest = new String[segmentCount - 1];
41 for (var i = 0; i < segmentCount; i++) {
42 var segment = qualifiedName.getSegment(i);
43 if (i == segmentCount - 1) {
44 segment = segment + RefineryLibrary.EXTENSION;
45 }
46 if (i == 0) {
47 first = segment;
48 } else {
49 rest[i - 1] = segment;
50 }
51 }
52 if (first == null) {
53 throw new AssertionError("Expected qualified name to have non-null segments");
54 }
55 return Path.of(first, rest);
56 }
57
58 @Override
59 public Optional<QualifiedName> getQualifiedName(URI uri, List<Path> libraryPaths) {
60 if (libraryPaths.isEmpty()) {
61 return Optional.empty();
62 }
63 if (!uri.isFile() || !uri.hasAbsolutePath()) {
64 return Optional.empty();
65 }
66 var path = Path.of(uri.toFileString());
67 for (var library : libraryPaths) {
68 if (path.startsWith(library)) {
69 return getRelativeQualifiedName(library, path);
70 }
71 }
72 return Optional.empty();
73 }
74
75 private static Optional<QualifiedName> getRelativeQualifiedName(Path library, Path path) {
76 var relativePath = path.relativize(library);
77 var segments = new ArrayList<String>();
78 for (Path value : relativePath) {
79 segments.add(value.toString());
80 }
81 int lastIndex = segments.size() - 1;
82 var lastSegment = segments.get(lastIndex);
83 if (!lastSegment.endsWith(EXTENSION)) {
84 return Optional.empty();
85 }
86 lastSegment = lastSegment.substring(0, lastSegment.length() - RefineryLibrary.EXTENSION.length());
87 segments.set(lastIndex, lastSegment);
88 return Optional.of(QualifiedName.create(segments));
89 }
90}
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
deleted file mode 100644
index 0efca199..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java
+++ /dev/null
@@ -1,54 +0,0 @@
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
index 9db2900e..e1f8d7bc 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java
@@ -8,13 +8,18 @@ package tools.refinery.language.library;
8import org.eclipse.emf.common.util.URI; 8import org.eclipse.emf.common.util.URI;
9import org.eclipse.xtext.naming.QualifiedName; 9import org.eclipse.xtext.naming.QualifiedName;
10 10
11import java.nio.file.Path;
11import java.util.List; 12import java.util.List;
12import java.util.Optional; 13import java.util.Optional;
13 14
14public interface RefineryLibrary { 15public interface RefineryLibrary {
16 String EXTENSION = ".refinery";
17
15 default List<QualifiedName> getAutomaticImports() { 18 default List<QualifiedName> getAutomaticImports() {
16 return List.of(); 19 return List.of();
17 } 20 }
18 21
19 Optional<URI> resolveQualifiedName(QualifiedName qualifiedName); 22 Optional<URI> resolveQualifiedName(QualifiedName qualifiedName, List<Path> libraryPaths);
23
24 Optional<QualifiedName> getQualifiedName(URI uri, List<Path> libraryPaths);
20} 25}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java
index 1e78cee1..b3931401 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java
@@ -5,14 +5,40 @@
5 */ 5 */
6package tools.refinery.language.naming; 6package tools.refinery.language.naming;
7 7
8import com.google.inject.Inject;
8import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider; 9import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider;
10import org.eclipse.xtext.naming.IQualifiedNameConverter;
9import org.eclipse.xtext.naming.QualifiedName; 11import org.eclipse.xtext.naming.QualifiedName;
10import tools.refinery.language.model.problem.Problem; 12import tools.refinery.language.model.problem.Problem;
13import tools.refinery.language.scoping.imports.ImportAdapter;
14import tools.refinery.language.utils.ProblemUtil;
11 15
12public class ProblemDelegateQualifiedNameProvider extends DefaultDeclarativeQualifiedNameProvider { 16public class ProblemDelegateQualifiedNameProvider extends DefaultDeclarativeQualifiedNameProvider {
13 protected QualifiedName qualifiedName(Problem ele) { 17 @Inject
14 var qualifiedName = computeFullyQualifiedNameFromNameAttribute(ele); 18 private IQualifiedNameConverter qualifiedNameConverter;
15 // Strip the root prefix even if explicitly provided. 19
16 return NamingUtil.stripRootPrefix(qualifiedName); 20 protected QualifiedName qualifiedName(Problem problem) {
21 var qualifiedNameString = problem.getName();
22 if (qualifiedNameString != null) {
23 return NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(qualifiedNameString));
24 }
25 if (!ProblemUtil.isModule(problem)) {
26 return null;
27 }
28 var resource = problem.eResource();
29 if (resource == null) {
30 return null;
31 }
32 var resourceUri = resource.getURI();
33 if (resourceUri == null) {
34 return null;
35 }
36 var resourceSet = resource.getResourceSet();
37 if (resourceSet == null) {
38 return null;
39 }
40 var adapter = ImportAdapter.getOrInstall(resourceSet);
41 // If a module has no explicitly specified name, return the qualified name it was resolved under.
42 return adapter.getQualifiedName(resourceUri);
17 } 43 }
18} 44}
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 a2ec7ed0..7d90ea00 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
@@ -8,14 +8,17 @@ package tools.refinery.language.resource;
8import com.google.common.collect.ImmutableMap; 8import com.google.common.collect.ImmutableMap;
9import com.google.inject.Inject; 9import com.google.inject.Inject;
10import com.google.inject.Singleton; 10import com.google.inject.Singleton;
11import com.google.inject.name.Named;
11import org.eclipse.emf.ecore.EObject; 12import org.eclipse.emf.ecore.EObject;
12import org.eclipse.xtext.EcoreUtil2; 13import org.eclipse.xtext.EcoreUtil2;
13import org.eclipse.xtext.naming.IQualifiedNameConverter; 14import org.eclipse.xtext.naming.IQualifiedNameConverter;
15import org.eclipse.xtext.naming.IQualifiedNameProvider;
14import org.eclipse.xtext.naming.QualifiedName; 16import org.eclipse.xtext.naming.QualifiedName;
15import org.eclipse.xtext.resource.EObjectDescription; 17import org.eclipse.xtext.resource.EObjectDescription;
16import org.eclipse.xtext.resource.IEObjectDescription; 18import org.eclipse.xtext.resource.IEObjectDescription;
17import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; 19import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;
18import org.eclipse.xtext.util.IAcceptor; 20import org.eclipse.xtext.util.IAcceptor;
21import tools.refinery.language.naming.ProblemQualifiedNameProvider;
19import tools.refinery.language.scoping.imports.ImportCollector; 22import tools.refinery.language.scoping.imports.ImportCollector;
20import tools.refinery.language.model.problem.*; 23import tools.refinery.language.model.problem.*;
21import tools.refinery.language.naming.NamingUtil; 24import tools.refinery.language.naming.NamingUtil;
@@ -46,6 +49,10 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
46 private IQualifiedNameConverter qualifiedNameConverter; 49 private IQualifiedNameConverter qualifiedNameConverter;
47 50
48 @Inject 51 @Inject
52 @Named(ProblemQualifiedNameProvider.NAMED_DELEGATE)
53 private IQualifiedNameProvider delegateQualifiedNameProvider;
54
55 @Inject
49 private ImportCollector importCollector; 56 private ImportCollector importCollector;
50 57
51 @Override 58 @Override
@@ -53,17 +60,17 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
53 if (!shouldExport(eObject)) { 60 if (!shouldExport(eObject)) {
54 return false; 61 return false;
55 } 62 }
56 var qualifiedName = getNameAsQualifiedName(eObject);
57 if (qualifiedName == null) {
58 return true;
59 }
60 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); 63 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class);
64 var problemQualifiedName = getProblemQualifiedName(problem);
61 var userData = getUserData(eObject); 65 var userData = getUserData(eObject);
62 if (eObject.equals(problem)) { 66 if (eObject.equals(problem)) {
63 acceptEObjectDescription(eObject, qualifiedName, QualifiedName.EMPTY, userData, true, acceptor); 67 acceptEObjectDescription(eObject, problemQualifiedName, QualifiedName.EMPTY, userData, true, acceptor);
68 return true;
69 }
70 var qualifiedName = getNameAsQualifiedName(eObject);
71 if (qualifiedName == null) {
64 return true; 72 return true;
65 } 73 }
66 var problemQualifiedName = getNameAsQualifiedName(problem);
67 QualifiedName lastQualifiedNameToExport = null; 74 QualifiedName lastQualifiedNameToExport = null;
68 if (shouldExportSimpleName(eObject)) { 75 if (shouldExportSimpleName(eObject)) {
69 lastQualifiedNameToExport = qualifiedName; 76 lastQualifiedNameToExport = qualifiedName;
@@ -107,6 +114,14 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
107 return qualifiedName; 114 return qualifiedName;
108 } 115 }
109 116
117 protected QualifiedName getProblemQualifiedName(Problem problem) {
118 if (problem == null) {
119 return QualifiedName.EMPTY;
120 }
121 var qualifiedName = delegateQualifiedNameProvider.getFullyQualifiedName(problem);
122 return qualifiedName == null ? QualifiedName.EMPTY : qualifiedName;
123 }
124
110 public static boolean shouldExport(EObject eObject) { 125 public static boolean shouldExport(EObject eObject) {
111 if (eObject instanceof Variable) { 126 if (eObject instanceof Variable) {
112 // Variables are always private to the containing predicate definition. 127 // Variables are always private to the containing predicate definition.
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 3e00b87e..610efc03 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,6 +6,7 @@
6package tools.refinery.language.scoping; 6package tools.refinery.language.scoping;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.name.Named;
9import org.eclipse.emf.ecore.EObject; 10import org.eclipse.emf.ecore.EObject;
10import org.eclipse.emf.ecore.EReference; 11import org.eclipse.emf.ecore.EReference;
11import org.eclipse.emf.ecore.resource.Resource; 12import org.eclipse.emf.ecore.resource.Resource;
@@ -18,13 +19,14 @@ import org.eclipse.xtext.scoping.IScope;
18import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeDelegatingScopeProvider; 19import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeDelegatingScopeProvider;
19import org.eclipse.xtext.scoping.impl.SelectableBasedScope; 20import org.eclipse.xtext.scoping.impl.SelectableBasedScope;
20import org.eclipse.xtext.util.IResourceScopeCache; 21import org.eclipse.xtext.util.IResourceScopeCache;
21import tools.refinery.language.naming.NamingUtil; 22import tools.refinery.language.naming.ProblemQualifiedNameProvider;
22 23
23public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScopeProvider { 24public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScopeProvider {
24 private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemLocalScopeProvider.CACHE_KEY"; 25 private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemLocalScopeProvider.CACHE_KEY";
25 26
26 @Inject 27 @Inject
27 private IQualifiedNameProvider qualifiedNameProvider; 28 @Named(ProblemQualifiedNameProvider.NAMED_DELEGATE)
29 private IQualifiedNameProvider delegateQualifiedNameProvider;
28 30
29 @Inject 31 @Inject
30 private IResourceDescriptionsProvider resourceDescriptionsProvider; 32 private IResourceDescriptionsProvider resourceDescriptionsProvider;
@@ -64,7 +66,7 @@ public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScop
64 if (rootElement == null) { 66 if (rootElement == null) {
65 return new LocalImports(resourceDescription, null); 67 return new LocalImports(resourceDescription, null);
66 } 68 }
67 var rootName = NamingUtil.stripRootPrefix(qualifiedNameProvider.getFullyQualifiedName(rootElement)); 69 var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement);
68 if (rootName == null) { 70 if (rootName == null) {
69 return new LocalImports(resourceDescription, null); 71 return new LocalImports(resourceDescription, null);
70 } 72 }
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
new file mode 100644
index 00000000..5a8f7fd7
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
@@ -0,0 +1,230 @@
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.cache.Cache;
10import com.google.common.cache.CacheBuilder;
11import org.apache.log4j.Logger;
12import org.eclipse.emf.common.notify.Notification;
13import org.eclipse.emf.common.notify.impl.AdapterImpl;
14import org.eclipse.emf.common.util.URI;
15import org.eclipse.emf.ecore.EObject;
16import org.eclipse.emf.ecore.resource.Resource;
17import org.eclipse.emf.ecore.resource.ResourceSet;
18import org.eclipse.emf.ecore.util.EcoreUtil;
19import org.eclipse.xtext.naming.QualifiedName;
20import tools.refinery.language.library.RefineryLibrary;
21
22import java.io.File;
23import java.nio.file.Path;
24import java.util.*;
25
26public class ImportAdapter extends AdapterImpl {
27 private static final Logger LOG = Logger.getLogger(ImportAdapter.class);
28 private static final List<RefineryLibrary> DEFAULT_LIBRARIES;
29 private static final List<Path> DEFAULT_PATHS;
30
31 static {
32 var serviceLoader = ServiceLoader.load(RefineryLibrary.class);
33 var defaultLibraries = new ArrayList<RefineryLibrary>();
34 for (var service : serviceLoader) {
35 defaultLibraries.add(service);
36 }
37 DEFAULT_LIBRARIES = List.copyOf(defaultLibraries);
38 var pathEnv = System.getenv("REFINERY_LIBRARY_PATH");
39 if (pathEnv == null) {
40 DEFAULT_PATHS = List.of();
41 } else {
42 DEFAULT_PATHS = Splitter.on(File.pathSeparatorChar)
43 .splitToStream(pathEnv)
44 .map(pathString -> Path.of(pathString).toAbsolutePath().normalize())
45 .toList();
46 }
47 }
48
49 private final List<RefineryLibrary> libraries;
50 private final List<Path> libraryPaths;
51 private final Cache<QualifiedName, QualifiedName> failedResolutions =
52 CacheBuilder.newBuilder().maximumSize(100).build();
53 private final Map<QualifiedName, URI> qualifiedNameToUriMap = new LinkedHashMap<>();
54 private final Map<URI, QualifiedName> uriToQualifiedNameMap = new LinkedHashMap<>();
55
56 private ImportAdapter(ResourceSet resourceSet) {
57 libraries = new ArrayList<>(DEFAULT_LIBRARIES);
58 libraryPaths = new ArrayList<>(DEFAULT_PATHS);
59 for (var resource : resourceSet.getResources()) {
60 resourceAdded(resource);
61 }
62 }
63
64 @Override
65 public boolean isAdapterForType(Object type) {
66 return type == ImportAdapter.class;
67 }
68
69 public List<RefineryLibrary> getLibraries() {
70 return libraries;
71 }
72
73 public List<Path> getLibraryPaths() {
74 return libraryPaths;
75 }
76
77 public URI resolveQualifiedName(QualifiedName qualifiedName) {
78 var uri = getResolvedUri(qualifiedName);
79 if (uri != null) {
80 return uri;
81 }
82 if (isFailed(qualifiedName)) {
83 return null;
84 }
85 for (var library : libraries) {
86 var result = library.resolveQualifiedName(qualifiedName, libraryPaths);
87 if (result.isPresent()) {
88 uri = result.get();
89 markAsResolved(qualifiedName, uri);
90 return uri;
91 }
92 }
93 markAsUnresolved(qualifiedName);
94 return null;
95 }
96
97 private URI getResolvedUri(QualifiedName qualifiedName) {
98 return qualifiedNameToUriMap.get(qualifiedName);
99 }
100
101 private boolean isFailed(QualifiedName qualifiedName) {
102 return failedResolutions.getIfPresent(qualifiedName) != null;
103 }
104
105 private void markAsResolved(QualifiedName qualifiedName, URI uri) {
106 if (qualifiedNameToUriMap.put(qualifiedName, uri) != null) {
107 throw new IllegalArgumentException("Already resolved " + qualifiedName);
108 }
109 // We don't need to signal an error here, because modules with multiple qualified names will lead to
110 // validation errors later.
111 uriToQualifiedNameMap.putIfAbsent(uri, qualifiedName);
112 failedResolutions.invalidate(qualifiedName);
113 }
114
115 private void markAsUnresolved(QualifiedName qualifiedName) {
116 failedResolutions.put(qualifiedName, qualifiedName);
117 }
118
119 public QualifiedName getQualifiedName(URI uri) {
120 return uriToQualifiedNameMap.get(uri);
121 }
122
123 @Override
124 public void notifyChanged(Notification msg) {
125 switch (msg.getEventType()) {
126 case Notification.ADD -> {
127 if (msg.getNewValue() instanceof Resource resource) {
128 resourceAdded(resource);
129 }
130 }
131 case Notification.ADD_MANY -> {
132 if (msg.getNewValue() instanceof List<?> list) {
133 manyResourcesAdded(list);
134 }
135 }
136 case Notification.REMOVE -> {
137 if (msg.getOldValue() instanceof Resource resource) {
138 resourceRemoved(resource);
139 }
140 }
141 case Notification.REMOVE_MANY -> {
142 if (msg.getOldValue() instanceof List<?> list) {
143 manyResourcesRemoved(list);
144 }
145 }
146 default -> {
147 // Nothing to update.
148 }
149 }
150 }
151
152 private void manyResourcesAdded(List<?> list) {
153 for (var element : list) {
154 if (element instanceof Resource resource) {
155 resourceAdded(resource);
156 }
157 }
158 }
159
160 private void manyResourcesRemoved(List<?> list) {
161 for (var element : list) {
162 if (element instanceof Resource resource) {
163 resourceRemoved(resource);
164 }
165 }
166 }
167
168 private void resourceAdded(Resource resource) {
169 var uri = resource.getURI();
170 for (var library : libraries) {
171 var result = library.getQualifiedName(uri, libraryPaths);
172 if (result.isPresent()) {
173 var qualifiedName = result.get();
174 var previousQualifiedName = uriToQualifiedNameMap.putIfAbsent(uri, qualifiedName);
175 if (previousQualifiedName == null) {
176 if (qualifiedNameToUriMap.put(qualifiedName, uri) != null) {
177 throw new IllegalArgumentException("Duplicate resource for" + qualifiedName);
178 }
179 } else if (!previousQualifiedName.equals(qualifiedName)) {
180 LOG.warn("Expected %s to have qualified name %s, got %s instead".formatted(
181 uri, previousQualifiedName, qualifiedName));
182 }
183 }
184 }
185 }
186
187 private void resourceRemoved(Resource resource) {
188 var qualifiedName = uriToQualifiedNameMap.remove(resource.getURI());
189 if (qualifiedName != null) {
190 qualifiedNameToUriMap.remove(qualifiedName);
191 }
192 }
193
194 public static ImportAdapter getOrInstall(ResourceSet resourceSet) {
195 var adapter = getAdapter(resourceSet);
196 if (adapter == null) {
197 adapter = new ImportAdapter(resourceSet);
198 resourceSet.eAdapters().add(adapter);
199 }
200 return adapter;
201 }
202
203 private static ImportAdapter getAdapter(ResourceSet resourceSet) {
204 return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class);
205 }
206
207 public static void copySettings(EObject context, ResourceSet newResourceSet) {
208 var resource = context.eResource();
209 if (resource == null) {
210 return;
211 }
212 var originalResourceSet = resource.getResourceSet();
213 if (originalResourceSet == null) {
214 return;
215 }
216 copySettings(originalResourceSet, newResourceSet);
217 }
218
219 public static void copySettings(ResourceSet originalResourceSet, ResourceSet newResourceSet) {
220 var originalAdapter = getAdapter(originalResourceSet);
221 if (originalAdapter == null) {
222 return;
223 }
224 var newAdapter = getOrInstall(newResourceSet);
225 newAdapter.libraries.clear();
226 newAdapter.libraries.addAll(originalAdapter.libraries);
227 newAdapter.libraryPaths.clear();
228 newAdapter.libraryPaths.addAll(originalAdapter.libraryPaths);
229 }
230}
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
index cea99f0a..6cdfa63e 100644
--- 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
@@ -18,7 +18,6 @@ import org.eclipse.xtext.naming.QualifiedName;
18import org.eclipse.xtext.nodemodel.util.NodeModelUtils; 18import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
19import org.eclipse.xtext.resource.IEObjectDescription; 19import org.eclipse.xtext.resource.IEObjectDescription;
20import org.eclipse.xtext.util.IResourceScopeCache; 20import org.eclipse.xtext.util.IResourceScopeCache;
21import tools.refinery.language.library.RefineryLibraries;
22import tools.refinery.language.model.problem.ImportStatement; 21import tools.refinery.language.model.problem.ImportStatement;
23import tools.refinery.language.model.problem.Problem; 22import tools.refinery.language.model.problem.Problem;
24import tools.refinery.language.model.problem.ProblemPackage; 23import tools.refinery.language.model.problem.ProblemPackage;
@@ -54,28 +53,49 @@ public class ImportCollector {
54 if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) { 53 if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) {
55 return ImportCollection.EMPTY; 54 return ImportCollection.EMPTY;
56 } 55 }
56 var resourceSet = resource.getResourceSet();
57 if (resourceSet == null) {
58 return ImportCollection.EMPTY;
59 }
57 Map<QualifiedName, Set<QualifiedName>> aliasesMap = new LinkedHashMap<>(); 60 Map<QualifiedName, Set<QualifiedName>> aliasesMap = new LinkedHashMap<>();
58 for (var statement : problem.getStatements()) { 61 for (var statement : problem.getStatements()) {
59 if (statement instanceof ImportStatement importStatement) { 62 if (statement instanceof ImportStatement importStatement) {
60 collectImportStatement(importStatement, aliasesMap); 63 collectImportStatement(importStatement, aliasesMap);
61 } 64 }
62 } 65 }
66 var adapter = ImportAdapter.getOrInstall(resourceSet);
63 var collection = new ImportCollection(); 67 var collection = new ImportCollection();
64 collection.addAll(RefineryLibraries.getAutomaticImports()); 68 collectAutomaticImports(collection, adapter);
69 collectExplicitImports(aliasesMap, collection, adapter);
70 collection.remove(resource.getURI());
71 return collection;
72 }
73
74 private void collectAutomaticImports(ImportCollection importCollection, ImportAdapter adapter) {
75 for (var library : adapter.getLibraries()) {
76 for (var qualifiedName : library.getAutomaticImports()) {
77 var uri = adapter.resolveQualifiedName(qualifiedName);
78 if (uri != null) {
79 importCollection.add(NamedImport.implicit(uri, qualifiedName));
80 }
81 }
82 }
83 }
84
85 private void collectExplicitImports(Map<QualifiedName, Set<QualifiedName>> aliasesMap,
86 ImportCollection collection, ImportAdapter adapter) {
65 for (var entry : aliasesMap.entrySet()) { 87 for (var entry : aliasesMap.entrySet()) {
66 var qualifiedName = entry.getKey(); 88 var qualifiedName = entry.getKey();
67 RefineryLibraries.resolveQualifiedName(qualifiedName).ifPresent(uri -> { 89 var uri = adapter.resolveQualifiedName(qualifiedName);
68 if (!uri.equals(resource.getURI())) { 90 if (uri != null) {
69 var aliases = entry.getValue(); 91 var aliases = entry.getValue();
70 collection.add(NamedImport.explicit(uri, qualifiedName, List.copyOf(aliases))); 92 collection.add(NamedImport.explicit(uri, qualifiedName, List.copyOf(aliases)));
71 } 93 }
72 });
73 } 94 }
74 collection.remove(resource.getURI());
75 return collection;
76 } 95 }
77 96
78 private void collectImportStatement(ImportStatement importStatement, Map<QualifiedName, Set<QualifiedName>> aliasesMap) { 97 private void collectImportStatement(ImportStatement importStatement,
98 Map<QualifiedName, Set<QualifiedName>> aliasesMap) {
79 var nodes = NodeModelUtils.findNodesForFeature(importStatement, 99 var nodes = NodeModelUtils.findNodesForFeature(importStatement,
80 ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE); 100 ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE);
81 var aliasString = importStatement.getAlias(); 101 var aliasString = importStatement.getAlias();
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 23ff55e7..6b48cb5a 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
@@ -124,8 +124,12 @@ public final class ProblemUtil {
124 }; 124 };
125 } 125 }
126 126
127 public static boolean isModule(Problem problem) {
128 return problem.getKind() == ModuleKind.MODULE;
129 }
130
127 public static boolean isInModule(EObject eObject) { 131 public static boolean isInModule(EObject eObject) {
128 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); 132 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class);
129 return problem != null && problem.getKind() == ModuleKind.MODULE; 133 return problem != null && isModule(problem);
130 } 134 }
131} 135}
diff --git a/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary b/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary
index bb7e369d..8e454ee5 100644
--- a/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary
+++ b/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary
@@ -2,3 +2,4 @@
2# 2#
3# SPDX-License-Identifier: EPL-2.0 3# SPDX-License-Identifier: EPL-2.0
4tools.refinery.language.library.BuiltinLibrary 4tools.refinery.language.library.BuiltinLibrary
5tools.refinery.language.library.PathLibrary
diff --git a/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery b/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery
index 022c3167..f9ef959d 100644
--- a/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery
+++ b/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery
@@ -1,7 +1,7 @@
1% SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 1% SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
2% 2%
3% SPDX-License-Identifier: EPL-2.0 3% SPDX-License-Identifier: EPL-2.0
4problem builtin. 4module builtin.
5 5
6abstract class node. 6abstract class node.
7 7