diff options
author | Kristóf Marussy <marussy@mit.bme.hu> | 2024-02-19 00:44:05 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-19 00:44:05 +0100 |
commit | 201c3da912aeaab8a3f51b84198b35c7936d0446 (patch) | |
tree | e427986134c41b540b10b9a9576f5a1788d8fbed /subprojects/generator | |
parent | chore(deps): bump dependencies (diff) | |
parent | docs: update README (diff) | |
download | refinery-201c3da912aeaab8a3f51b84198b35c7936d0446.tar.gz refinery-201c3da912aeaab8a3f51b84198b35c7936d0446.tar.zst refinery-201c3da912aeaab8a3f51b84198b35c7936d0446.zip |
Merge pull request #53 from kris7t/imports
Module and import resolution support
Diffstat (limited to 'subprojects/generator')
-rw-r--r-- | subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java | 129 |
1 files changed, 111 insertions, 18 deletions
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..580a87b6 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 | */ |
@@ -9,16 +9,24 @@ import com.google.inject.Inject; | |||
9 | import com.google.inject.Provider; | 9 | import com.google.inject.Provider; |
10 | import org.eclipse.emf.common.util.URI; | 10 | import org.eclipse.emf.common.util.URI; |
11 | import org.eclipse.emf.ecore.resource.Resource; | 11 | import org.eclipse.emf.ecore.resource.Resource; |
12 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
12 | import org.eclipse.xtext.diagnostics.Severity; | 13 | import org.eclipse.xtext.diagnostics.Severity; |
13 | import org.eclipse.xtext.resource.FileExtensionProvider; | 14 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
14 | import org.eclipse.xtext.resource.IResourceFactory; | 15 | import org.eclipse.xtext.resource.*; |
15 | import org.eclipse.xtext.resource.XtextResourceSet; | 16 | import org.eclipse.xtext.scoping.impl.GlobalResourceDescriptionProvider; |
17 | import org.eclipse.xtext.util.CancelIndicator; | ||
16 | import org.eclipse.xtext.util.LazyStringInputStream; | 18 | import org.eclipse.xtext.util.LazyStringInputStream; |
17 | import org.eclipse.xtext.validation.CheckMode; | 19 | import org.eclipse.xtext.validation.CheckMode; |
18 | import org.eclipse.xtext.validation.IResourceValidator; | 20 | import org.eclipse.xtext.validation.IResourceValidator; |
21 | import org.eclipse.xtext.validation.Issue; | ||
19 | import tools.refinery.language.model.problem.Problem; | 22 | import tools.refinery.language.model.problem.Problem; |
20 | import tools.refinery.language.model.problem.Relation; | 23 | import tools.refinery.language.model.problem.Relation; |
21 | import tools.refinery.language.model.problem.ScopeDeclaration; | 24 | import tools.refinery.language.model.problem.ScopeDeclaration; |
25 | import tools.refinery.language.naming.NamingUtil; | ||
26 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
27 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy.ShadowingKey; | ||
28 | import tools.refinery.language.scoping.imports.ImportAdapter; | ||
29 | import tools.refinery.language.scoping.imports.ImportCollector; | ||
22 | import tools.refinery.store.util.CancellationToken; | 30 | import tools.refinery.store.util.CancellationToken; |
23 | 31 | ||
24 | import java.io.ByteArrayOutputStream; | 32 | import java.io.ByteArrayOutputStream; |
@@ -26,11 +34,12 @@ import java.io.File; | |||
26 | import java.io.IOException; | 34 | import java.io.IOException; |
27 | import java.io.InputStream; | 35 | import java.io.InputStream; |
28 | import java.nio.charset.StandardCharsets; | 36 | import java.nio.charset.StandardCharsets; |
29 | import java.util.ArrayList; | 37 | import java.nio.file.Path; |
30 | import java.util.HashSet; | 38 | import java.util.*; |
31 | import java.util.List; | 39 | import java.util.stream.Collectors; |
32 | import java.util.Map; | ||
33 | 40 | ||
41 | // This class is used as a fluent builder. | ||
42 | @SuppressWarnings("UnusedReturnValue") | ||
34 | public class ProblemLoader { | 43 | public class ProblemLoader { |
35 | private String fileExtension; | 44 | private String fileExtension; |
36 | 45 | ||
@@ -43,8 +52,19 @@ public class ProblemLoader { | |||
43 | @Inject | 52 | @Inject |
44 | private IResourceValidator resourceValidator; | 53 | private IResourceValidator resourceValidator; |
45 | 54 | ||
55 | @Inject | ||
56 | private ImportCollector importCollector; | ||
57 | |||
58 | @Inject | ||
59 | private GlobalResourceDescriptionProvider globalResourceDescriptionProvider; | ||
60 | |||
61 | @Inject | ||
62 | private IQualifiedNameConverter qualifiedNameConverter; | ||
63 | |||
46 | private CancellationToken cancellationToken = CancellationToken.NONE; | 64 | private CancellationToken cancellationToken = CancellationToken.NONE; |
47 | 65 | ||
66 | private final List<Path> extraPaths = new ArrayList<>(); | ||
67 | |||
48 | @Inject | 68 | @Inject |
49 | public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) { | 69 | public void setFileExtensionProvider(FileExtensionProvider fileExtensionProvider) { |
50 | this.fileExtension = fileExtensionProvider.getPrimaryFileExtension(); | 70 | this.fileExtension = fileExtensionProvider.getPrimaryFileExtension(); |
@@ -55,6 +75,15 @@ public class ProblemLoader { | |||
55 | return this; | 75 | return this; |
56 | } | 76 | } |
57 | 77 | ||
78 | public ProblemLoader extraPath(String path) { | ||
79 | return extraPath(Path.of(path)); | ||
80 | } | ||
81 | |||
82 | public ProblemLoader extraPath(Path path) { | ||
83 | extraPaths.add(path.toAbsolutePath().normalize()); | ||
84 | return this; | ||
85 | } | ||
86 | |||
58 | public Problem loadString(String problemString, URI uri) throws IOException { | 87 | public Problem loadString(String problemString, URI uri) throws IOException { |
59 | try (var stream = new LazyStringInputStream(problemString)) { | 88 | try (var stream = new LazyStringInputStream(problemString)) { |
60 | return loadStream(stream, uri); | 89 | return loadStream(stream, uri); |
@@ -66,8 +95,8 @@ public class ProblemLoader { | |||
66 | } | 95 | } |
67 | 96 | ||
68 | public Problem loadStream(InputStream inputStream, URI uri) throws IOException { | 97 | public Problem loadStream(InputStream inputStream, URI uri) throws IOException { |
69 | var resourceSet = resourceSetProvider.get(); | 98 | var resourceSet = createResourceSet(); |
70 | var resourceUri = uri == null ? URI.createFileURI("__synthetic." + fileExtension) : uri; | 99 | var resourceUri = uri == null ? URI.createURI("__synthetic." + fileExtension) : uri; |
71 | var resource = resourceFactory.createResource(resourceUri); | 100 | var resource = resourceFactory.createResource(resourceUri); |
72 | resourceSet.getResources().add(resource); | 101 | resourceSet.getResources().add(resource); |
73 | resource.load(inputStream, Map.of()); | 102 | resource.load(inputStream, Map.of()); |
@@ -87,22 +116,45 @@ public class ProblemLoader { | |||
87 | } | 116 | } |
88 | 117 | ||
89 | public Problem loadUri(URI uri) throws IOException { | 118 | public Problem loadUri(URI uri) throws IOException { |
90 | var resourceSet = resourceSetProvider.get(); | 119 | var resourceSet = createResourceSet(); |
91 | var resource = resourceFactory.createResource(uri); | 120 | var resource = resourceFactory.createResource(uri); |
92 | resourceSet.getResources().add(resource); | 121 | resourceSet.getResources().add(resource); |
93 | resource.load(Map.of()); | 122 | resource.load(Map.of()); |
94 | return loadResource(resource); | 123 | return loadResource(resource); |
95 | } | 124 | } |
96 | 125 | ||
126 | private XtextResourceSet createResourceSet() { | ||
127 | var resourceSet = resourceSetProvider.get(); | ||
128 | var adapter = ImportAdapter.getOrInstall(resourceSet); | ||
129 | adapter.getLibraryPaths().addAll(0, extraPaths); | ||
130 | return resourceSet; | ||
131 | } | ||
132 | |||
97 | public Problem loadResource(Resource resource) { | 133 | public Problem loadResource(Resource resource) { |
98 | var issues = resourceValidator.validate(resource, CheckMode.ALL, () -> { | 134 | EcoreUtil.resolveAll(resource); |
135 | CancelIndicator cancelIndicator = () -> { | ||
99 | cancellationToken.checkCancelled(); | 136 | cancellationToken.checkCancelled(); |
100 | return Thread.interrupted(); | 137 | return Thread.interrupted(); |
101 | }); | 138 | }; |
139 | var shadowedNames = new LinkedHashMap<ShadowingKey, Set<IEObjectDescription>>(); | ||
140 | var issues = new ArrayList<Issue>(); | ||
141 | validateResource(resource, issues, cancelIndicator); | ||
102 | cancellationToken.checkCancelled(); | 142 | cancellationToken.checkCancelled(); |
103 | var errors = issues.stream() | 143 | var resourceSet = resource.getResourceSet(); |
104 | .filter(issue -> issue.getSeverity() == Severity.ERROR) | 144 | if (resourceSet != null) { |
105 | .toList(); | 145 | var imports = importCollector.getAllImports(resource).toUriSet(); |
146 | cancellationToken.checkCancelled(); | ||
147 | for (var importedUri : imports) { | ||
148 | var importedResource = resourceSet.getResource(importedUri, false); | ||
149 | if (importedResource == null) { | ||
150 | throw new IllegalStateException("Unknown imported resource: " + importedUri); | ||
151 | } | ||
152 | findShadowedNames(importedResource, shadowedNames); | ||
153 | validateResource(importedResource, issues, cancelIndicator); | ||
154 | } | ||
155 | } | ||
156 | addNameClashIssues(issues, shadowedNames); | ||
157 | var errors = issues.stream().filter(issue -> issue.getSeverity() == Severity.ERROR).toList(); | ||
106 | if (!errors.isEmpty()) { | 158 | if (!errors.isEmpty()) { |
107 | throw new ValidationErrorsException(resource.getURI(), errors); | 159 | throw new ValidationErrorsException(resource.getURI(), errors); |
108 | } | 160 | } |
@@ -112,8 +164,49 @@ public class ProblemLoader { | |||
112 | return problem; | 164 | return problem; |
113 | } | 165 | } |
114 | 166 | ||
115 | public Problem loadScopeConstraints(Problem problem, List<String> extraScopes, | 167 | private void findShadowedNames(Resource importedResource, |
116 | List<String> overrideScopes) throws IOException { | 168 | LinkedHashMap<ShadowingKey, Set<IEObjectDescription>> shadowedNames) { |
169 | var resourceDescription = globalResourceDescriptionProvider.getResourceDescription(importedResource); | ||
170 | for (var eObjectDescription : resourceDescription.getExportedObjects()) { | ||
171 | var name = eObjectDescription.getName(); | ||
172 | if (NamingUtil.isFullyQualified(name)) { | ||
173 | var shadowingKey = ProblemResourceDescriptionStrategy.getShadowingKey(eObjectDescription); | ||
174 | var entries = shadowedNames.computeIfAbsent(shadowingKey, ignored -> new LinkedHashSet<>()); | ||
175 | entries.add(eObjectDescription); | ||
176 | } | ||
177 | } | ||
178 | cancellationToken.checkCancelled(); | ||
179 | } | ||
180 | |||
181 | private void validateResource(Resource importedResource, ArrayList<Issue> issues, | ||
182 | CancelIndicator cancelIndicator) { | ||
183 | issues.addAll(resourceValidator.validate(importedResource, CheckMode.ALL, cancelIndicator)); | ||
184 | cancellationToken.checkCancelled(); | ||
185 | } | ||
186 | |||
187 | private void addNameClashIssues(ArrayList<Issue> issues, | ||
188 | LinkedHashMap<ShadowingKey, Set<IEObjectDescription>> shadowedNames) { | ||
189 | for (var entry : shadowedNames.entrySet()) { | ||
190 | var eObjectDescriptions = entry.getValue(); | ||
191 | if (eObjectDescriptions.size() <= 1) { | ||
192 | continue; | ||
193 | } | ||
194 | var qualifiedName = qualifiedNameConverter.toString(NamingUtil.stripRootPrefix(entry.getKey().name())); | ||
195 | var uris = eObjectDescriptions.stream() | ||
196 | .map(eObjectDescription -> eObjectDescription.getEObjectURI().trimFragment().toString()) | ||
197 | .collect(Collectors.joining(", ")); | ||
198 | var message = "Object with qualified name %s is also defined in %s".formatted(qualifiedName, uris); | ||
199 | for (var eObjectDescription : eObjectDescriptions) { | ||
200 | var issue = new Issue.IssueImpl(); | ||
201 | issue.setSeverity(Severity.ERROR); | ||
202 | issue.setMessage(message); | ||
203 | issue.setUriToProblem(eObjectDescription.getEObjectURI()); | ||
204 | issues.add(issue); | ||
205 | } | ||
206 | } | ||
207 | } | ||
208 | |||
209 | public Problem loadScopeConstraints(Problem problem, List<String> extraScopes, List<String> overrideScopes) throws IOException { | ||
117 | var allScopes = new ArrayList<>(extraScopes); | 210 | var allScopes = new ArrayList<>(extraScopes); |
118 | allScopes.addAll(overrideScopes); | 211 | allScopes.addAll(overrideScopes); |
119 | if (allScopes.isEmpty()) { | 212 | if (allScopes.isEmpty()) { |