aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 19:41:32 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 20:29:02 +0200
commita3f1e6872f4f768d14899a1e70bbdc14f32e478d (patch)
treeb2daf0c81724f31ee190f5d63eb42988086dabf2
parentfix: nullary model initialization (diff)
downloadrefinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.gz
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.zst
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.zip
feat: improve semantics error reporting
Also makes model seeds cancellable to reduce server load during semantic analysis.
-rw-r--r--subprojects/frontend/src/editor/AnalysisErrorNotification.tsx74
-rw-r--r--subprojects/frontend/src/editor/AnimatedButton.tsx9
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx6
-rw-r--r--subprojects/frontend/src/editor/EditorErrors.tsx93
-rw-r--r--subprojects/frontend/src/editor/EditorPane.tsx2
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts39
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts4
-rw-r--r--subprojects/frontend/src/editor/GenerateButton.tsx48
-rw-r--r--subprojects/frontend/src/xtext/SemanticsService.ts28
-rw-r--r--subprojects/frontend/src/xtext/UpdateService.ts2
-rw-r--r--subprojects/frontend/src/xtext/ValidationService.ts44
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts7
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts7
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java121
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java51
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java99
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java (renamed from subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java)2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java3
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java43
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java44
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java3
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java9
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java88
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java112
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java7
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java7
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java3
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java4
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java7
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java10
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java13
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java3
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java3
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java35
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java7
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java3
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java31
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java7
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java9
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java7
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java3
70 files changed, 1036 insertions, 300 deletions
diff --git a/subprojects/frontend/src/editor/AnalysisErrorNotification.tsx b/subprojects/frontend/src/editor/AnalysisErrorNotification.tsx
new file mode 100644
index 00000000..591a3600
--- /dev/null
+++ b/subprojects/frontend/src/editor/AnalysisErrorNotification.tsx
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { reaction } from 'mobx';
8import { type SnackbarKey, useSnackbar } from 'notistack';
9import { useEffect, useState } from 'react';
10
11import type EditorStore from './EditorStore';
12
13function MessageObserver({
14 editorStore,
15}: {
16 editorStore: EditorStore;
17}): React.ReactNode {
18 const [message, setMessage] = useState(
19 editorStore.delayedErrors.semanticsError ?? '',
20 );
21 // Instead of making this component an `observer`,
22 // we only update the message is one is present to make sure that the
23 // disappear animation has a chance to complete.
24 useEffect(
25 () =>
26 reaction(
27 () => editorStore.delayedErrors.semanticsError,
28 (newMessage) => {
29 if (newMessage !== undefined) {
30 setMessage(newMessage);
31 }
32 },
33 { fireImmediately: false },
34 ),
35 [editorStore],
36 );
37 return message;
38}
39
40export default function AnalysisErrorNotification({
41 editorStore,
42}: {
43 editorStore: EditorStore;
44}): null {
45 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
46 useEffect(() => {
47 let key: SnackbarKey | undefined;
48 const disposer = reaction(
49 () => editorStore.delayedErrors.semanticsError !== undefined,
50 (hasError) => {
51 if (hasError) {
52 if (key === undefined) {
53 key = enqueueSnackbar({
54 message: <MessageObserver editorStore={editorStore} />,
55 variant: 'error',
56 persist: true,
57 });
58 }
59 } else if (key !== undefined) {
60 closeSnackbar(key);
61 key = undefined;
62 }
63 },
64 { fireImmediately: true },
65 );
66 return () => {
67 disposer();
68 if (key !== undefined) {
69 closeSnackbar(key);
70 }
71 };
72 }, [editorStore, enqueueSnackbar, closeSnackbar]);
73 return null;
74}
diff --git a/subprojects/frontend/src/editor/AnimatedButton.tsx b/subprojects/frontend/src/editor/AnimatedButton.tsx
index dbbda618..24ec69be 100644
--- a/subprojects/frontend/src/editor/AnimatedButton.tsx
+++ b/subprojects/frontend/src/editor/AnimatedButton.tsx
@@ -48,7 +48,7 @@ export default function AnimatedButton({
48 onClick?: () => void; 48 onClick?: () => void;
49 color: 'error' | 'warning' | 'primary' | 'inherit'; 49 color: 'error' | 'warning' | 'primary' | 'inherit';
50 disabled?: boolean; 50 disabled?: boolean;
51 startIcon: JSX.Element; 51 startIcon?: JSX.Element;
52 sx?: SxProps<Theme> | undefined; 52 sx?: SxProps<Theme> | undefined;
53 children?: ReactNode; 53 children?: ReactNode;
54}): JSX.Element { 54}): JSX.Element {
@@ -79,7 +79,11 @@ export default function AnimatedButton({
79 className="rounded shaded" 79 className="rounded shaded"
80 disabled={disabled ?? false} 80 disabled={disabled ?? false}
81 startIcon={startIcon} 81 startIcon={startIcon}
82 width={width === undefined ? 'auto' : `calc(${width} + 50px)`} 82 width={
83 width === undefined
84 ? 'auto'
85 : `calc(${width} + ${startIcon === undefined ? 28 : 50}px)`
86 }
83 > 87 >
84 <Box 88 <Box
85 display="flex" 89 display="flex"
@@ -100,6 +104,7 @@ AnimatedButton.defaultProps = {
100 'aria-label': undefined, 104 'aria-label': undefined,
101 onClick: undefined, 105 onClick: undefined,
102 disabled: false, 106 disabled: false,
107 startIcon: undefined,
103 sx: undefined, 108 sx: undefined,
104 children: undefined, 109 children: undefined,
105}; 110};
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx
index 9b187e5c..ca51f975 100644
--- a/subprojects/frontend/src/editor/EditorButtons.tsx
+++ b/subprojects/frontend/src/editor/EditorButtons.tsx
@@ -5,8 +5,8 @@
5 */ 5 */
6 6
7import type { Diagnostic } from '@codemirror/lint'; 7import type { Diagnostic } from '@codemirror/lint';
8import CancelIcon from '@mui/icons-material/Cancel';
8import CheckIcon from '@mui/icons-material/Check'; 9import CheckIcon from '@mui/icons-material/Check';
9import ErrorIcon from '@mui/icons-material/Error';
10import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; 10import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
11import FormatPaint from '@mui/icons-material/FormatPaint'; 11import FormatPaint from '@mui/icons-material/FormatPaint';
12import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; 12import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
@@ -28,7 +28,7 @@ import type EditorStore from './EditorStore';
28function getLintIcon(severity: Diagnostic['severity'] | undefined) { 28function getLintIcon(severity: Diagnostic['severity'] | undefined) {
29 switch (severity) { 29 switch (severity) {
30 case 'error': 30 case 'error':
31 return <ErrorIcon fontSize="small" />; 31 return <CancelIcon fontSize="small" />;
32 case 'warning': 32 case 'warning':
33 return <WarningIcon fontSize="small" />; 33 return <WarningIcon fontSize="small" />;
34 case 'info': 34 case 'info':
@@ -95,7 +95,7 @@ export default observer(function EditorButtons({
95 })} 95 })}
96 value="show-lint-panel" 96 value="show-lint-panel"
97 > 97 >
98 {getLintIcon(editorStore?.highestDiagnosticLevel)} 98 {getLintIcon(editorStore?.delayedErrors?.highestDiagnosticLevel)}
99 </ToggleButton> 99 </ToggleButton>
100 </ToggleButtonGroup> 100 </ToggleButtonGroup>
101 <IconButton 101 <IconButton
diff --git a/subprojects/frontend/src/editor/EditorErrors.tsx b/subprojects/frontend/src/editor/EditorErrors.tsx
new file mode 100644
index 00000000..40becf7e
--- /dev/null
+++ b/subprojects/frontend/src/editor/EditorErrors.tsx
@@ -0,0 +1,93 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { Diagnostic } from '@codemirror/lint';
8import { type IReactionDisposer, makeAutoObservable, reaction } from 'mobx';
9
10import type EditorStore from './EditorStore';
11
12const HYSTERESIS_TIME_MS = 250;
13
14export interface State {
15 analyzing: boolean;
16 errorCount: number;
17 warningCount: number;
18 infoCount: number;
19 semanticsError: string | undefined;
20}
21
22export default class EditorErrors implements State {
23 private readonly disposer: IReactionDisposer;
24
25 private timer: number | undefined;
26
27 analyzing = false;
28
29 errorCount = 0;
30
31 warningCount = 0;
32
33 infoCount = 0;
34
35 semanticsError: string | undefined;
36
37 constructor(private readonly store: EditorStore) {
38 this.updateImmediately(this.getNextState());
39 makeAutoObservable<EditorErrors, 'disposer' | 'timer'>(this, {
40 disposer: false,
41 timer: false,
42 });
43 this.disposer = reaction(
44 () => this.getNextState(),
45 (nextState) => {
46 if (this.timer !== undefined) {
47 clearTimeout(this.timer);
48 this.timer = undefined;
49 }
50 if (nextState.analyzing) {
51 this.timer = setTimeout(
52 () => this.updateImmediately(nextState),
53 HYSTERESIS_TIME_MS,
54 );
55 } else {
56 this.updateImmediately(nextState);
57 }
58 },
59 { fireImmediately: true },
60 );
61 }
62
63 get highestDiagnosticLevel(): Diagnostic['severity'] | undefined {
64 if (this.errorCount > 0) {
65 return 'error';
66 }
67 if (this.warningCount > 0) {
68 return 'warning';
69 }
70 if (this.infoCount > 0) {
71 return 'info';
72 }
73 return undefined;
74 }
75
76 private getNextState(): State {
77 return {
78 analyzing: this.store.analyzing,
79 errorCount: this.store.errorCount,
80 warningCount: this.store.warningCount,
81 infoCount: this.store.infoCount,
82 semanticsError: this.store.semanticsError,
83 };
84 }
85
86 private updateImmediately(nextState: State) {
87 Object.assign(this, nextState);
88 }
89
90 dispose() {
91 this.disposer();
92 }
93}
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx
index c9f86496..1125a0ec 100644
--- a/subprojects/frontend/src/editor/EditorPane.tsx
+++ b/subprojects/frontend/src/editor/EditorPane.tsx
@@ -13,6 +13,7 @@ import { useState } from 'react';
13 13
14import { useRootStore } from '../RootStoreProvider'; 14import { useRootStore } from '../RootStoreProvider';
15 15
16import AnalysisErrorNotification from './AnalysisErrorNotification';
16import ConnectionStatusNotification from './ConnectionStatusNotification'; 17import ConnectionStatusNotification from './ConnectionStatusNotification';
17import EditorArea from './EditorArea'; 18import EditorArea from './EditorArea';
18import EditorButtons from './EditorButtons'; 19import EditorButtons from './EditorButtons';
@@ -48,6 +49,7 @@ export default observer(function EditorPane(): JSX.Element {
48 <EditorLoading /> 49 <EditorLoading />
49 ) : ( 50 ) : (
50 <> 51 <>
52 <AnalysisErrorNotification editorStore={editorStore} />
51 <ConnectionStatusNotification editorStore={editorStore} /> 53 <ConnectionStatusNotification editorStore={editorStore} />
52 <SearchPanelPortal editorStore={editorStore} /> 54 <SearchPanelPortal editorStore={editorStore} />
53 <EditorArea editorStore={editorStore} /> 55 <EditorArea editorStore={editorStore} />
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index c79f6ec1..563725bb 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -29,6 +29,7 @@ import type PWAStore from '../PWAStore';
29import getLogger from '../utils/getLogger'; 29import getLogger from '../utils/getLogger';
30import type XtextClient from '../xtext/XtextClient'; 30import type XtextClient from '../xtext/XtextClient';
31 31
32import EditorErrors from './EditorErrors';
32import LintPanelStore from './LintPanelStore'; 33import LintPanelStore from './LintPanelStore';
33import SearchPanelStore from './SearchPanelStore'; 34import SearchPanelStore from './SearchPanelStore';
34import createEditorState from './createEditorState'; 35import createEditorState from './createEditorState';
@@ -54,15 +55,22 @@ export default class EditorStore {
54 55
55 readonly lintPanel: LintPanelStore; 56 readonly lintPanel: LintPanelStore;
56 57
58 readonly delayedErrors: EditorErrors;
59
57 showLineNumbers = false; 60 showLineNumbers = false;
58 61
59 disposed = false; 62 disposed = false;
60 63
64 analyzing = false;
65
66 semanticsError: string | undefined;
67
61 semantics: unknown = {}; 68 semantics: unknown = {};
62 69
63 constructor(initialValue: string, pwaStore: PWAStore) { 70 constructor(initialValue: string, pwaStore: PWAStore) {
64 this.id = nanoid(); 71 this.id = nanoid();
65 this.state = createEditorState(initialValue, this); 72 this.state = createEditorState(initialValue, this);
73 this.delayedErrors = new EditorErrors(this);
66 this.searchPanel = new SearchPanelStore(this); 74 this.searchPanel = new SearchPanelStore(this);
67 this.lintPanel = new LintPanelStore(this); 75 this.lintPanel = new LintPanelStore(this);
68 (async () => { 76 (async () => {
@@ -82,6 +90,7 @@ export default class EditorStore {
82 state: observable.ref, 90 state: observable.ref,
83 client: observable.ref, 91 client: observable.ref,
84 view: observable.ref, 92 view: observable.ref,
93 semantics: observable.ref,
85 searchPanel: false, 94 searchPanel: false,
86 lintPanel: false, 95 lintPanel: false,
87 contentAssist: false, 96 contentAssist: false,
@@ -215,19 +224,6 @@ export default class EditorStore {
215 this.doCommand(nextDiagnostic); 224 this.doCommand(nextDiagnostic);
216 } 225 }
217 226
218 get highestDiagnosticLevel(): Diagnostic['severity'] | undefined {
219 if (this.errorCount > 0) {
220 return 'error';
221 }
222 if (this.warningCount > 0) {
223 return 'warning';
224 }
225 if (this.infoCount > 0) {
226 return 'info';
227 }
228 return undefined;
229 }
230
231 updateSemanticHighlighting(ranges: IHighlightRange[]): void { 227 updateSemanticHighlighting(ranges: IHighlightRange[]): void {
232 this.dispatch(setSemanticHighlighting(ranges)); 228 this.dispatch(setSemanticHighlighting(ranges));
233 } 229 }
@@ -284,12 +280,29 @@ export default class EditorStore {
284 return true; 280 return true;
285 } 281 }
286 282
283 analysisStarted() {
284 this.analyzing = true;
285 }
286
287 analysisCompleted(semanticAnalysisSkipped = false) {
288 this.analyzing = false;
289 if (semanticAnalysisSkipped) {
290 this.semanticsError = undefined;
291 }
292 }
293
294 setSemanticsError(semanticsError: string) {
295 this.semanticsError = semanticsError;
296 }
297
287 setSemantics(semantics: unknown) { 298 setSemantics(semantics: unknown) {
299 this.semanticsError = undefined;
288 this.semantics = semantics; 300 this.semantics = semantics;
289 } 301 }
290 302
291 dispose(): void { 303 dispose(): void {
292 this.client?.dispose(); 304 this.client?.dispose();
305 this.delayedErrors.dispose();
293 this.disposed = true; 306 this.disposed = true;
294 } 307 }
295} 308}
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts
index 4afb93e6..dd551a52 100644
--- a/subprojects/frontend/src/editor/EditorTheme.ts
+++ b/subprojects/frontend/src/editor/EditorTheme.ts
@@ -4,7 +4,7 @@
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; 7import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw';
8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; 8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw';
9import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; 9import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw';
10import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; 10import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw';
@@ -331,7 +331,7 @@ export default styled('div', {
331 '.cm-lintRange-active': { 331 '.cm-lintRange-active': {
332 background: theme.palette.highlight.activeLintRange, 332 background: theme.palette.highlight.activeLintRange,
333 }, 333 },
334 ...lintSeverityStyle('error', errorSVG, 120), 334 ...lintSeverityStyle('error', cancelSVG, 120),
335 ...lintSeverityStyle('warning', warningSVG, 110), 335 ...lintSeverityStyle('warning', warningSVG, 110),
336 ...lintSeverityStyle('info', infoSVG, 100), 336 ...lintSeverityStyle('info', infoSVG, 100),
337 }; 337 };
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx
index 3837ef8e..5bac0464 100644
--- a/subprojects/frontend/src/editor/GenerateButton.tsx
+++ b/subprojects/frontend/src/editor/GenerateButton.tsx
@@ -4,10 +4,8 @@
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; 7import CancelIcon from '@mui/icons-material/Cancel';
8import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 8import PlayArrowIcon from '@mui/icons-material/PlayArrow';
9import Button from '@mui/material/Button';
10import type { SxProps, Theme } from '@mui/material/styles';
11import { observer } from 'mobx-react-lite'; 9import { observer } from 'mobx-react-lite';
12 10
13import AnimatedButton from './AnimatedButton'; 11import AnimatedButton from './AnimatedButton';
@@ -18,26 +16,45 @@ const GENERATE_LABEL = 'Generate';
18const GenerateButton = observer(function GenerateButton({ 16const GenerateButton = observer(function GenerateButton({
19 editorStore, 17 editorStore,
20 hideWarnings, 18 hideWarnings,
21 sx,
22}: { 19}: {
23 editorStore: EditorStore | undefined; 20 editorStore: EditorStore | undefined;
24 hideWarnings?: boolean | undefined; 21 hideWarnings?: boolean | undefined;
25 sx?: SxProps<Theme> | undefined;
26}): JSX.Element { 22}): JSX.Element {
27 if (editorStore === undefined) { 23 if (editorStore === undefined) {
28 return ( 24 return (
29 <Button 25 <AnimatedButton color="inherit" disabled>
30 color="inherit"
31 className="rounded shaded"
32 disabled
33 {...(sx === undefined ? {} : { sx })}
34 >
35 Loading&hellip; 26 Loading&hellip;
36 </Button> 27 </AnimatedButton>
28 );
29 }
30
31 const { analyzing, errorCount, warningCount, semanticsError } =
32 editorStore.delayedErrors;
33
34 if (analyzing) {
35 return (
36 <AnimatedButton color="inherit" disabled>
37 Analyzing&hellip;
38 </AnimatedButton>
37 ); 39 );
38 } 40 }
39 41
40 const { errorCount, warningCount } = editorStore; 42 if (semanticsError !== undefined && editorStore.opened) {
43 return (
44 <AnimatedButton
45 color="error"
46 disabled
47 startIcon={<CancelIcon />}
48 sx={(theme) => ({
49 '&.Mui-disabled': {
50 color: `${theme.palette.error.main} !important`,
51 },
52 })}
53 >
54 Analysis error
55 </AnimatedButton>
56 );
57 }
41 58
42 const diagnostics: string[] = []; 59 const diagnostics: string[] = [];
43 if (errorCount > 0) { 60 if (errorCount > 0) {
@@ -54,8 +71,7 @@ const GenerateButton = observer(function GenerateButton({
54 aria-label={`Select next diagnostic out of ${summary}`} 71 aria-label={`Select next diagnostic out of ${summary}`}
55 onClick={() => editorStore.nextDiagnostic()} 72 onClick={() => editorStore.nextDiagnostic()}
56 color="error" 73 color="error"
57 startIcon={<DangerousOutlinedIcon />} 74 startIcon={<CancelIcon />}
58 {...(sx === undefined ? {} : { sx })}
59 > 75 >
60 {summary} 76 {summary}
61 </AnimatedButton> 77 </AnimatedButton>
@@ -67,7 +83,6 @@ const GenerateButton = observer(function GenerateButton({
67 disabled={!editorStore.opened} 83 disabled={!editorStore.opened}
68 color={warningCount > 0 ? 'warning' : 'primary'} 84 color={warningCount > 0 ? 'warning' : 'primary'}
69 startIcon={<PlayArrowIcon />} 85 startIcon={<PlayArrowIcon />}
70 {...(sx === undefined ? {} : { sx })}
71 > 86 >
72 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`} 87 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`}
73 </AnimatedButton> 88 </AnimatedButton>
@@ -76,7 +91,6 @@ const GenerateButton = observer(function GenerateButton({
76 91
77GenerateButton.defaultProps = { 92GenerateButton.defaultProps = {
78 hideWarnings: false, 93 hideWarnings: false,
79 sx: undefined,
80}; 94};
81 95
82export default GenerateButton; 96export default GenerateButton;
diff --git a/subprojects/frontend/src/xtext/SemanticsService.ts b/subprojects/frontend/src/xtext/SemanticsService.ts
new file mode 100644
index 00000000..50ec371a
--- /dev/null
+++ b/subprojects/frontend/src/xtext/SemanticsService.ts
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import type EditorStore from '../editor/EditorStore';
8
9import type ValidationService from './ValidationService';
10import { SemanticsResult } from './xtextServiceResults';
11
12export default class SemanticsService {
13 constructor(
14 private readonly store: EditorStore,
15 private readonly validationService: ValidationService,
16 ) {}
17
18 onPush(push: unknown): void {
19 const result = SemanticsResult.parse(push);
20 this.validationService.setSemanticsIssues(result.issues ?? []);
21 if (result.error !== undefined) {
22 this.store.setSemanticsError(result.error);
23 } else {
24 this.store.setSemantics(push);
25 }
26 this.store.analysisCompleted();
27 }
28}
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts
index ee5ebde2..1ac722e1 100644
--- a/subprojects/frontend/src/xtext/UpdateService.ts
+++ b/subprojects/frontend/src/xtext/UpdateService.ts
@@ -133,6 +133,7 @@ export default class UpdateService {
133 return; 133 return;
134 } 134 }
135 log.trace('Editor delta', delta); 135 log.trace('Editor delta', delta);
136 this.store.analysisStarted();
136 const result = await this.webSocketClient.send({ 137 const result = await this.webSocketClient.send({
137 resource: this.resourceName, 138 resource: this.resourceName,
138 serviceType: 'update', 139 serviceType: 'update',
@@ -157,6 +158,7 @@ export default class UpdateService {
157 private async updateFullTextExclusive(): Promise<void> { 158 private async updateFullTextExclusive(): Promise<void> {
158 log.debug('Performing full text update'); 159 log.debug('Performing full text update');
159 this.tracker.prepareFullTextUpdateExclusive(); 160 this.tracker.prepareFullTextUpdateExclusive();
161 this.store.analysisStarted();
160 const result = await this.webSocketClient.send({ 162 const result = await this.webSocketClient.send({
161 resource: this.resourceName, 163 resource: this.resourceName,
162 serviceType: 'update', 164 serviceType: 'update',
diff --git a/subprojects/frontend/src/xtext/ValidationService.ts b/subprojects/frontend/src/xtext/ValidationService.ts
index 64fb63eb..1a896db3 100644
--- a/subprojects/frontend/src/xtext/ValidationService.ts
+++ b/subprojects/frontend/src/xtext/ValidationService.ts
@@ -9,7 +9,7 @@ import type { Diagnostic } from '@codemirror/lint';
9import type EditorStore from '../editor/EditorStore'; 9import type EditorStore from '../editor/EditorStore';
10 10
11import type UpdateService from './UpdateService'; 11import type UpdateService from './UpdateService';
12import { ValidationResult } from './xtextServiceResults'; 12import { Issue, ValidationResult } from './xtextServiceResults';
13 13
14export default class ValidationService { 14export default class ValidationService {
15 constructor( 15 constructor(
@@ -17,11 +17,41 @@ export default class ValidationService {
17 private readonly updateService: UpdateService, 17 private readonly updateService: UpdateService,
18 ) {} 18 ) {}
19 19
20 private lastValidationIssues: Issue[] = [];
21
22 private lastSemanticsIssues: Issue[] = [];
23
20 onPush(push: unknown): void { 24 onPush(push: unknown): void {
21 const { issues } = ValidationResult.parse(push); 25 ({ issues: this.lastValidationIssues } = ValidationResult.parse(push));
26 this.lastSemanticsIssues = [];
27 this.updateDiagnostics();
28 if (
29 this.lastValidationIssues.some(({ severity }) => severity === 'error')
30 ) {
31 this.store.analysisCompleted(true);
32 }
33 }
34
35 onDisconnect(): void {
36 this.store.updateDiagnostics([]);
37 this.lastValidationIssues = [];
38 this.lastSemanticsIssues = [];
39 }
40
41 setSemanticsIssues(issues: Issue[]): void {
42 this.lastSemanticsIssues = issues;
43 this.updateDiagnostics();
44 }
45
46 private updateDiagnostics(): void {
22 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 47 const allChanges = this.updateService.computeChangesSinceLastUpdate();
23 const diagnostics: Diagnostic[] = []; 48 const diagnostics: Diagnostic[] = [];
24 issues.forEach(({ offset, length, severity, description }) => { 49 function createDiagnostic({
50 offset,
51 length,
52 severity,
53 description,
54 }: Issue): void {
25 if (severity === 'ignore') { 55 if (severity === 'ignore') {
26 return; 56 return;
27 } 57 }
@@ -31,11 +61,9 @@ export default class ValidationService {
31 severity, 61 severity,
32 message: description, 62 message: description,
33 }); 63 });
34 }); 64 }
65 this.lastValidationIssues.forEach(createDiagnostic);
66 this.lastSemanticsIssues.forEach(createDiagnostic);
35 this.store.updateDiagnostics(diagnostics); 67 this.store.updateDiagnostics(diagnostics);
36 } 68 }
37
38 onDisconnect(): void {
39 this.store.updateDiagnostics([]);
40 }
41} 69}
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts
index d145cd30..87778084 100644
--- a/subprojects/frontend/src/xtext/XtextClient.ts
+++ b/subprojects/frontend/src/xtext/XtextClient.ts
@@ -17,6 +17,7 @@ import getLogger from '../utils/getLogger';
17import ContentAssistService from './ContentAssistService'; 17import ContentAssistService from './ContentAssistService';
18import HighlightingService from './HighlightingService'; 18import HighlightingService from './HighlightingService';
19import OccurrencesService from './OccurrencesService'; 19import OccurrencesService from './OccurrencesService';
20import SemanticsService from './SemanticsService';
20import UpdateService from './UpdateService'; 21import UpdateService from './UpdateService';
21import ValidationService from './ValidationService'; 22import ValidationService from './ValidationService';
22import XtextWebSocketClient from './XtextWebSocketClient'; 23import XtextWebSocketClient from './XtextWebSocketClient';
@@ -37,6 +38,8 @@ export default class XtextClient {
37 38
38 private readonly occurrencesService: OccurrencesService; 39 private readonly occurrencesService: OccurrencesService;
39 40
41 private readonly semanticsService: SemanticsService;
42
40 constructor( 43 constructor(
41 private readonly store: EditorStore, 44 private readonly store: EditorStore,
42 private readonly pwaStore: PWAStore, 45 private readonly pwaStore: PWAStore,
@@ -54,6 +57,7 @@ export default class XtextClient {
54 ); 57 );
55 this.validationService = new ValidationService(store, this.updateService); 58 this.validationService = new ValidationService(store, this.updateService);
56 this.occurrencesService = new OccurrencesService(store, this.updateService); 59 this.occurrencesService = new OccurrencesService(store, this.updateService);
60 this.semanticsService = new SemanticsService(store, this.validationService);
57 } 61 }
58 62
59 start(): void { 63 start(): void {
@@ -67,6 +71,7 @@ export default class XtextClient {
67 } 71 }
68 72
69 private onDisconnect(): void { 73 private onDisconnect(): void {
74 this.store.analysisCompleted(true);
70 this.highlightingService.onDisconnect(); 75 this.highlightingService.onDisconnect();
71 this.validationService.onDisconnect(); 76 this.validationService.onDisconnect();
72 this.occurrencesService.onDisconnect(); 77 this.occurrencesService.onDisconnect();
@@ -115,7 +120,7 @@ export default class XtextClient {
115 this.validationService.onPush(push); 120 this.validationService.onPush(push);
116 return; 121 return;
117 case 'semantics': 122 case 'semantics':
118 this.store.setSemantics(push); 123 this.semanticsService.onPush(push);
119 return; 124 return;
120 default: 125 default:
121 throw new Error('Unknown service'); 126 throw new Error('Unknown service');
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts
index d3b467ad..cae95771 100644
--- a/subprojects/frontend/src/xtext/xtextServiceResults.ts
+++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts
@@ -125,3 +125,10 @@ export const FormattingResult = DocumentStateResult.extend({
125}); 125});
126 126
127export type FormattingResult = z.infer<typeof FormattingResult>; 127export type FormattingResult = z.infer<typeof FormattingResult>;
128
129export const SemanticsResult = z.object({
130 error: z.string().optional(),
131 issues: Issue.array().optional(),
132});
133
134export type SemanticsResult = z.infer<typeof SemanticsResult>;
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
index 5f854ac3..5ed65e04 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
@@ -15,12 +15,14 @@ import tools.refinery.language.utils.ProblemDesugarer;
15import tools.refinery.language.utils.ProblemUtil; 15import tools.refinery.language.utils.ProblemUtil;
16import tools.refinery.store.model.ModelStoreBuilder; 16import tools.refinery.store.model.ModelStoreBuilder;
17import tools.refinery.store.query.Constraint; 17import tools.refinery.store.query.Constraint;
18import tools.refinery.store.query.dnf.InvalidClauseException;
18import tools.refinery.store.query.dnf.Query; 19import tools.refinery.store.query.dnf.Query;
19import tools.refinery.store.query.dnf.RelationalQuery; 20import tools.refinery.store.query.dnf.RelationalQuery;
20import tools.refinery.store.query.literal.*; 21import tools.refinery.store.query.literal.*;
21import tools.refinery.store.query.term.NodeVariable; 22import tools.refinery.store.query.term.NodeVariable;
22import tools.refinery.store.query.term.Variable; 23import tools.refinery.store.query.term.Variable;
23import tools.refinery.store.reasoning.ReasoningAdapter; 24import tools.refinery.store.reasoning.ReasoningAdapter;
25import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
24import tools.refinery.store.reasoning.representation.PartialRelation; 26import tools.refinery.store.reasoning.representation.PartialRelation;
25import tools.refinery.store.reasoning.seed.ModelSeed; 27import tools.refinery.store.reasoning.seed.ModelSeed;
26import tools.refinery.store.reasoning.seed.Seed; 28import tools.refinery.store.reasoning.seed.Seed;
@@ -60,7 +62,9 @@ public class ModelInitializer {
60 62
61 private final Map<Relation, RelationInfo> relationInfoMap = new LinkedHashMap<>(); 63 private final Map<Relation, RelationInfo> relationInfoMap = new LinkedHashMap<>();
62 64
63 private final Map<PartialRelation, RelationInfo> partialRelationInfoMap = new LinkedHashMap<>(); 65 private final Map<PartialRelation, RelationInfo> partialRelationInfoMap = new HashMap<>();
66
67 private Map<AnyPartialSymbol, Relation> inverseTrace = new HashMap<>();
64 68
65 private Map<Relation, PartialRelation> relationTrace; 69 private Map<Relation, PartialRelation> relationTrace;
66 70
@@ -82,6 +86,10 @@ public class ModelInitializer {
82 return relationTrace; 86 return relationTrace;
83 } 87 }
84 88
89 public Relation getInverseTrace(AnyPartialSymbol partialRelation) {
90 return inverseTrace.get(partialRelation);
91 }
92
85 public ModelSeed createModel(Problem problem, ModelStoreBuilder storeBuilder) { 93 public ModelSeed createModel(Problem problem, ModelStoreBuilder storeBuilder) {
86 this.problem = problem; 94 this.problem = problem;
87 this.storeBuilder = storeBuilder; 95 this.storeBuilder = storeBuilder;
@@ -172,7 +180,7 @@ public class ModelInitializer {
172 collectPartialRelation(invalidMultiplicityConstraint, 1, TruthValue.FALSE, TruthValue.FALSE); 180 collectPartialRelation(invalidMultiplicityConstraint, 1, TruthValue.FALSE, TruthValue.FALSE);
173 } 181 }
174 } else { 182 } else {
175 throw new IllegalArgumentException("Unknown feature declaration: " + featureDeclaration); 183 throw new TracedException(featureDeclaration, "Unknown feature declaration");
176 } 184 }
177 } 185 }
178 } 186 }
@@ -189,6 +197,7 @@ public class ModelInitializer {
189 private void putRelationInfo(Relation relation, RelationInfo info) { 197 private void putRelationInfo(Relation relation, RelationInfo info) {
190 relationInfoMap.put(relation, info); 198 relationInfoMap.put(relation, info);
191 partialRelationInfoMap.put(info.partialRelation(), info); 199 partialRelationInfoMap.put(info.partialRelation(), info);
200 inverseTrace.put(info.partialRelation(), relation);
192 } 201 }
193 202
194 private RelationInfo collectPartialRelation(Relation relation, int arity, TruthValue value, 203 private RelationInfo collectPartialRelation(Relation relation, int arity, TruthValue value,
@@ -197,6 +206,7 @@ public class ModelInitializer {
197 var name = getName(relation); 206 var name = getName(relation);
198 var info = new RelationInfo(name, arity, value, defaultValue); 207 var info = new RelationInfo(name, arity, value, defaultValue);
199 partialRelationInfoMap.put(info.partialRelation(), info); 208 partialRelationInfoMap.put(info.partialRelation(), info);
209 inverseTrace.put(info.partialRelation(), relation);
200 return info; 210 return info;
201 }); 211 });
202 } 212 }
@@ -216,7 +226,11 @@ public class ModelInitializer {
216 } 226 }
217 227
218 private void collectEnumMetamodel(EnumDeclaration enumDeclaration) { 228 private void collectEnumMetamodel(EnumDeclaration enumDeclaration) {
219 metamodelBuilder.type(getPartialRelation(enumDeclaration), nodeRelation); 229 try {
230 metamodelBuilder.type(getPartialRelation(enumDeclaration), nodeRelation);
231 } catch (RuntimeException e) {
232 throw TracedException.addTrace(enumDeclaration, e);
233 }
220 } 234 }
221 235
222 private void collectClassDeclarationMetamodel(ClassDeclaration classDeclaration) { 236 private void collectClassDeclarationMetamodel(ClassDeclaration classDeclaration) {
@@ -226,13 +240,15 @@ public class ModelInitializer {
226 for (var superType : superTypes) { 240 for (var superType : superTypes) {
227 partialSuperTypes.add(getPartialRelation(superType)); 241 partialSuperTypes.add(getPartialRelation(superType));
228 } 242 }
229 metamodelBuilder.type(getPartialRelation(classDeclaration), classDeclaration.isAbstract(), 243 try {
230 partialSuperTypes); 244 metamodelBuilder.type(getPartialRelation(classDeclaration), classDeclaration.isAbstract(),
245 partialSuperTypes);
246 } catch (RuntimeException e) {
247 throw TracedException.addTrace(classDeclaration, e);
248 }
231 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) { 249 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
232 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) { 250 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
233 collectReferenceDeclarationMetamodel(classDeclaration, referenceDeclaration); 251 collectReferenceDeclarationMetamodel(classDeclaration, referenceDeclaration);
234 } else {
235 throw new IllegalArgumentException("Unknown feature declaration: " + featureDeclaration);
236 } 252 }
237 } 253 }
238 } 254 }
@@ -249,7 +265,11 @@ public class ModelInitializer {
249 oppositeRelation = getPartialRelation(opposite); 265 oppositeRelation = getPartialRelation(opposite);
250 } 266 }
251 var multiplicity = getMultiplicityConstraint(referenceDeclaration); 267 var multiplicity = getMultiplicityConstraint(referenceDeclaration);
252 metamodelBuilder.reference(relation, source, containment, multiplicity, target, oppositeRelation); 268 try {
269 metamodelBuilder.reference(relation, source, containment, multiplicity, target, oppositeRelation);
270 } catch (RuntimeException e) {
271 throw TracedException.addTrace(classDeclaration, e);
272 }
253 } 273 }
254 274
255 private Multiplicity getMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) { 275 private Multiplicity getMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) {
@@ -267,7 +287,7 @@ public class ModelInitializer {
267 interval = CardinalityIntervals.between(rangeMultiplicity.getLowerBound(), 287 interval = CardinalityIntervals.between(rangeMultiplicity.getLowerBound(),
268 upperBound < 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(upperBound)); 288 upperBound < 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(upperBound));
269 } else { 289 } else {
270 throw new IllegalArgumentException("Unknown multiplicity: " + problemMultiplicity); 290 throw new TracedException(problemMultiplicity, "Unknown multiplicity");
271 } 291 }
272 var constraint = getRelationInfo(referenceDeclaration.getInvalidMultiplicity()).partialRelation(); 292 var constraint = getRelationInfo(referenceDeclaration.getInvalidMultiplicity()).partialRelation();
273 return ConstrainedMultiplicity.of(interval, constraint); 293 return ConstrainedMultiplicity.of(interval, constraint);
@@ -327,13 +347,19 @@ public class ModelInitializer {
327 } 347 }
328 348
329 private void collectAssertion(Assertion assertion) { 349 private void collectAssertion(Assertion assertion) {
330 var relation = assertion.getRelation();
331 var tuple = getTuple(assertion); 350 var tuple = getTuple(assertion);
332 var value = getTruthValue(assertion.getValue()); 351 var value = getTruthValue(assertion.getValue());
352 var relation = assertion.getRelation();
353 var info = getRelationInfo(relation);
354 var partialRelation = info.partialRelation();
355 if (partialRelation.arity() != tuple.getSize()) {
356 throw new TracedException(assertion, "Expected %d arguments for %s, got %d instead"
357 .formatted(partialRelation.arity(), partialRelation, tuple.getSize()));
358 }
333 if (assertion.isDefault()) { 359 if (assertion.isDefault()) {
334 mergeDefaultValue(relation, tuple, value); 360 info.defaultAssertions().mergeValue(tuple, value);
335 } else { 361 } else {
336 mergeValue(relation, tuple, value); 362 info.assertions().mergeValue(tuple, value);
337 } 363 }
338 } 364 }
339 365
@@ -341,10 +367,6 @@ public class ModelInitializer {
341 getRelationInfo(relation).assertions().mergeValue(key, value); 367 getRelationInfo(relation).assertions().mergeValue(key, value);
342 } 368 }
343 369
344 private void mergeDefaultValue(Relation relation, Tuple key, TruthValue value) {
345 getRelationInfo(relation).defaultAssertions().mergeValue(key, value);
346 }
347
348 private RelationInfo getRelationInfo(Relation relation) { 370 private RelationInfo getRelationInfo(Relation relation) {
349 var info = relationInfoMap.get(relation); 371 var info = relationInfoMap.get(relation);
350 if (info == null) { 372 if (info == null) {
@@ -372,7 +394,7 @@ public class ModelInitializer {
372 } else if (argument instanceof WildcardAssertionArgument) { 394 } else if (argument instanceof WildcardAssertionArgument) {
373 nodes[i] = -1; 395 nodes[i] = -1;
374 } else { 396 } else {
375 throw new IllegalArgumentException("Unknown assertion argument: " + argument); 397 throw new TracedException(argument, "Unsupported assertion argument");
376 } 398 }
377 } 399 }
378 return Tuple.of(nodes); 400 return Tuple.of(nodes);
@@ -393,8 +415,24 @@ public class ModelInitializer {
393 private void collectPredicates() { 415 private void collectPredicates() {
394 for (var statement : problem.getStatements()) { 416 for (var statement : problem.getStatements()) {
395 if (statement instanceof PredicateDefinition predicateDefinition) { 417 if (statement instanceof PredicateDefinition predicateDefinition) {
396 collectPredicateDefinition(predicateDefinition); 418 collectPredicateDefinitionTraced(predicateDefinition);
419 }
420 }
421 }
422
423 private void collectPredicateDefinitionTraced(PredicateDefinition predicateDefinition) {
424 try {
425 collectPredicateDefinition(predicateDefinition);
426 } catch (InvalidClauseException e) {
427 int clauseIndex = e.getClauseIndex();
428 var bodies = predicateDefinition.getBodies();
429 if (clauseIndex < bodies.size()) {
430 throw new TracedException(bodies.get(clauseIndex), e);
431 } else {
432 throw new TracedException(predicateDefinition, e);
397 } 433 }
434 } catch (RuntimeException e) {
435 throw TracedException.addTrace(predicateDefinition, e);
398 } 436 }
399 } 437 }
400 438
@@ -436,13 +474,17 @@ public class ModelInitializer {
436 } 474 }
437 var builder = Query.builder(name).parameters(parameters); 475 var builder = Query.builder(name).parameters(parameters);
438 for (var body : predicateDefinition.getBodies()) { 476 for (var body : predicateDefinition.getBodies()) {
439 var localScope = extendScope(parameterMap, body.getImplicitVariables()); 477 try {
440 var problemLiterals = body.getLiterals(); 478 var localScope = extendScope(parameterMap, body.getImplicitVariables());
441 var literals = new ArrayList<Literal>(commonLiterals); 479 var problemLiterals = body.getLiterals();
442 for (var problemLiteral : problemLiterals) { 480 var literals = new ArrayList<>(commonLiterals);
443 toLiterals(problemLiteral, localScope, literals); 481 for (var problemLiteral : problemLiterals) {
482 toLiteralsTraced(problemLiteral, localScope, literals);
483 }
484 builder.clause(literals);
485 } catch (RuntimeException e) {
486 throw TracedException.addTrace(body, e);
444 } 487 }
445 builder.clause(literals);
446 } 488 }
447 return builder.build(); 489 return builder.build();
448 } 490 }
@@ -462,13 +504,23 @@ public class ModelInitializer {
462 return localScope; 504 return localScope;
463 } 505 }
464 506
465 private void toLiterals(Expr expr, Map<tools.refinery.language.model.problem.Variable, Variable> localScope, 507 private void toLiteralsTraced(Expr expr, Map<tools.refinery.language.model.problem.Variable, Variable> localScope,
508 List<Literal> literals) {
509 try {
510 toLiterals(expr, localScope, literals);
511 } catch (RuntimeException e) {
512 throw TracedException.addTrace(expr, e);
513 }
514 }
515
516 private void toLiterals(Expr expr, Map<tools.refinery.language.model.problem.Variable,
517 Variable> localScope,
466 List<Literal> literals) { 518 List<Literal> literals) {
467 if (expr instanceof LogicConstant logicConstant) { 519 if (expr instanceof LogicConstant logicConstant) {
468 switch (logicConstant.getLogicValue()) { 520 switch (logicConstant.getLogicValue()) {
469 case TRUE -> literals.add(BooleanLiteral.TRUE); 521 case TRUE -> literals.add(BooleanLiteral.TRUE);
470 case FALSE -> literals.add(BooleanLiteral.FALSE); 522 case FALSE -> literals.add(BooleanLiteral.FALSE);
471 default -> throw new IllegalArgumentException("Unsupported literal: " + expr); 523 default -> throw new TracedException(logicConstant, "Unsupported literal");
472 } 524 }
473 } else if (expr instanceof Atom atom) { 525 } else if (expr instanceof Atom atom) {
474 var target = getPartialRelation(atom.getRelation()); 526 var target = getPartialRelation(atom.getRelation());
@@ -478,7 +530,7 @@ public class ModelInitializer {
478 } else if (expr instanceof NegationExpr negationExpr) { 530 } else if (expr instanceof NegationExpr negationExpr) {
479 var body = negationExpr.getBody(); 531 var body = negationExpr.getBody();
480 if (!(body instanceof Atom atom)) { 532 if (!(body instanceof Atom atom)) {
481 throw new IllegalArgumentException("Cannot negate literal: " + body); 533 throw new TracedException(body, "Cannot negate literal");
482 } 534 }
483 var target = getPartialRelation(atom.getRelation()); 535 var target = getPartialRelation(atom.getRelation());
484 Constraint constraint; 536 Constraint constraint;
@@ -498,11 +550,12 @@ public class ModelInitializer {
498 boolean positive = switch (comparisonExpr.getOp()) { 550 boolean positive = switch (comparisonExpr.getOp()) {
499 case EQ -> true; 551 case EQ -> true;
500 case NOT_EQ -> false; 552 case NOT_EQ -> false;
501 default -> throw new IllegalArgumentException("Unsupported operator: " + comparisonExpr.getOp()); 553 default -> throw new TracedException(
554 comparisonExpr, "Unsupported operator");
502 }; 555 };
503 literals.add(new EquivalenceLiteral(positive, argumentList.get(0), argumentList.get(1))); 556 literals.add(new EquivalenceLiteral(positive, argumentList.get(0), argumentList.get(1)));
504 } else { 557 } else {
505 throw new IllegalArgumentException("Unsupported literal: " + expr); 558 throw new TracedException(expr, "Unsupported literal");
506 } 559 }
507 } 560 }
508 561
@@ -512,7 +565,7 @@ public class ModelInitializer {
512 var argumentList = new ArrayList<Variable>(expressions.size()); 565 var argumentList = new ArrayList<Variable>(expressions.size());
513 for (var expr : expressions) { 566 for (var expr : expressions) {
514 if (!(expr instanceof VariableOrNodeExpr variableOrNodeExpr)) { 567 if (!(expr instanceof VariableOrNodeExpr variableOrNodeExpr)) {
515 throw new IllegalArgumentException("Unsupported argument: " + expr); 568 throw new TracedException(expr, "Unsupported argument");
516 } 569 }
517 var variableOrNode = variableOrNodeExpr.getVariableOrNode(); 570 var variableOrNode = variableOrNodeExpr.getVariableOrNode();
518 if (variableOrNode instanceof Node node) { 571 if (variableOrNode instanceof Node node) {
@@ -526,10 +579,12 @@ public class ModelInitializer {
526 } else { 579 } else {
527 var variable = localScope.get(problemVariable); 580 var variable = localScope.get(problemVariable);
528 if (variable == null) { 581 if (variable == null) {
529 throw new IllegalArgumentException("Unknown variable: " + problemVariable.getName()); 582 throw new TracedException(variableOrNode, "Unknown variable: " + problemVariable.getName());
530 } 583 }
531 argumentList.add(variable); 584 argumentList.add(variable);
532 } 585 }
586 } else {
587 throw new TracedException(variableOrNode, "Unknown argument");
533 } 588 }
534 } 589 }
535 return argumentList; 590 return argumentList;
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java
new file mode 100644
index 00000000..38fd8a67
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.model;
7
8import org.eclipse.emf.ecore.EObject;
9
10public class TracedException extends RuntimeException {
11 private final transient EObject sourceElement;
12
13 public TracedException(EObject sourceElement) {
14 this.sourceElement = sourceElement;
15 }
16
17 public TracedException(EObject sourceElement, String message) {
18 super(message);
19 this.sourceElement = sourceElement;
20 }
21
22 public TracedException(EObject sourceElement, String message, Throwable cause) {
23 super(message, cause);
24 this.sourceElement = sourceElement;
25 }
26
27 public TracedException(EObject sourceElement, Throwable cause) {
28 super(cause);
29 this.sourceElement = sourceElement;
30 }
31
32 public EObject getSourceElement() {
33 return sourceElement;
34 }
35
36 @Override
37 public String getMessage() {
38 var message = super.getMessage();
39 if (message == null) {
40 return "Internal error";
41 }
42 return message;
43 }
44
45 public static TracedException addTrace(EObject sourceElement, Throwable cause) {
46 if (cause instanceof TracedException tracedException && tracedException.sourceElement != null) {
47 return tracedException;
48 }
49 return new TracedException(sourceElement, cause);
50 }
51}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
new file mode 100644
index 00000000..aa14f39d
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12import tools.refinery.store.reasoning.seed.Seed;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.viatra.runtime.CancellationToken;
15
16import java.util.Set;
17
18class CancellableSeed<T> implements Seed<T> {
19 private final CancellationToken cancellationToken;
20 private final Seed<T> seed;
21
22 private CancellableSeed(CancellationToken cancellationToken, Seed<T> seed) {
23 this.cancellationToken = cancellationToken;
24 this.seed = seed;
25 }
26
27 @Override
28 public int arity() {
29 return seed.arity();
30 }
31
32 @Override
33 public Class<T> valueType() {
34 return seed.valueType();
35 }
36
37 @Override
38 public T reducedValue() {
39 return seed.reducedValue();
40 }
41
42 @Override
43 public T get(Tuple key) {
44 return seed.get(key);
45 }
46
47 @Override
48 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
49 return new CancellableCursor<>(cancellationToken, seed.getCursor(defaultValue, nodeCount));
50 }
51
52 public static ModelSeed wrap(CancellationToken cancellationToken, ModelSeed modelSeed) {
53 var builder = ModelSeed.builder(modelSeed.getNodeCount());
54 for (var partialSymbol : modelSeed.getSeededSymbols()) {
55 wrap(cancellationToken, (PartialSymbol<?, ?>) partialSymbol, modelSeed, builder);
56 }
57 return builder.build();
58 }
59
60 private static <A, C> void wrap(CancellationToken cancellationToken, PartialSymbol<A, C> partialSymbol,
61 ModelSeed originalModelSeed, ModelSeed.Builder builder) {
62 var originalSeed = originalModelSeed.getSeed(partialSymbol);
63 builder.seed(partialSymbol, new CancellableSeed<>(cancellationToken, originalSeed));
64 }
65
66 private record CancellableCursor<T>(CancellationToken cancellationToken, Cursor<Tuple, T> cursor)
67 implements Cursor<Tuple, T> {
68 @Override
69 public Tuple getKey() {
70 return cursor.getKey();
71 }
72
73 @Override
74 public T getValue() {
75 return cursor.getValue();
76 }
77
78 @Override
79 public boolean isTerminated() {
80 return cursor.isTerminated();
81 }
82
83 @Override
84 public boolean move() {
85 cancellationToken.checkCancelled();
86 return cursor.move();
87 }
88
89 @Override
90 public boolean isDirty() {
91 return cursor.isDirty();
92 }
93
94 @Override
95 public Set<AnyVersionedMap> getDependingMaps() {
96 return cursor.getDependingMaps();
97 }
98 }
99}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
index ce34ef6c..ff592e93 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
@@ -5,5 +5,5 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8public record SemanticsErrorResult(String error) implements SemanticsResult { 8public record SemanticsInternalErrorResult(String error) implements SemanticsResult {
9} 9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
new file mode 100644
index 00000000..644bd179
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import org.eclipse.xtext.web.server.validation.ValidationResult;
9
10import java.util.List;
11
12public record SemanticsIssuesResult(List<ValidationResult.Issue> issues) implements SemanticsResult {
13}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
index 92639578..a2e19a2f 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
@@ -7,5 +7,6 @@ package tools.refinery.language.web.semantics;
7 7
8import org.eclipse.xtext.web.server.IServiceResult; 8import org.eclipse.xtext.web.server.IServiceResult;
9 9
10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult, SemanticsErrorResult { 10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult,
11 SemanticsInternalErrorResult, SemanticsIssuesResult {
11} 12}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
index 39191162..56b2cbc1 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -5,9 +5,11 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8import com.google.gson.JsonObject;
8import com.google.inject.Inject; 9import com.google.inject.Inject;
9import com.google.inject.Provider; 10import com.google.inject.Provider;
10import com.google.inject.Singleton; 11import com.google.inject.Singleton;
12import org.eclipse.xtext.ide.ExecutorServiceProvider;
11import org.eclipse.xtext.service.OperationCanceledManager; 13import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator; 14import org.eclipse.xtext.util.CancelIndicator;
13import org.eclipse.xtext.web.server.model.AbstractCachedService; 15import org.eclipse.xtext.web.server.model.AbstractCachedService;
@@ -19,6 +21,7 @@ import org.slf4j.LoggerFactory;
19import tools.refinery.language.model.problem.Problem; 21import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.web.xtext.server.push.PushWebDocument; 22import tools.refinery.language.web.xtext.server.push.PushWebDocument;
21 23
24import java.util.List;
22import java.util.concurrent.*; 25import java.util.concurrent.*;
23 26
24@Singleton 27@Singleton
@@ -34,7 +37,12 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
34 @Inject 37 @Inject
35 private ValidationService validationService; 38 private ValidationService validationService;
36 39
37 private final ExecutorService executorService = Executors.newCachedThreadPool(); 40 private ExecutorService executorService;
41
42 @Inject
43 public void setExecutorServiceProvider(ExecutorServiceProvider provider) {
44 executorService = provider.get(this.getClass().getName());
45 }
38 46
39 @Override 47 @Override
40 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) { 48 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
@@ -42,12 +50,15 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
42 if (LOG.isTraceEnabled()) { 50 if (LOG.isTraceEnabled()) {
43 start = System.currentTimeMillis(); 51 start = System.currentTimeMillis();
44 } 52 }
45 var problem = getProblem(doc, cancelIndicator); 53 if (hasError(doc, cancelIndicator)) {
46 if (problem == null) {
47 return null; 54 return null;
48 } 55 }
56 var problem = getProblem(doc);
57 if (problem == null) {
58 return new SemanticsSuccessResult(List.of(), new JsonObject());
59 }
49 var worker = workerProvider.get(); 60 var worker = workerProvider.get();
50 worker.setProblem(problem,cancelIndicator); 61 worker.setProblem(problem, cancelIndicator);
51 var future = executorService.submit(worker); 62 var future = executorService.submit(worker);
52 SemanticsResult result = null; 63 SemanticsResult result = null;
53 try { 64 try {
@@ -58,11 +69,19 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
58 Thread.currentThread().interrupt(); 69 Thread.currentThread().interrupt();
59 } catch (ExecutionException e) { 70 } catch (ExecutionException e) {
60 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause()); 71 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause());
61 throw new IllegalStateException(e); 72 LOG.debug("Error while computing semantics", e);
73 if (e.getCause() instanceof Error error) {
74 throw error;
75 }
76 String message = e.getMessage();
77 if (message == null) {
78 message = "Partial interpretation error";
79 }
80 return new SemanticsInternalErrorResult(message);
62 } catch (TimeoutException e) { 81 } catch (TimeoutException e) {
63 future.cancel(true); 82 future.cancel(true);
64 LOG.trace("Semantics service timeout", e); 83 LOG.trace("Semantics service timeout", e);
65 return new SemanticsErrorResult("Partial interpretation timed out"); 84 return new SemanticsInternalErrorResult("Partial interpretation timed out");
66 } 85 }
67 if (LOG.isTraceEnabled()) { 86 if (LOG.isTraceEnabled()) {
68 long end = System.currentTimeMillis(); 87 long end = System.currentTimeMillis();
@@ -72,17 +91,17 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
72 return result; 91 return result;
73 } 92 }
74 93
75 @Nullable 94 private boolean hasError(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
76 private Problem getProblem(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
77 if (!(doc instanceof PushWebDocument pushDoc)) { 95 if (!(doc instanceof PushWebDocument pushDoc)) {
78 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc); 96 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc);
79 } 97 }
80 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true); 98 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true);
81 boolean hasError = validationResult.getIssues().stream() 99 return validationResult.getIssues().stream()
82 .anyMatch(issue -> "error".equals(issue.getSeverity())); 100 .anyMatch(issue -> "error".equals(issue.getSeverity()));
83 if (hasError) { 101 }
84 return null; 102
85 } 103 @Nullable
104 private Problem getProblem(IXtextWebDocument doc) {
86 var contents = doc.getResource().getContents(); 105 var contents = doc.getResource().getContents();
87 if (contents.isEmpty()) { 106 if (contents.isEmpty()) {
88 return null; 107 return null;
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
index 25589260..43d0238c 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -8,13 +8,19 @@ package tools.refinery.language.web.semantics;
8import com.google.gson.JsonArray; 8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject; 9import com.google.gson.JsonObject;
10import com.google.inject.Inject; 10import com.google.inject.Inject;
11import org.eclipse.emf.common.util.Diagnostic;
12import org.eclipse.emf.ecore.EObject;
11import org.eclipse.xtext.service.OperationCanceledManager; 13import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator; 14import org.eclipse.xtext.util.CancelIndicator;
13import org.slf4j.Logger; 15import org.eclipse.xtext.validation.CheckType;
14import org.slf4j.LoggerFactory; 16import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
17import org.eclipse.xtext.validation.IDiagnosticConverter;
18import org.eclipse.xtext.validation.Issue;
19import org.eclipse.xtext.web.server.validation.ValidationResult;
15import tools.refinery.language.model.problem.Problem; 20import tools.refinery.language.model.problem.Problem;
16import tools.refinery.language.semantics.model.ModelInitializer; 21import tools.refinery.language.semantics.model.ModelInitializer;
17import tools.refinery.language.semantics.model.SemanticsUtils; 22import tools.refinery.language.semantics.model.SemanticsUtils;
23import tools.refinery.language.semantics.model.TracedException;
18import tools.refinery.store.model.Model; 24import tools.refinery.store.model.Model;
19import tools.refinery.store.model.ModelStore; 25import tools.refinery.store.model.ModelStore;
20import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 26import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
@@ -22,17 +28,19 @@ import tools.refinery.store.reasoning.ReasoningAdapter;
22import tools.refinery.store.reasoning.ReasoningStoreAdapter; 28import tools.refinery.store.reasoning.ReasoningStoreAdapter;
23import tools.refinery.store.reasoning.literal.Concreteness; 29import tools.refinery.store.reasoning.literal.Concreteness;
24import tools.refinery.store.reasoning.representation.PartialRelation; 30import tools.refinery.store.reasoning.representation.PartialRelation;
31import tools.refinery.store.reasoning.translator.TranslationException;
25import tools.refinery.store.representation.TruthValue; 32import tools.refinery.store.representation.TruthValue;
26import tools.refinery.store.tuple.Tuple; 33import tools.refinery.store.tuple.Tuple;
27import tools.refinery.viatra.runtime.CancellationToken; 34import tools.refinery.viatra.runtime.CancellationToken;
28 35
36import java.util.ArrayList;
29import java.util.Arrays; 37import java.util.Arrays;
30import java.util.List; 38import java.util.List;
31import java.util.TreeMap; 39import java.util.TreeMap;
32import java.util.concurrent.Callable; 40import java.util.concurrent.Callable;
33 41
34class SemanticsWorker implements Callable<SemanticsResult> { 42class SemanticsWorker implements Callable<SemanticsResult> {
35 private static final Logger LOG = LoggerFactory.getLogger(SemanticsWorker.class); 43 private static final String DIAGNOSTIC_ID = "tools.refinery.language.semantics.SemanticError";
36 44
37 @Inject 45 @Inject
38 private SemanticsUtils semanticsUtils; 46 private SemanticsUtils semanticsUtils;
@@ -41,6 +49,9 @@ class SemanticsWorker implements Callable<SemanticsResult> {
41 private OperationCanceledManager operationCanceledManager; 49 private OperationCanceledManager operationCanceledManager;
42 50
43 @Inject 51 @Inject
52 private IDiagnosticConverter diagnosticConverter;
53
54 @Inject
44 private ModelInitializer initializer; 55 private ModelInitializer initializer;
45 56
46 private Problem problem; 57 private Problem problem;
@@ -71,15 +82,17 @@ class SemanticsWorker implements Callable<SemanticsResult> {
71 cancellationToken.checkCancelled(); 82 cancellationToken.checkCancelled();
72 var store = builder.build(); 83 var store = builder.build();
73 cancellationToken.checkCancelled(); 84 cancellationToken.checkCancelled();
74 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); 85 var cancellableModelSeed = CancellableSeed.wrap(cancellationToken, modelSeed);
86 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(cancellableModelSeed);
75 cancellationToken.checkCancelled(); 87 cancellationToken.checkCancelled();
76 var partialInterpretation = getPartialInterpretation(initializer, model); 88 var partialInterpretation = getPartialInterpretation(initializer, model);
77 89
78 return new SemanticsSuccessResult(nodeTrace, partialInterpretation); 90 return new SemanticsSuccessResult(nodeTrace, partialInterpretation);
79 } catch (RuntimeException e) { 91 } catch (TracedException e) {
80 LOG.debug("Error while computing semantics", e); 92 return getTracedErrorResult(e.getSourceElement(), e.getMessage());
81 var message = e.getMessage(); 93 } catch (TranslationException e) {
82 return new SemanticsErrorResult(message == null ? "Partial interpretation error" : e.getMessage()); 94 var sourceElement = initializer.getInverseTrace(e.getPartialSymbol());
95 return getTracedErrorResult(sourceElement, e.getMessage());
83 } 96 }
84 } 97 }
85 98
@@ -130,4 +143,19 @@ class SemanticsWorker implements Callable<SemanticsResult> {
130 json.add(value.toString()); 143 json.add(value.toString());
131 return json; 144 return json;
132 } 145 }
146
147 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) {
148 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) {
149 return new SemanticsInternalErrorResult(message);
150 }
151 var diagnostic = new FeatureBasedDiagnostic(Diagnostic.ERROR, message, sourceElement, null, 0,
152 CheckType.EXPENSIVE, DIAGNOSTIC_ID);
153 var xtextIssues = new ArrayList<Issue>();
154 diagnosticConverter.convertValidatorDiagnostic(diagnostic, xtextIssues::add);
155 var issues = xtextIssues.stream()
156 .map(issue -> new ValidationResult.Issue(issue.getMessage(), "error", issue.getLineNumber(),
157 issue.getColumn(), issue.getOffset(), issue.getLength()))
158 .toList();
159 return new SemanticsIssuesResult(issues);
160 }
133} 161}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
index 1542c694..2d43fb26 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -53,10 +53,9 @@ public class PushWebDocument extends XtextWebDocument {
53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName, 53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
54 CancelIndicator cancelIndicator, boolean logCacheMiss) { 54 CancelIndicator cancelIndicator, boolean logCacheMiss) {
55 var serviceClass = service.getClass(); 55 var serviceClass = service.getClass();
56 var previousResult = precomputedServices.get(serviceClass);
57 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss); 56 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
58 precomputedServices.put(serviceClass, result); 57 precomputedServices.put(serviceClass, result);
59 if (result != null && !result.equals(previousResult)) { 58 if (result != null) {
60 notifyPrecomputationListeners(serviceName, result); 59 notifyPrecomputationListeners(serviceName, result);
61 } 60 }
62 } 61 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
index 99ca5420..889a55cb 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
@@ -93,7 +93,7 @@ class ProblemWebSocketServletIntegrationTest {
93 clientSocket.waitForTestResult(); 93 clientSocket.waitForTestResult();
94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); 94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
95 var responses = clientSocket.getResponses(); 95 var responses = clientSocket.getResponses();
96 assertThat(responses, hasSize(7)); 96 assertThat(responses, hasSize(8));
97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); 97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}"));
98 assertThat(responses.get(1), startsWith( 98 assertThat(responses.get(1), startsWith(
99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," + 99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," +
@@ -108,7 +108,10 @@ class ProblemWebSocketServletIntegrationTest {
108 assertThat(responses.get(5), startsWith( 108 assertThat(responses.get(5), startsWith(
109 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," + 109 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," +
110 "\"push\":{\"regions\":[")); 110 "\"push\":{\"regions\":["));
111 assertThat(responses.get(6), startsWith( 111 assertThat(responses.get(6), equalTo(
112 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"validate\"," +
113 "\"push\":{\"issues\":[]}}"));
114 assertThat(responses.get(7), startsWith(
112 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"semantics\"," + 115 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"semantics\"," +
113 "\"push\":{")); 116 "\"push\":{"));
114 } 117 }
@@ -130,7 +133,7 @@ class ProblemWebSocketServletIntegrationTest {
130 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}", 133 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}",
131 Callback.NOOP 134 Callback.NOOP
132 ); 135 );
133 case 7 -> session.close(); 136 case 8 -> session.close();
134 } 137 }
135 } 138 }
136 } 139 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
index b7142506..22ce1b47 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
@@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test;
18import org.junit.jupiter.api.extension.ExtendWith; 18import org.junit.jupiter.api.extension.ExtendWith;
19import org.mockito.ArgumentCaptor; 19import org.mockito.ArgumentCaptor;
20import org.mockito.junit.jupiter.MockitoExtension; 20import org.mockito.junit.jupiter.MockitoExtension;
21import tools.refinery.language.web.semantics.SemanticsService;
21import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider; 22import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider;
22import tools.refinery.language.web.tests.ProblemWebInjectorProvider; 23import tools.refinery.language.web.tests.ProblemWebInjectorProvider;
23import tools.refinery.language.web.xtext.server.ResponseHandler; 24import tools.refinery.language.web.xtext.server.ResponseHandler;
@@ -59,11 +60,16 @@ class TransactionExecutorTest {
59 @Inject 60 @Inject
60 private AwaitTerminationExecutorServiceProvider executorServices; 61 private AwaitTerminationExecutorServiceProvider executorServices;
61 62
63 @Inject
64 private SemanticsService semanticsService;
65
62 private TransactionExecutor transactionExecutor; 66 private TransactionExecutor transactionExecutor;
63 67
64 @BeforeEach 68 @BeforeEach
65 void beforeEach() { 69 void beforeEach() {
66 transactionExecutor = new TransactionExecutor(new SimpleSession(), resourceServiceProviderRegistry); 70 transactionExecutor = new TransactionExecutor(new SimpleSession(), resourceServiceProviderRegistry);
71 // Manually re-create the semantics analysis thread pool if it was disposed by the previous test.
72 semanticsService.setExecutorServiceProvider(executorServices);
67 } 73 }
68 74
69 @Test 75 @Test
@@ -95,7 +101,7 @@ class TransactionExecutorTest {
95 "0"))); 101 "0")));
96 102
97 var captor = newCaptor(); 103 var captor = newCaptor();
98 verify(responseHandler, times(3)).onResponse(captor.capture()); 104 verify(responseHandler, times(4)).onResponse(captor.capture());
99 var newStateId = getStateId("bar", captor.getAllValues().get(0)); 105 var newStateId = getStateId("bar", captor.getAllValues().get(0));
100 assertHighlightingResponse(newStateId, captor.getAllValues().get(1)); 106 assertHighlightingResponse(newStateId, captor.getAllValues().get(1));
101 } 107 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java
new file mode 100644
index 00000000..c39277a0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8public class InvalidQueryException extends RuntimeException {
9 public InvalidQueryException() {
10 }
11
12 public InvalidQueryException(String message) {
13 super(message);
14 }
15
16 public InvalidQueryException(String message, Throwable cause) {
17 super(message, cause);
18 }
19
20 public InvalidQueryException(Throwable cause) {
21 super(cause);
22 }
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
index 5d77b9aa..8800a155 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
@@ -7,6 +7,7 @@ package tools.refinery.store.query.dnf;
7 7
8import org.jetbrains.annotations.NotNull; 8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.query.Constraint; 9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.literal.*; 11import tools.refinery.store.query.literal.*;
11import tools.refinery.store.query.substitution.MapBasedSubstitution; 12import tools.refinery.store.query.substitution.MapBasedSubstitution;
12import tools.refinery.store.query.substitution.StatelessSubstitution; 13import tools.refinery.store.query.substitution.StatelessSubstitution;
@@ -160,7 +161,7 @@ class ClausePostProcessor {
160 // Inputs count as positive, because they are already bound when we evaluate literals. 161 // Inputs count as positive, because they are already bound when we evaluate literals.
161 positiveVariables.add(variable); 162 positiveVariables.add(variable);
162 } else if (!existentiallyQuantifiedVariables.contains(variable)) { 163 } else if (!existentiallyQuantifiedVariables.contains(variable)) {
163 throw new IllegalArgumentException("Unbound %s parameter %s" 164 throw new InvalidQueryException("Unbound %s parameter %s"
164 .formatted(ParameterDirection.OUT, variable)); 165 .formatted(ParameterDirection.OUT, variable));
165 } 166 }
166 } 167 }
@@ -172,7 +173,7 @@ class ClausePostProcessor {
172 var representative = pair.getKey(); 173 var representative = pair.getKey();
173 if (!positiveVariables.contains(representative)) { 174 if (!positiveVariables.contains(representative)) {
174 var variableSet = pair.getValue(); 175 var variableSet = pair.getValue();
175 throw new IllegalArgumentException("Variables %s were merged by equivalence but are not bound" 176 throw new InvalidQueryException("Variables %s were merged by equivalence but are not bound"
176 .formatted(variableSet)); 177 .formatted(variableSet));
177 } 178 }
178 } 179 }
@@ -184,7 +185,7 @@ class ClausePostProcessor {
184 for (var variable : literal.getPrivateVariables(positiveVariables)) { 185 for (var variable : literal.getPrivateVariables(positiveVariables)) {
185 var oldLiteral = negativeVariablesMap.put(variable, literal); 186 var oldLiteral = negativeVariablesMap.put(variable, literal);
186 if (oldLiteral != null) { 187 if (oldLiteral != null) {
187 throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s" 188 throw new InvalidQueryException("Unbound variable %s appears in multiple literals %s and %s"
188 .formatted(variable, oldLiteral, literal)); 189 .formatted(variable, oldLiteral, literal));
189 } 190 }
190 } 191 }
@@ -206,7 +207,7 @@ class ClausePostProcessor {
206 variable.addToSortedLiterals(); 207 variable.addToSortedLiterals();
207 } 208 }
208 if (!variableToLiteralInputMap.isEmpty()) { 209 if (!variableToLiteralInputMap.isEmpty()) {
209 throw new IllegalArgumentException("Unbound input variables %s" 210 throw new InvalidQueryException("Unbound input variables %s"
210 .formatted(variableToLiteralInputMap.keySet())); 211 .formatted(variableToLiteralInputMap.keySet()));
211 } 212 }
212 } 213 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
index 55f1aae5..86a1b6b2 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.DnfEqualityChecker; 10import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.equality.LiteralEqualityHelper; 11import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper; 12import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
@@ -55,7 +56,7 @@ public final class Dnf implements Constraint {
55 FunctionalDependency<Variable> functionalDependency) { 56 FunctionalDependency<Variable> functionalDependency) {
56 for (var variable : toValidate) { 57 for (var variable : toValidate) {
57 if (!parameterSet.contains(variable)) { 58 if (!parameterSet.contains(variable)) {
58 throw new IllegalArgumentException( 59 throw new InvalidQueryException(
59 "Variable %s of functional dependency %s does not appear in the parameter list %s" 60 "Variable %s of functional dependency %s does not appear in the parameter list %s"
60 .formatted(variable, functionalDependency, symbolicParameters)); 61 .formatted(variable, functionalDependency, symbolicParameters));
61 } 62 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
index 0538427f..0f9fd366 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java
@@ -5,10 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.dnf.callback.*; 9import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
11import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper;
12import tools.refinery.store.query.literal.Literal; 10import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.*; 11import tools.refinery.store.query.term.*;
14 12
@@ -100,7 +98,7 @@ public final class DnfBuilder {
100 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { 98 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) {
101 var variable = symbolicParameter.getVariable(); 99 var variable = symbolicParameter.getVariable();
102 if (!parameterVariables.add(variable)) { 100 if (!parameterVariables.add(variable)) {
103 throw new IllegalArgumentException("Variable %s is already on the parameter list %s" 101 throw new InvalidQueryException("Variable %s is already on the parameter list %s"
104 .formatted(variable, parameters)); 102 .formatted(variable, parameters));
105 } 103 }
106 parameters.add(symbolicParameter); 104 parameters.add(symbolicParameter);
@@ -218,88 +216,10 @@ public final class DnfBuilder {
218 } 216 }
219 217
220 public Dnf build() { 218 public Dnf build() {
221 var postProcessedClauses = postProcessClauses(); 219 var postProcessor = new DnfPostProcessor(parameters, clauses);
220 var postProcessedClauses = postProcessor.postProcessClauses();
222 return new Dnf(name, Collections.unmodifiableList(parameters), 221 return new Dnf(name, Collections.unmodifiableList(parameters),
223 Collections.unmodifiableList(functionalDependencies), 222 Collections.unmodifiableList(functionalDependencies),
224 Collections.unmodifiableList(postProcessedClauses)); 223 Collections.unmodifiableList(postProcessedClauses));
225 } 224 }
226
227 private List<DnfClause> postProcessClauses() {
228 var parameterInfoMap = getParameterInfoMap();
229 var postProcessedClauses = new LinkedHashSet<CanonicalClause>(clauses.size());
230 for (var literals : clauses) {
231 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
232 var result = postProcessor.postProcessClause();
233 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
234 postProcessedClauses.add(new CanonicalClause(clauseResult.clause()));
235 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
236 switch (constantResult) {
237 case ALWAYS_TRUE -> {
238 var inputVariables = getInputVariables();
239 return List.of(new DnfClause(inputVariables, List.of()));
240 }
241 case ALWAYS_FALSE -> {
242 // Skip this clause because it can never match.
243 }
244 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
245 constantResult);
246 }
247 } else {
248 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
249 }
250 }
251 return postProcessedClauses.stream().map(CanonicalClause::getDnfClause).toList();
252 }
253
254 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
255 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
256 int arity = parameters.size();
257 for (int i = 0; i < arity; i++) {
258 var parameter = parameters.get(i);
259 mutableParameterInfoMap.put(parameter.getVariable(),
260 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
261 }
262 return Collections.unmodifiableMap(mutableParameterInfoMap);
263 }
264
265 private Set<Variable> getInputVariables() {
266 var inputParameters = new LinkedHashSet<Variable>();
267 for (var parameter : parameters) {
268 if (parameter.getDirection() == ParameterDirection.IN) {
269 inputParameters.add(parameter.getVariable());
270 }
271 }
272 return Collections.unmodifiableSet(inputParameters);
273 }
274
275 private class CanonicalClause {
276 private final DnfClause dnfClause;
277
278 public CanonicalClause(DnfClause dnfClause) {
279 this.dnfClause = dnfClause;
280 }
281
282 public DnfClause getDnfClause() {
283 return dnfClause;
284 }
285
286 @Override
287 public boolean equals(Object obj) {
288 if (this == obj) {
289 return true;
290 }
291 if (obj == null || getClass() != obj.getClass()) {
292 return false;
293 }
294 var otherCanonicalClause = (CanonicalClause) obj;
295 var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, parameters, parameters);
296 return dnfClause.equalsWithSubstitution(helper, otherCanonicalClause.dnfClause);
297 }
298
299 @Override
300 public int hashCode() {
301 var helper = new SubstitutingLiteralHashCodeHelper(parameters);
302 return dnfClause.hashCodeWithSubstitution(helper);
303 }
304 }
305} 225}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
new file mode 100644
index 00000000..50236642
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
@@ -0,0 +1,112 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
11import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.ParameterDirection;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.*;
17
18class DnfPostProcessor {
19 private final List<SymbolicParameter> parameters;
20 private final List<List<Literal>> clauses;
21
22 public DnfPostProcessor(List<SymbolicParameter> parameters, List<List<Literal>> clauses) {
23 this.parameters = parameters;
24 this.clauses = clauses;
25 }
26
27 public List<DnfClause> postProcessClauses() {
28 var parameterInfoMap = getParameterInfoMap();
29 var postProcessedClauses = new LinkedHashSet<CanonicalClause>(clauses.size());
30 int index = 0;
31 for (var literals : clauses) {
32 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
33 ClausePostProcessor.Result result;
34 try {
35 result = postProcessor.postProcessClause();
36 } catch (InvalidQueryException e) {
37 throw new InvalidClauseException(index, e);
38 }
39 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
40 postProcessedClauses.add(new CanonicalClause(clauseResult.clause()));
41 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
42 switch (constantResult) {
43 case ALWAYS_TRUE -> {
44 var inputVariables = getInputVariables();
45 return List.of(new DnfClause(inputVariables, List.of()));
46 }
47 case ALWAYS_FALSE -> {
48 // Skip this clause because it can never match.
49 }
50 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
51 constantResult);
52 }
53 } else {
54 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
55 }
56 index++;
57 }
58 return postProcessedClauses.stream().map(CanonicalClause::getDnfClause).toList();
59 }
60
61 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
62 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
63 int arity = parameters.size();
64 for (int i = 0; i < arity; i++) {
65 var parameter = parameters.get(i);
66 mutableParameterInfoMap.put(parameter.getVariable(),
67 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
68 }
69 return Collections.unmodifiableMap(mutableParameterInfoMap);
70 }
71
72 private Set<Variable> getInputVariables() {
73 var inputParameters = new LinkedHashSet<Variable>();
74 for (var parameter : parameters) {
75 if (parameter.getDirection() == ParameterDirection.IN) {
76 inputParameters.add(parameter.getVariable());
77 }
78 }
79 return Collections.unmodifiableSet(inputParameters);
80 }
81
82 private class CanonicalClause {
83 private final DnfClause dnfClause;
84
85 public CanonicalClause(DnfClause dnfClause) {
86 this.dnfClause = dnfClause;
87 }
88
89 public DnfClause getDnfClause() {
90 return dnfClause;
91 }
92
93 @Override
94 public boolean equals(Object obj) {
95 if (this == obj) {
96 return true;
97 }
98 if (obj == null || getClass() != obj.getClass()) {
99 return false;
100 }
101 var otherCanonicalClause = (CanonicalClause) obj;
102 var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, parameters, parameters);
103 return dnfClause.equalsWithSubstitution(helper, otherCanonicalClause.dnfClause);
104 }
105
106 @Override
107 public int hashCode() {
108 var helper = new SubstitutingLiteralHashCodeHelper(parameters);
109 return dnfClause.hashCodeWithSubstitution(helper);
110 }
111 }
112}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
index b00b2cb7..aef07ee3 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
9
8import java.util.HashSet; 10import java.util.HashSet;
9import java.util.Set; 11import java.util.Set;
10 12
@@ -13,7 +15,7 @@ public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
13 var uniqueForEach = new HashSet<>(unique); 15 var uniqueForEach = new HashSet<>(unique);
14 uniqueForEach.retainAll(forEach); 16 uniqueForEach.retainAll(forEach);
15 if (!uniqueForEach.isEmpty()) { 17 if (!uniqueForEach.isEmpty()) {
16 throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency" 18 throw new InvalidQueryException("Variables %s appear on both sides of the functional dependency"
17 .formatted(uniqueForEach)); 19 .formatted(uniqueForEach));
18 } 20 }
19 } 21 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
index bf7651ad..225f6844 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallPolarity; 9import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.term.Aggregator; 10import tools.refinery.store.query.term.Aggregator;
10import tools.refinery.store.query.term.AssignedValue; 11import tools.refinery.store.query.term.AssignedValue;
@@ -26,14 +27,14 @@ public final class FunctionalQuery<T> extends Query<T> {
26 var parameter = parameters.get(i); 27 var parameter = parameters.get(i);
27 var parameterType = parameter.tryGetType(); 28 var parameterType = parameter.tryGetType();
28 if (parameterType.isPresent()) { 29 if (parameterType.isPresent()) {
29 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" 30 throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead"
30 .formatted(parameter, dnf, parameterType.get().getName())); 31 .formatted(parameter, dnf, parameterType.get().getName()));
31 } 32 }
32 } 33 }
33 var outputParameter = parameters.get(outputIndex); 34 var outputParameter = parameters.get(outputIndex);
34 var outputParameterType = outputParameter.tryGetType(); 35 var outputParameterType = outputParameter.tryGetType();
35 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) { 36 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) {
36 throw new IllegalArgumentException("Expected parameter %s of %s to be %s, but got %s instead".formatted( 37 throw new InvalidQueryException("Expected parameter %s of %s to be %s, but got %s instead".formatted(
37 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); 38 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node")));
38 } 39 }
39 this.type = type; 40 this.type = type;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java
new file mode 100644
index 00000000..747574b9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.InvalidQueryException;
9
10public class InvalidClauseException extends InvalidQueryException {
11 private final int clauseIndex;
12
13 public InvalidClauseException(int clauseIndex) {
14 this.clauseIndex = clauseIndex;
15 }
16
17 public InvalidClauseException(int clauseIndex, String message) {
18 super(message);
19 this.clauseIndex = clauseIndex;
20 }
21
22 public InvalidClauseException(int clauseIndex, String message, Throwable cause) {
23 super(message, cause);
24 this.clauseIndex = clauseIndex;
25 }
26
27 public InvalidClauseException(int clauseIndex, Throwable cause) {
28 super(cause);
29 this.clauseIndex = clauseIndex;
30 }
31
32 public int getClauseIndex() {
33 return clauseIndex;
34 }
35}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
index 618fb595..98f71e11 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallLiteral; 9import tools.refinery.store.query.literal.CallLiteral;
9import tools.refinery.store.query.literal.CallPolarity; 10import tools.refinery.store.query.literal.CallPolarity;
10import tools.refinery.store.query.term.AssignedValue; 11import tools.refinery.store.query.term.AssignedValue;
@@ -19,7 +20,7 @@ public final class RelationalQuery extends Query<Boolean> {
19 for (var parameter : dnf.getSymbolicParameters()) { 20 for (var parameter : dnf.getSymbolicParameters()) {
20 var parameterType = parameter.tryGetType(); 21 var parameterType = parameter.tryGetType();
21 if (parameterType.isPresent()) { 22 if (parameterType.isPresent()) {
22 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" 23 throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead"
23 .formatted(parameter, dnf, parameterType.get().getName())); 24 .formatted(parameter, dnf, parameterType.get().getName()));
24 } 25 }
25 } 26 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
index 3722f7f9..0e99d441 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper; 11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
11import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
@@ -27,7 +28,7 @@ public abstract class AbstractCallLiteral extends AbstractLiteral {
27 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { 28 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
28 int arity = target.arity(); 29 int arity = target.arity();
29 if (arguments.size() != arity) { 30 if (arguments.size() != arity) {
30 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), 31 throw new InvalidQueryException("%s needs %d arguments, but got %s".formatted(target.name(),
31 target.arity(), arguments.size())); 32 target.arity(), arguments.size()));
32 } 33 }
33 this.target = target; 34 this.target = target;
@@ -39,7 +40,7 @@ public abstract class AbstractCallLiteral extends AbstractLiteral {
39 var argument = arguments.get(i); 40 var argument = arguments.get(i);
40 var parameter = parameters.get(i); 41 var parameter = parameters.get(i);
41 if (!parameter.isAssignable(argument)) { 42 if (!parameter.isAssignable(argument)) {
42 throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" 43 throw new InvalidQueryException("Argument %d of %s is not assignable to parameter %s"
43 .formatted(i, target, parameter)); 44 .formatted(i, target, parameter));
44 } 45 }
45 switch (parameter.getDirection()) { 46 switch (parameter.getDirection()) {
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java
index 75f4bd49..9bb572c0 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper; 11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
11import tools.refinery.store.query.term.ConstantTerm; 12import tools.refinery.store.query.term.ConstantTerm;
@@ -26,11 +27,11 @@ public abstract class AbstractCountLiteral<T> extends AbstractCallLiteral {
26 List<Variable> arguments) { 27 List<Variable> arguments) {
27 super(target, arguments); 28 super(target, arguments);
28 if (!resultVariable.getType().equals(resultType)) { 29 if (!resultVariable.getType().equals(resultType)) {
29 throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted( 30 throw new InvalidQueryException("Count result variable %s must be of type %s, got %s instead".formatted(
30 resultVariable, resultType, resultVariable.getType().getName())); 31 resultVariable, resultType, resultVariable.getType().getName()));
31 } 32 }
32 if (arguments.contains(resultVariable)) { 33 if (arguments.contains(resultVariable)) {
33 throw new IllegalArgumentException("Count result variable %s must not appear in the argument list" 34 throw new InvalidQueryException("Count result variable %s must not appear in the argument list"
34 .formatted(resultVariable)); 35 .formatted(resultVariable));
35 } 36 }
36 this.resultType = resultType; 37 this.resultType = resultType;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
index a2f8e009..e3acfacc 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper; 11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
11import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
@@ -26,19 +27,19 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
26 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { 27 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
27 super(target, arguments); 28 super(target, arguments);
28 if (!inputVariable.getType().equals(aggregator.getInputType())) { 29 if (!inputVariable.getType().equals(aggregator.getInputType())) {
29 throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( 30 throw new InvalidQueryException("Input variable %s must of type %s, got %s instead".formatted(
30 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); 31 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
31 } 32 }
32 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { 33 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) {
33 throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list" 34 throw new InvalidQueryException("Input variable %s must be bound with direction %s in the argument list"
34 .formatted(inputVariable, ParameterDirection.OUT)); 35 .formatted(inputVariable, ParameterDirection.OUT));
35 } 36 }
36 if (!resultVariable.getType().equals(aggregator.getResultType())) { 37 if (!resultVariable.getType().equals(aggregator.getResultType())) {
37 throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( 38 throw new InvalidQueryException("Result variable %s must of type %s, got %s instead".formatted(
38 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); 39 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
39 } 40 }
40 if (arguments.contains(resultVariable)) { 41 if (arguments.contains(resultVariable)) {
41 throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted( 42 throw new InvalidQueryException("Result variable %s must not appear in the argument list".formatted(
42 resultVariable)); 43 resultVariable));
43 } 44 }
44 this.resultVariable = resultVariable; 45 this.resultVariable = resultVariable;
@@ -66,7 +67,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
66 @Override 67 @Override
67 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { 68 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
68 if (positiveVariablesInClause.contains(inputVariable)) { 69 if (positiveVariablesInClause.contains(inputVariable)) {
69 throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable)); 70 throw new InvalidQueryException("Aggregation variable %s must not be bound".formatted(inputVariable));
70 } 71 }
71 return super.getInputVariables(positiveVariablesInClause); 72 return super.getInputVariables(positiveVariablesInClause);
72 } 73 }
@@ -80,7 +81,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
80 yield emptyValue == null ? BooleanLiteral.FALSE : 81 yield emptyValue == null ? BooleanLiteral.FALSE :
81 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); 82 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue));
82 } 83 }
83 case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set"); 84 case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to aggregate over an infinite set");
84 case NOT_REDUCIBLE -> this; 85 case NOT_REDUCIBLE -> this;
85 }; 86 };
86 } 87 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
index d8a4b494..dadf487f 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper; 10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
@@ -16,18 +17,20 @@ import java.util.Collections;
16import java.util.Objects; 17import java.util.Objects;
17import java.util.Set; 18import java.util.Set;
18 19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
19public class AssignLiteral<T> extends AbstractLiteral { 22public class AssignLiteral<T> extends AbstractLiteral {
20 private final DataVariable<T> variable; 23 private final DataVariable<T> variable;
21 private final Term<T> term; 24 private final Term<T> term;
22 25
23 public AssignLiteral(DataVariable<T> variable, Term<T> term) { 26 public AssignLiteral(DataVariable<T> variable, Term<T> term) {
24 if (!term.getType().equals(variable.getType())) { 27 if (!term.getType().equals(variable.getType())) {
25 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( 28 throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted(
26 term, variable.getType().getName(), term.getType().getName())); 29 term, variable.getType().getName(), term.getType().getName()));
27 } 30 }
28 var inputVariables = term.getInputVariables(); 31 var inputVariables = term.getInputVariables();
29 if (inputVariables.contains(variable)) { 32 if (inputVariables.contains(variable)) {
30 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( 33 throw new InvalidQueryException("Result variable %s must not appear in the term %s".formatted(
31 variable, term)); 34 variable, term));
32 } 35 }
33 this.variable = variable; 36 this.variable = variable;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
index 1b05943d..2d0e4e97 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper; 11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
11import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
@@ -25,14 +26,14 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
25 int arity = target.arity(); 26 int arity = target.arity();
26 if (polarity.isTransitive()) { 27 if (polarity.isTransitive()) {
27 if (arity != 2) { 28 if (arity != 2) {
28 throw new IllegalArgumentException("Transitive closures can only take binary relations"); 29 throw new InvalidQueryException("Transitive closures can only take binary relations");
29 } 30 }
30 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { 31 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
31 throw new IllegalArgumentException("Transitive closures can only be computed over nodes"); 32 throw new InvalidQueryException("Transitive closures can only be computed over nodes");
32 } 33 }
33 if (parameters.get(0).getDirection() != ParameterDirection.OUT || 34 if (parameters.get(0).getDirection() != ParameterDirection.OUT ||
34 parameters.get(1).getDirection() != ParameterDirection.OUT) { 35 parameters.get(1).getDirection() != ParameterDirection.OUT) {
35 throw new IllegalArgumentException("Transitive closures cannot take input parameters"); 36 throw new InvalidQueryException("Transitive closures cannot take input parameters");
36 } 37 }
37 } 38 }
38 this.polarity = polarity; 39 this.polarity = polarity;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
index ca70b0fd..716c7109 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
9
8public enum CallPolarity { 10public enum CallPolarity {
9 POSITIVE(true, false), 11 POSITIVE(true, false),
10 NEGATIVE(false, false), 12 NEGATIVE(false, false),
@@ -31,7 +33,7 @@ public enum CallPolarity {
31 return switch (this) { 33 return switch (this) {
32 case POSITIVE -> NEGATIVE; 34 case POSITIVE -> NEGATIVE;
33 case NEGATIVE -> POSITIVE; 35 case NEGATIVE -> POSITIVE;
34 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated"); 36 case TRANSITIVE -> throw new InvalidQueryException("Transitive polarity cannot be negated");
35 }; 37 };
36 } 38 }
37} 39}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java
index 1271183a..dfedd2cb 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper; 10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
@@ -18,12 +19,14 @@ import java.util.Collections;
18import java.util.Objects; 19import java.util.Objects;
19import java.util.Set; 20import java.util.Set;
20 21
22// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
23@SuppressWarnings("squid:S2160")
21public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> { 24public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> {
22 private final Term<Boolean> term; 25 private final Term<Boolean> term;
23 26
24 public CheckLiteral(Term<Boolean> term) { 27 public CheckLiteral(Term<Boolean> term) {
25 if (!term.getType().equals(Boolean.class)) { 28 if (!term.getType().equals(Boolean.class)) {
26 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( 29 throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted(
27 term, Boolean.class.getName(), term.getType().getName())); 30 term, Boolean.class.getName(), term.getType().getName()));
28 } 31 }
29 this.term = term; 32 this.term = term;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
index 9a0c22d1..7343f709 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper; 10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
@@ -22,7 +23,7 @@ public final class EquivalenceLiteral extends AbstractLiteral implements CanNega
22 23
23 public EquivalenceLiteral(boolean positive, Variable left, Variable right) { 24 public EquivalenceLiteral(boolean positive, Variable left, Variable right) {
24 if (!left.tryGetType().equals(right.tryGetType())) { 25 if (!left.tryGetType().equals(right.tryGetType())) {
25 throw new IllegalArgumentException("Variables %s and %s of different type cannot be equivalent" 26 throw new InvalidQueryException("Variables %s and %s of different type cannot be equivalent"
26 .formatted(left, right)); 27 .formatted(left, right));
27 } 28 }
28 this.positive = positive; 29 this.positive = positive;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java
index f6545f9f..f7323947 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper; 11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
11import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
@@ -32,14 +33,14 @@ public class RepresentativeElectionLiteral extends AbstractCallLiteral {
32 var parameters = target.getParameters(); 33 var parameters = target.getParameters();
33 int arity = target.arity(); 34 int arity = target.arity();
34 if (arity != 2) { 35 if (arity != 2) {
35 throw new IllegalArgumentException("SCCs can only take binary relations"); 36 throw new InvalidQueryException("SCCs can only take binary relations");
36 } 37 }
37 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { 38 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
38 throw new IllegalArgumentException("SCCs can only be computed over nodes"); 39 throw new InvalidQueryException("SCCs can only be computed over nodes");
39 } 40 }
40 if (parameters.get(0).getDirection() != ParameterDirection.OUT || 41 if (parameters.get(0).getDirection() != ParameterDirection.OUT ||
41 parameters.get(1).getDirection() != ParameterDirection.OUT) { 42 parameters.get(1).getDirection() != ParameterDirection.OUT) {
42 throw new IllegalArgumentException("SCCs cannot take input parameters"); 43 throw new InvalidQueryException("SCCs cannot take input parameters");
43 } 44 }
44 } 45 }
45 46
@@ -72,7 +73,7 @@ public class RepresentativeElectionLiteral extends AbstractCallLiteral {
72 var reduction = getTarget().getReduction(); 73 var reduction = getTarget().getReduction();
73 return switch (reduction) { 74 return switch (reduction) {
74 case ALWAYS_FALSE -> BooleanLiteral.FALSE; 75 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
75 case ALWAYS_TRUE -> throw new IllegalArgumentException( 76 case ALWAYS_TRUE -> throw new InvalidQueryException(
76 "Trying to elect representatives over an infinite set"); 77 "Trying to elect representatives over an infinite set");
77 case NOT_REDUCIBLE -> this; 78 case NOT_REDUCIBLE -> this;
78 }; 79 };
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
index 4d88051b..3801bc11 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10 11
11import java.util.Optional; 12import java.util.Optional;
@@ -33,7 +34,7 @@ public abstract sealed class AnyDataVariable extends Variable implements AnyTerm
33 34
34 @Override 35 @Override
35 public NodeVariable asNodeVariable() { 36 public NodeVariable asNodeVariable() {
36 throw new IllegalStateException("%s is a data variable".formatted(this)); 37 throw new InvalidQueryException("%s is a data variable".formatted(this));
37 } 38 }
38 39
39 @Override 40 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
index 09c86db6..cdbf592a 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper; 10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
@@ -26,11 +27,11 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
26 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) { 27 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) {
27 super(type); 28 super(type);
28 if (!left.getType().equals(leftType)) { 29 if (!left.getType().equals(leftType)) {
29 throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted( 30 throw new InvalidQueryException("Expected left %s to be of type %s, got %s instead".formatted(
30 left, leftType.getName(), left.getType().getName())); 31 left, leftType.getName(), left.getType().getName()));
31 } 32 }
32 if (!right.getType().equals(rightType)) { 33 if (!right.getType().equals(rightType)) {
33 throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted( 34 throw new InvalidQueryException("Expected right %s to be of type %s, got %s instead".formatted(
34 right, rightType.getName(), right.getType().getName())); 35 right, rightType.getName(), right.getType().getName()));
35 } 36 }
36 this.leftType = leftType; 37 this.leftType = leftType;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
index e722c84f..415ae286 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper; 10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
@@ -21,7 +22,7 @@ public final class ConstantTerm<T> extends AbstractTerm<T> {
21 public ConstantTerm(Class<T> type, T value) { 22 public ConstantTerm(Class<T> type, T value) {
22 super(type); 23 super(type);
23 if (value != null && !type.isInstance(value)) { 24 if (value != null && !type.isInstance(value)) {
24 throw new IllegalArgumentException("Value %s is not an instance of %s".formatted(value, type.getName())); 25 throw new InvalidQueryException("Value %s is not an instance of %s".formatted(value, type.getName()));
25 } 26 }
26 this.value = value; 27 this.value = value;
27 } 28 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
index 9b62e545..2206b522 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper; 11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
11import tools.refinery.store.query.literal.EquivalenceLiteral; 12import tools.refinery.store.query.literal.EquivalenceLiteral;
@@ -41,8 +42,8 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
41 @Override 42 @Override
42 public <U> DataVariable<U> asDataVariable(Class<U> newType) { 43 public <U> DataVariable<U> asDataVariable(Class<U> newType) {
43 if (!getType().equals(newType)) { 44 if (!getType().equals(newType)) {
44 throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(), 45 throw new InvalidQueryException("%s is not of type %s but of type %s"
45 getType().getName())); 46 .formatted(this, newType.getName(), getType().getName()));
46 } 47 }
47 @SuppressWarnings("unchecked") 48 @SuppressWarnings("unchecked")
48 var result = (DataVariable<U>) this; 49 var result = (DataVariable<U>) this;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
index 2f9c8bf1..53c32e20 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.literal.ConstantLiteral; 10import tools.refinery.store.query.literal.ConstantLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral; 11import tools.refinery.store.query.literal.EquivalenceLiteral;
11 12
@@ -48,7 +49,7 @@ public final class NodeVariable extends Variable {
48 49
49 @Override 50 @Override
50 public <T> DataVariable<T> asDataVariable(Class<T> type) { 51 public <T> DataVariable<T> asDataVariable(Class<T> type) {
51 throw new IllegalStateException("%s is a node variable".formatted(this)); 52 throw new InvalidQueryException("%s is a node variable".formatted(this));
52 } 53 }
53 54
54 @Override 55 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
index cd0739be..da83f3c3 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
@@ -6,8 +6,8 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8public enum ParameterDirection { 8public enum ParameterDirection {
9 OUT("@Out"), 9 OUT("out"),
10 IN("@In"); 10 IN("in");
11 11
12 private final String name; 12 private final String name;
13 13
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
index 6451ea00..a464ece5 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper; 10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
@@ -22,7 +23,7 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
22 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) { 23 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) {
23 super(type); 24 super(type);
24 if (!body.getType().equals(bodyType)) { 25 if (!body.getType().equals(bodyType)) {
25 throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body, 26 throw new InvalidQueryException("Expected body %s to be of type %s, got %s instead".formatted(body,
26 bodyType.getName(), body.getType().getName())); 27 bodyType.getName(), body.getType().getName()));
27 } 28 }
28 this.bodyType = bodyType; 29 this.bodyType = bodyType;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
index abae6e5c..924277ed 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.view; 6package tools.refinery.store.query.view;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.representation.Symbol; 10import tools.refinery.store.representation.Symbol;
10 11
@@ -66,7 +67,7 @@ public class FilteredView<T> extends TuplePreservingView<T> {
66 // The predicate doesn't need to handle the default value if it is null. 67 // The predicate doesn't need to handle the default value if it is null.
67 } 68 }
68 if (matchesDefaultValue) { 69 if (matchesDefaultValue) {
69 throw new IllegalArgumentException("Tuples with default value %s cannot be enumerated in %s" 70 throw new InvalidQueryException("Tuples with default value %s cannot be enumerated in %s"
70 .formatted(defaultValue, getSymbol())); 71 .formatted(defaultValue, getSymbol()));
71 } 72 }
72 } 73 }
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
index d75d7f17..12cfaa4e 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
@@ -50,7 +50,7 @@ class DnfToDefinitionStringTest {
50 var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build(); 50 var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build();
51 51
52 assertThat(dnf.toDefinitionString(), is(""" 52 assertThat(dnf.toDefinitionString(), is("""
53 pred Example(@In p) <-> 53 pred Example(in p) <->
54 <empty>. 54 <empty>.
55 """)); 55 """));
56 } 56 }
@@ -73,7 +73,7 @@ class DnfToDefinitionStringTest {
73 .build(); 73 .build();
74 74
75 assertThat(dnf.toDefinitionString(), is(""" 75 assertThat(dnf.toDefinitionString(), is("""
76 pred Example(@In p) <-> 76 pred Example(in p) <->
77 !(@RelationView("key") friend(p, q)). 77 !(@RelationView("key") friend(p, q)).
78 """)); 78 """));
79 } 79 }
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java
index e22dbb21..854bd469 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.term.NodeVariable; 10import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.ParameterDirection; 11import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
@@ -80,7 +81,7 @@ class TopologicalSortTest {
80 example.call(r, t, q, s), 81 example.call(r, t, q, s),
81 friendView.call(r, t) 82 friendView.call(r, t)
82 ); 83 );
83 assertThrows(IllegalArgumentException.class, builder::build); 84 assertThrows(InvalidQueryException.class, builder::build);
84 } 85 }
85 86
86 @Test 87 @Test
@@ -93,7 +94,7 @@ class TopologicalSortTest {
93 example.call(p, q, r, s), 94 example.call(p, q, r, s),
94 example.call(r, t, q, s) 95 example.call(r, t, q, s)
95 ); 96 );
96 assertThrows(IllegalArgumentException.class, builder::build); 97 assertThrows(InvalidQueryException.class, builder::build);
97 } 98 }
98 99
99 @Test 100 @Test
@@ -107,6 +108,6 @@ class TopologicalSortTest {
107 example.call(r, t, q, s), 108 example.call(r, t, q, s),
108 example.call(p, q, r, t) 109 example.call(p, q, r, t)
109 ); 110 );
110 assertThrows(IllegalArgumentException.class, builder::build); 111 assertThrows(InvalidQueryException.class, builder::build);
111 } 112 }
112} 113}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java
index bfeaa447..fc3f5d48 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java
@@ -48,7 +48,7 @@ class VariableDirectionTest {
48 @MethodSource("clausesWithVariableInput") 48 @MethodSource("clausesWithVariableInput")
49 void unboundOutVariableTest(List<? extends Literal> clause) { 49 void unboundOutVariableTest(List<? extends Literal> clause) {
50 var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause); 50 var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause);
51 assertThrows(IllegalArgumentException.class, builder::build); 51 assertThrows(InvalidClauseException.class, builder::build);
52 } 52 }
53 53
54 @ParameterizedTest 54 @ParameterizedTest
@@ -100,7 +100,7 @@ class VariableDirectionTest {
100 var clauseWithEquivalence = new ArrayList<Literal>(clause); 100 var clauseWithEquivalence = new ArrayList<Literal>(clause);
101 clauseWithEquivalence.add(r.isEquivalent(p)); 101 clauseWithEquivalence.add(r.isEquivalent(p));
102 var builder = Dnf.builder().clause(clauseWithEquivalence); 102 var builder = Dnf.builder().clause(clauseWithEquivalence);
103 assertThrows(IllegalArgumentException.class, builder::build); 103 assertThrows(InvalidClauseException.class, builder::build);
104 } 104 }
105 105
106 static Stream<Arguments> clausesNotBindingVariable() { 106 static Stream<Arguments> clausesNotBindingVariable() {
@@ -118,7 +118,7 @@ class VariableDirectionTest {
118 @MethodSource("literalsWithPrivateVariable") 118 @MethodSource("literalsWithPrivateVariable")
119 void unboundTwicePrivateVariableTest(Literal literal) { 119 void unboundTwicePrivateVariableTest(Literal literal) {
120 var builder = Dnf.builder().clause(not(personView.call(p)), literal); 120 var builder = Dnf.builder().clause(not(personView.call(p)), literal);
121 assertThrows(IllegalArgumentException.class, builder::build); 121 assertThrows(InvalidClauseException.class, builder::build);
122 } 122 }
123 123
124 @ParameterizedTest 124 @ParameterizedTest
@@ -126,7 +126,7 @@ class VariableDirectionTest {
126 void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) { 126 void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) {
127 var r = Variable.of("r"); 127 var r = Variable.of("r");
128 var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal); 128 var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal);
129 assertThrows(IllegalArgumentException.class, builder::build); 129 assertThrows(InvalidClauseException.class, builder::build);
130 } 130 }
131 131
132 static Stream<Arguments> literalsWithPrivateVariable() { 132 static Stream<Arguments> literalsWithPrivateVariable() {
@@ -159,7 +159,7 @@ class VariableDirectionTest {
159 @MethodSource("literalsWithRequiredVariableInput") 159 @MethodSource("literalsWithRequiredVariableInput")
160 void unboundPrivateVariableTest(Literal literal) { 160 void unboundPrivateVariableTest(Literal literal) {
161 var builder = Dnf.builder().clause(literal); 161 var builder = Dnf.builder().clause(literal);
162 assertThrows(IllegalArgumentException.class, builder::build); 162 assertThrows(InvalidClauseException.class, builder::build);
163 } 163 }
164 164
165 @ParameterizedTest 165 @ParameterizedTest
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java
index 35910e08..ddd57e96 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java
@@ -7,15 +7,16 @@ package tools.refinery.store.query.literal;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.Constraint; 9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.InvalidClauseException;
11import tools.refinery.store.query.term.*; 13import tools.refinery.store.query.term.*;
12 14
13import java.util.List; 15import java.util.List;
14import java.util.Set; 16import java.util.Set;
15 17
16import static org.hamcrest.MatcherAssert.assertThat; 18import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.containsInAnyOrder; 19import static org.hamcrest.Matchers.*;
18import static org.hamcrest.Matchers.empty;
19import static org.junit.jupiter.api.Assertions.assertAll; 20import static org.junit.jupiter.api.Assertions.assertAll;
20import static org.junit.jupiter.api.Assertions.assertThrows; 21import static org.junit.jupiter.api.Assertions.assertThrows;
21import static tools.refinery.store.query.literal.Literals.not; 22import static tools.refinery.store.query.literal.Literals.not;
@@ -57,13 +58,13 @@ class AggregationLiteralTest {
57 @Test 58 @Test
58 void missingAggregationVariableTest() { 59 void missingAggregationVariableTest() {
59 var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z); 60 var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z);
60 assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); 61 assertThrows(InvalidQueryException.class, () -> x.assign(aggregation));
61 } 62 }
62 63
63 @Test 64 @Test
64 void circularAggregationVariableTest() { 65 void circularAggregationVariableTest() {
65 var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x); 66 var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x);
66 assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); 67 assertThrows(InvalidQueryException.class, () -> x.assign(aggregation));
67 } 68 }
68 69
69 @Test 70 @Test
@@ -73,7 +74,7 @@ class AggregationLiteralTest {
73 not(fakeConstraint.call(p, y)), 74 not(fakeConstraint.call(p, y)),
74 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) 75 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y))
75 ); 76 );
76 assertThrows(IllegalArgumentException.class, builder::build); 77 assertThrows(InvalidClauseException.class, builder::build);
77 } 78 }
78 79
79 @Test 80 @Test
@@ -83,6 +84,6 @@ class AggregationLiteralTest {
83 y.assign(constant(27)), 84 y.assign(constant(27)),
84 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) 85 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y))
85 ); 86 );
86 assertThrows(IllegalArgumentException.class, builder::build); 87 assertThrows(InvalidClauseException.class, builder::build);
87 } 88 }
88} 89}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java
index 6e0e91e1..2235a95d 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.reasoning.literal; 6package tools.refinery.store.reasoning.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Reduction; 11import tools.refinery.store.query.literal.Reduction;
11import tools.refinery.store.query.term.Parameter; 12import tools.refinery.store.query.term.Parameter;
@@ -17,7 +18,7 @@ public record ModalConstraint(Modality modality, Concreteness concreteness, Cons
17 implements Constraint { 18 implements Constraint {
18 public ModalConstraint { 19 public ModalConstraint {
19 if (constraint instanceof AnySymbolView || constraint instanceof ModalConstraint) { 20 if (constraint instanceof AnySymbolView || constraint instanceof ModalConstraint) {
20 throw new IllegalArgumentException("Already concrete constraints cannot be abstracted"); 21 throw new InvalidQueryException("Already concrete constraints cannot be abstracted");
21 } 22 }
22 } 23 }
23 24
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
index 2c879397..2614c26e 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.reasoning.literal; 6package tools.refinery.store.reasoning.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallLiteral; 9import tools.refinery.store.query.literal.CallLiteral;
9 10
10public final class PartialLiterals { 11public final class PartialLiterals {
@@ -31,7 +32,7 @@ public final class PartialLiterals {
31 public static CallLiteral addModality(CallLiteral literal, Modality modality, Concreteness concreteness) { 32 public static CallLiteral addModality(CallLiteral literal, Modality modality, Concreteness concreteness) {
32 var target = literal.getTarget(); 33 var target = literal.getTarget();
33 if (target instanceof ModalConstraint) { 34 if (target instanceof ModalConstraint) {
34 throw new IllegalArgumentException("Literal %s already has modality".formatted(literal)); 35 throw new InvalidQueryException("Literal %s already has modality".formatted(literal));
35 } 36 }
36 var polarity = literal.getPolarity(); 37 var polarity = literal.getPolarity();
37 var modalTarget = new ModalConstraint(modality.commute(polarity), concreteness, target); 38 var modalTarget = new ModalConstraint(modality.commute(polarity), concreteness, target);
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java
index 28e6258e..e6b3eaf9 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java
@@ -13,6 +13,7 @@ import tools.refinery.store.tuple.Tuple;
13import java.util.Collections; 13import java.util.Collections;
14import java.util.LinkedHashMap; 14import java.util.LinkedHashMap;
15import java.util.Map; 15import java.util.Map;
16import java.util.Set;
16import java.util.function.Consumer; 17import java.util.function.Consumer;
17 18
18public class ModelSeed { 19public class ModelSeed {
@@ -43,6 +44,10 @@ public class ModelSeed {
43 return seeds.containsKey(symbol); 44 return seeds.containsKey(symbol);
44 } 45 }
45 46
47 public Set<AnyPartialSymbol> getSeededSymbols() {
48 return Collections.unmodifiableSet(seeds.keySet());
49 }
50
46 public <A> Cursor<Tuple, A> getCursor(PartialSymbol<A, ?> partialSymbol, A defaultValue) { 51 public <A> Cursor<Tuple, A> getCursor(PartialSymbol<A, ?> partialSymbol, A defaultValue) {
47 return getSeed(partialSymbol).getCursor(defaultValue, nodeCount); 52 return getSeed(partialSymbol).getCursor(defaultValue, nodeCount);
48 } 53 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java
new file mode 100644
index 00000000..edb886ba
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
9
10public class TranslationException extends RuntimeException {
11 private final transient AnyPartialSymbol partialSymbol;
12
13 public TranslationException(AnyPartialSymbol partialSymbol) {
14 this.partialSymbol = partialSymbol;
15 }
16
17 public TranslationException(AnyPartialSymbol partialSymbol, String message) {
18 super(message);
19 this.partialSymbol = partialSymbol;
20 }
21
22 public TranslationException(AnyPartialSymbol partialSymbol, String message, Throwable cause) {
23 super(message, cause);
24 this.partialSymbol = partialSymbol;
25 }
26
27 public TranslationException(AnyPartialSymbol partialSymbol, Throwable cause) {
28 super(cause);
29 this.partialSymbol = partialSymbol;
30 }
31
32 public AnyPartialSymbol getPartialSymbol() {
33 return partialSymbol;
34 }
35}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java
index 1087e54d..e3457fa7 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java
@@ -6,17 +6,18 @@
6package tools.refinery.store.reasoning.translator.containment; 6package tools.refinery.store.reasoning.translator.containment;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 10import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10 11
11public record ContainmentInfo(PartialRelation sourceType, Multiplicity multiplicity, 12public record ContainmentInfo(PartialRelation sourceType, Multiplicity multiplicity,
12 PartialRelation targetType) { 13 PartialRelation targetType) {
13 public ContainmentInfo { 14 public ContainmentInfo {
14 if (sourceType.arity() != 1) { 15 if (sourceType.arity() != 1) {
15 throw new IllegalArgumentException("Expected source type %s to be of arity 1, got %d instead" 16 throw new TranslationException(sourceType, "Expected source type %s to be of arity 1, got %d instead"
16 .formatted(sourceType, sourceType.arity())); 17 .formatted(sourceType, sourceType.arity()));
17 } 18 }
18 if (targetType.arity() != 1) { 19 if (targetType.arity() != 1) {
19 throw new IllegalArgumentException("Expected target type %s to be of arity 1, got %d instead" 20 throw new TranslationException(targetType, "Expected target type %s to be of arity 1, got %d instead"
20 .formatted(targetType, targetType.arity())); 21 .formatted(targetType, targetType.arity()));
21 } 22 }
22 } 23 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java
index cc43bce6..a21da3d4 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.reasoning.translator.metamodel; 6package tools.refinery.store.reasoning.translator.metamodel;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; 10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
10import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyBuilder; 11import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyBuilder;
11 12
@@ -23,7 +24,7 @@ public class ContainedTypeHierarchyBuilder extends TypeHierarchyBuilder {
23 for (var containedType : containedTypes) { 24 for (var containedType : containedTypes) {
24 var currentInfo = typeInfoMap.get(containedType); 25 var currentInfo = typeInfoMap.get(containedType);
25 if (currentInfo == null) { 26 if (currentInfo == null) {
26 throw new IllegalArgumentException("Invalid contained type: " + containedType); 27 throw new TranslationException(containedType, "Invalid contained type: " + containedType);
27 } 28 }
28 var newInfo = currentInfo.addSupertype(ContainmentHierarchyTranslator.CONTAINED_SYMBOL); 29 var newInfo = currentInfo.addSupertype(ContainmentHierarchyTranslator.CONTAINED_SYMBOL);
29 typeInfoMap.put(containedType, newInfo); 30 typeInfoMap.put(containedType, newInfo);
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
index d0732edc..ad0288ed 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.reasoning.translator.metamodel; 6package tools.refinery.store.reasoning.translator.metamodel;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; 10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
10import tools.refinery.store.reasoning.translator.containment.ContainmentInfo; 11import tools.refinery.store.reasoning.translator.containment.ContainmentInfo;
11import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo; 12import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo;
@@ -13,7 +14,6 @@ import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossR
13import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; 14import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
14import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity; 15import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
15import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; 16import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo;
16import tools.refinery.store.representation.cardinality.CardinalityIntervals;
17 17
18import java.util.*; 18import java.util.*;
19 19
@@ -70,12 +70,13 @@ public class MetamodelBuilder {
70 70
71 public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) { 71 public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) {
72 if (linkType.arity() != 2) { 72 if (linkType.arity() != 2) {
73 throw new IllegalArgumentException("Only references of arity 2 are supported, got %s with %d instead" 73 throw new TranslationException(linkType,
74 .formatted(linkType, linkType.arity())); 74 "Only references of arity 2 are supported, got %s with %d instead".formatted(
75 linkType, linkType.arity()));
75 } 76 }
76 var putResult = referenceInfoMap.put(linkType, info); 77 var putResult = referenceInfoMap.put(linkType, info);
77 if (putResult != null && !putResult.equals(info)) { 78 if (putResult != null && !putResult.equals(info)) {
78 throw new IllegalArgumentException("Duplicate reference info for partial relation: " + linkType); 79 throw new TranslationException(linkType, "Duplicate reference info for partial relation: " + linkType);
79 } 80 }
80 return this; 81 return this;
81 } 82 }
@@ -154,11 +155,11 @@ public class MetamodelBuilder {
154 var sourceType = info.sourceType(); 155 var sourceType = info.sourceType();
155 var targetType = info.targetType(); 156 var targetType = info.targetType();
156 if (typeHierarchyBuilder.isInvalidType(sourceType)) { 157 if (typeHierarchyBuilder.isInvalidType(sourceType)) {
157 throw new IllegalArgumentException("Source type %s of %s is not in type hierarchy" 158 throw new TranslationException(linkType, "Source type %s of %s is not in type hierarchy"
158 .formatted(sourceType, linkType)); 159 .formatted(sourceType, linkType));
159 } 160 }
160 if (typeHierarchyBuilder.isInvalidType(targetType)) { 161 if (typeHierarchyBuilder.isInvalidType(targetType)) {
161 throw new IllegalArgumentException("Target type %s of %s is not in type hierarchy" 162 throw new TranslationException(linkType, "Target type %s of %s is not in type hierarchy"
162 .formatted(targetType, linkType)); 163 .formatted(targetType, linkType));
163 } 164 }
164 var opposite = info.opposite(); 165 var opposite = info.opposite();
@@ -173,8 +174,9 @@ public class MetamodelBuilder {
173 } 174 }
174 if (opposite.equals(linkType)) { 175 if (opposite.equals(linkType)) {
175 if (!sourceType.equals(targetType)) { 176 if (!sourceType.equals(targetType)) {
176 throw new IllegalArgumentException("Target %s of undirected reference %s differs from source %s" 177 throw new TranslationException(linkType,
177 .formatted(targetType, linkType, sourceType)); 178 "Target %s of undirected reference %s differs from source %s".formatted(
179 targetType, linkType, sourceType));
178 } 180 }
179 undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, 181 undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType,
180 info.multiplicity())); 182 info.multiplicity()));
@@ -183,8 +185,8 @@ public class MetamodelBuilder {
183 oppositeReferences.put(opposite, linkType); 185 oppositeReferences.put(opposite, linkType);
184 } 186 }
185 if (info.containment()) { 187 if (info.containment()) {
186 if (targetMultiplicity.multiplicity().meet(CardinalityIntervals.ONE).isEmpty()) { 188 if (!UnconstrainedMultiplicity.INSTANCE.equals(targetMultiplicity)) {
187 throw new IllegalArgumentException("Invalid opposite %s with multiplicity %s of containment %s" 189 throw new TranslationException(opposite, "Invalid opposite %s with multiplicity %s of containment %s"
188 .formatted(opposite, targetMultiplicity, linkType)); 190 .formatted(opposite, targetMultiplicity, linkType));
189 } 191 }
190 containedTypes.add(targetType); 192 containedTypes.add(targetType);
@@ -200,23 +202,23 @@ public class MetamodelBuilder {
200 var sourceType = info.sourceType(); 202 var sourceType = info.sourceType();
201 var targetType = info.targetType(); 203 var targetType = info.targetType();
202 if (oppositeInfo == null) { 204 if (oppositeInfo == null) {
203 throw new IllegalArgumentException("Opposite %s of %s is not defined" 205 throw new TranslationException(linkType, "Opposite %s of %s is not defined"
204 .formatted(opposite, linkType)); 206 .formatted(opposite, linkType));
205 } 207 }
206 if (!linkType.equals(oppositeInfo.opposite())) { 208 if (!linkType.equals(oppositeInfo.opposite())) {
207 throw new IllegalArgumentException("Expected %s to have opposite %s, got %s instead" 209 throw new TranslationException(opposite, "Expected %s to have opposite %s, got %s instead"
208 .formatted(opposite, linkType, oppositeInfo.opposite())); 210 .formatted(opposite, linkType, oppositeInfo.opposite()));
209 } 211 }
210 if (!targetType.equals(oppositeInfo.sourceType())) { 212 if (!targetType.equals(oppositeInfo.sourceType())) {
211 throw new IllegalArgumentException("Expected %s to have source type %s, got %s instead" 213 throw new TranslationException(linkType, "Expected %s to have source type %s, got %s instead"
212 .formatted(opposite, targetType, oppositeInfo.sourceType())); 214 .formatted(opposite, targetType, oppositeInfo.sourceType()));
213 } 215 }
214 if (!sourceType.equals(oppositeInfo.targetType())) { 216 if (!sourceType.equals(oppositeInfo.targetType())) {
215 throw new IllegalArgumentException("Expected %s to have target type %s, got %s instead" 217 throw new TranslationException(linkType, "Expected %s to have target type %s, got %s instead"
216 .formatted(opposite, sourceType, oppositeInfo.targetType())); 218 .formatted(opposite, sourceType, oppositeInfo.targetType()));
217 } 219 }
218 if (oppositeInfo.containment() && info.containment()) { 220 if (oppositeInfo.containment() && info.containment()) {
219 throw new IllegalArgumentException("Opposite %s of containment %s cannot be containment" 221 throw new TranslationException(opposite, "Opposite %s of containment %s cannot be containment"
220 .formatted(opposite, linkType)); 222 .formatted(opposite, linkType));
221 } 223 }
222 } 224 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java
index fb84631d..f11ab46b 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java
@@ -10,6 +10,7 @@ import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.ReasoningAdapter; 10import tools.refinery.store.reasoning.ReasoningAdapter;
11import tools.refinery.store.reasoning.refinement.PartialModelInitializer; 11import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
12import tools.refinery.store.reasoning.seed.ModelSeed; 12import tools.refinery.store.reasoning.seed.ModelSeed;
13import tools.refinery.store.reasoning.translator.TranslationException;
13import tools.refinery.store.representation.Symbol; 14import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue; 15import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.representation.cardinality.CardinalityInterval; 16import tools.refinery.store.representation.cardinality.CardinalityInterval;
@@ -37,7 +38,8 @@ class MultiObjectInitializer implements PartialModelInitializer {
37 for (int i = 0; i < intervals.length; i++) { 38 for (int i = 0; i < intervals.length; i++) {
38 var interval = intervals[i]; 39 var interval = intervals[i];
39 if (interval.isEmpty()) { 40 if (interval.isEmpty()) {
40 throw new IllegalArgumentException("Inconsistent existence or equality for node " + i); 41 throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL,
42 "Inconsistent existence or equality for node " + i);
41 } 43 }
42 var uniqueInterval = uniqueTable.computeIfAbsent(intervals[i], Function.identity()); 44 var uniqueInterval = uniqueTable.computeIfAbsent(intervals[i], Function.identity());
43 countInterpretation.put(Tuple.of(i), uniqueInterval); 45 countInterpretation.put(Tuple.of(i), uniqueInterval);
@@ -58,9 +60,10 @@ class MultiObjectInitializer implements PartialModelInitializer {
58 } else { 60 } else {
59 Arrays.fill(intervals, CardinalityIntervals.SET); 61 Arrays.fill(intervals, CardinalityIntervals.SET);
60 if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL) || 62 if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL) ||
61 !modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) { 63 !modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) {
62 throw new IllegalArgumentException("Seed for %s and %s is required if there is no seed for %s" 64 throw new TranslationException(MultiObjectTranslator.COUNT_SYMBOL,
63 .formatted(ReasoningAdapter.EXISTS_SYMBOL, ReasoningAdapter.EQUALS_SYMBOL, 65 "Seed for %s and %s is required if there is no seed for %s".formatted(
66 ReasoningAdapter.EXISTS_SYMBOL, ReasoningAdapter.EQUALS_SYMBOL,
64 MultiObjectTranslator.COUNT_SYMBOL)); 67 MultiObjectTranslator.COUNT_SYMBOL));
65 } 68 }
66 } 69 }
@@ -78,9 +81,10 @@ class MultiObjectInitializer implements PartialModelInitializer {
78 switch (cursor.getValue()) { 81 switch (cursor.getValue()) {
79 case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.SOME); 82 case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.SOME);
80 case FALSE -> intervals[i] = intervals[i].meet(CardinalityIntervals.NONE); 83 case FALSE -> intervals[i] = intervals[i].meet(CardinalityIntervals.NONE);
81 case ERROR -> throw new IllegalArgumentException("Inconsistent existence for node " + i); 84 case ERROR -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL,
82 default -> throw new IllegalArgumentException("Invalid existence truth value %s for node %d" 85 "Inconsistent existence for node " + i);
83 .formatted(cursor.getValue(), i)); 86 default -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL,
87 "Invalid existence truth value %s for node %d".formatted(cursor.getValue(), i));
84 } 88 }
85 } 89 }
86 } 90 }
@@ -96,8 +100,8 @@ class MultiObjectInitializer implements PartialModelInitializer {
96 int i = key.get(0); 100 int i = key.get(0);
97 int otherIndex = key.get(1); 101 int otherIndex = key.get(1);
98 if (i != otherIndex) { 102 if (i != otherIndex) {
99 throw new IllegalArgumentException("Off-diagonal equivalence (%d, %d) is not permitted" 103 throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL,
100 .formatted(i, otherIndex)); 104 "Off-diagonal equivalence (%d, %d) is not permitted".formatted(i, otherIndex));
101 } 105 }
102 checkNodeId(intervals, i); 106 checkNodeId(intervals, i);
103 switch (cursor.getValue()) { 107 switch (cursor.getValue()) {
@@ -105,14 +109,15 @@ class MultiObjectInitializer implements PartialModelInitializer {
105 case UNKNOWN -> { 109 case UNKNOWN -> {
106 // Nothing do to, {@code intervals} is initialized with unknown equality. 110 // Nothing do to, {@code intervals} is initialized with unknown equality.
107 } 111 }
108 case ERROR -> throw new IllegalArgumentException("Inconsistent equality for node " + i); 112 case ERROR -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL,
109 default -> throw new IllegalArgumentException("Invalid equality truth value %s for node %d" 113 "Inconsistent equality for node " + i);
110 .formatted(cursor.getValue(), i)); 114 default -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL,
115 "Invalid equality truth value %s for node %d".formatted(cursor.getValue(), i));
111 } 116 }
112 } 117 }
113 for (int i = 0; i < intervals.length; i++) { 118 for (int i = 0; i < intervals.length; i++) {
114 if (seed.get(Tuple.of(i, i)) == TruthValue.FALSE) { 119 if (seed.get(Tuple.of(i, i)) == TruthValue.FALSE) {
115 throw new IllegalArgumentException("Inconsistent equality for node " + i); 120 throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Inconsistent equality for node " + i);
116 } 121 }
117 } 122 }
118 } 123 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java
index e441e41e..9db9cc96 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.reasoning.translator.multiplicity; 6package tools.refinery.store.reasoning.translator.multiplicity;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9import tools.refinery.store.representation.cardinality.CardinalityInterval; 10import tools.refinery.store.representation.cardinality.CardinalityInterval;
10import tools.refinery.store.representation.cardinality.CardinalityIntervals; 11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
11import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval; 12import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval;
@@ -14,17 +15,17 @@ public record ConstrainedMultiplicity(NonEmptyCardinalityInterval multiplicity,
14 implements Multiplicity { 15 implements Multiplicity {
15 public ConstrainedMultiplicity { 16 public ConstrainedMultiplicity {
16 if (multiplicity.equals(CardinalityIntervals.SET)) { 17 if (multiplicity.equals(CardinalityIntervals.SET)) {
17 throw new IllegalArgumentException("Expected a constrained cardinality interval"); 18 throw new TranslationException(errorSymbol, "Expected a constrained cardinality interval");
18 } 19 }
19 if (errorSymbol.arity() != 1) { 20 if (errorSymbol.arity() != 1) {
20 throw new IllegalArgumentException("Expected error symbol %s to have arity 1, got %d instead" 21 throw new TranslationException(errorSymbol, "Expected error symbol %s to have arity 1, got %d instead"
21 .formatted(errorSymbol, errorSymbol.arity())); 22 .formatted(errorSymbol, errorSymbol.arity()));
22 } 23 }
23 } 24 }
24 25
25 public static ConstrainedMultiplicity of(CardinalityInterval multiplicity, PartialRelation errorSymbol) { 26 public static ConstrainedMultiplicity of(CardinalityInterval multiplicity, PartialRelation errorSymbol) {
26 if (!(multiplicity instanceof NonEmptyCardinalityInterval nonEmptyCardinalityInterval)) { 27 if (!(multiplicity instanceof NonEmptyCardinalityInterval nonEmptyCardinalityInterval)) {
27 throw new IllegalArgumentException("Inconsistent multiplicity"); 28 throw new TranslationException(errorSymbol, "Inconsistent multiplicity");
28 } 29 }
29 return new ConstrainedMultiplicity(nonEmptyCardinalityInterval, errorSymbol); 30 return new ConstrainedMultiplicity(nonEmptyCardinalityInterval, errorSymbol);
30 } 31 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java
index 522d8455..c5e5e83e 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java
@@ -14,6 +14,7 @@ import tools.refinery.store.reasoning.lifting.DnfLifter;
14import tools.refinery.store.reasoning.literal.*; 14import tools.refinery.store.reasoning.literal.*;
15import tools.refinery.store.reasoning.representation.PartialRelation; 15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.translator.PartialRelationTranslator; 16import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
17import tools.refinery.store.reasoning.translator.TranslationException;
17import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; 18import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
18import tools.refinery.store.representation.cardinality.UpperCardinalities; 19import tools.refinery.store.representation.cardinality.UpperCardinalities;
19import tools.refinery.store.representation.cardinality.UpperCardinality; 20import tools.refinery.store.representation.cardinality.UpperCardinality;
@@ -36,11 +37,11 @@ public class InvalidMultiplicityErrorTranslator implements ModelStoreConfigurati
36 public InvalidMultiplicityErrorTranslator(PartialRelation nodeType, PartialRelation linkType, 37 public InvalidMultiplicityErrorTranslator(PartialRelation nodeType, PartialRelation linkType,
37 boolean inverse, Multiplicity multiplicity) { 38 boolean inverse, Multiplicity multiplicity) {
38 if (nodeType.arity() != 1) { 39 if (nodeType.arity() != 1) {
39 throw new IllegalArgumentException("Node type must be of arity 1, got %s with arity %d instead" 40 throw new TranslationException(linkType, "Node type must be of arity 1, got %s with arity %d instead"
40 .formatted(nodeType, nodeType.arity())); 41 .formatted(nodeType, nodeType.arity()));
41 } 42 }
42 if (linkType.arity() != 2) { 43 if (linkType.arity() != 2) {
43 throw new IllegalArgumentException("Link type must be of arity 2, got %s with arity %d instead" 44 throw new TranslationException(linkType, "Link type must be of arity 2, got %s with arity %d instead"
44 .formatted(linkType, linkType.arity())); 45 .formatted(linkType, linkType.arity()));
45 } 46 }
46 this.nodeType = nodeType; 47 this.nodeType = nodeType;
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java
index b25b9d7d..6e15a628 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java
@@ -17,6 +17,7 @@ import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; 17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
18import tools.refinery.store.reasoning.representation.PartialRelation; 18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator; 19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
20 21
21import java.util.List; 22import java.util.List;
22import java.util.Set; 23import java.util.Set;
@@ -26,6 +27,16 @@ public class OppositeRelationTranslator implements ModelStoreConfiguration, Part
26 private final PartialRelation opposite; 27 private final PartialRelation opposite;
27 28
28 public OppositeRelationTranslator(PartialRelation linkType, PartialRelation opposite) { 29 public OppositeRelationTranslator(PartialRelation linkType, PartialRelation opposite) {
30 if (linkType.arity() != 2) {
31 throw new TranslationException(linkType,
32 "Expected relation with opposite %s to have arity 2, got %d instead"
33 .formatted(linkType, linkType.arity()));
34 }
35 if (opposite.arity() != 2) {
36 throw new TranslationException(linkType,
37 "Expected opposite %s of %s to have arity 2, got %d instead"
38 .formatted(opposite, linkType, opposite.arity()));
39 }
29 this.linkType = linkType; 40 this.linkType = linkType;
30 this.opposite = opposite; 41 this.opposite = opposite;
31 } 42 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java
index ee022f2d..16745da1 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java
@@ -17,6 +17,7 @@ import tools.refinery.store.query.view.MayView;
17import tools.refinery.store.query.view.MustView; 17import tools.refinery.store.query.view.MustView;
18import tools.refinery.store.reasoning.representation.PartialRelation; 18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator; 19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
20import tools.refinery.store.representation.Symbol; 21import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.representation.TruthValue; 22import tools.refinery.store.representation.TruthValue;
22 23
@@ -33,11 +34,11 @@ public class PredicateTranslator implements ModelStoreConfiguration {
33 public PredicateTranslator(PartialRelation relation, RelationalQuery query, boolean mutable, 34 public PredicateTranslator(PartialRelation relation, RelationalQuery query, boolean mutable,
34 TruthValue defaultValue) { 35 TruthValue defaultValue) {
35 if (relation.arity() != query.arity()) { 36 if (relation.arity() != query.arity()) {
36 throw new IllegalArgumentException("Expected arity %d query for partial relation %s, got %d instead" 37 throw new TranslationException(relation, "Expected arity %d query for partial relation %s, got %d instead"
37 .formatted(relation.arity(), relation, query.arity())); 38 .formatted(relation.arity(), relation, query.arity()));
38 } 39 }
39 if (defaultValue.must()) { 40 if (defaultValue.must()) {
40 throw new IllegalArgumentException("Default value must be UNKNOWN or FALSE"); 41 throw new TranslationException(relation, "Default value must be UNKNOWN or FALSE");
41 } 42 }
42 this.relation = relation; 43 this.relation = relation;
43 this.query = query; 44 this.query = query;
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java
index 35ec54ad..3f918c97 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9 10
10import java.util.*; 11import java.util.*;
11 12
@@ -81,8 +82,9 @@ public class TypeHierarchy {
81 for (var supertype : allSupertypes) { 82 for (var supertype : allSupertypes) {
82 var supertypeInfo = extendedTypeInfoMap.get(supertype); 83 var supertypeInfo = extendedTypeInfoMap.get(supertype);
83 if (supertypeInfo == null) { 84 if (supertypeInfo == null) {
84 throw new IllegalArgumentException("Supertype %s of %s is missing from the type hierarchy" 85 throw new TranslationException(extendedTypeInfo.getType(),
85 .formatted(supertype, extendedTypeInfo.getType())); 86 "Supertype %s of %s is missing from the type hierarchy"
87 .formatted(supertype, extendedTypeInfo.getType()));
86 } 88 }
87 found.addAll(supertypeInfo.getAllSupertypes()); 89 found.addAll(supertypeInfo.getAllSupertypes());
88 } 90 }
@@ -101,7 +103,7 @@ public class TypeHierarchy {
101 } 103 }
102 for (var supertype : extendedTypeInfo.getAllSupertypes()) { 104 for (var supertype : extendedTypeInfo.getAllSupertypes()) {
103 if (type.equals(supertype)) { 105 if (type.equals(supertype)) {
104 throw new IllegalArgumentException("%s cannot be a supertype of itself".formatted(type)); 106 throw new TranslationException(type, "%s cannot be a supertype of itself".formatted(type));
105 } 107 }
106 var supertypeInfo = extendedTypeInfoMap.get(supertype); 108 var supertypeInfo = extendedTypeInfoMap.get(supertype);
107 supertypeInfo.getAllSubtypes().add(type); 109 supertypeInfo.getAllSubtypes().add(type);
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java
index 36efb878..ce8fda05 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9 10
10import java.util.*; 11import java.util.*;
11 12
@@ -18,12 +19,14 @@ public class TypeHierarchyBuilder {
18 19
19 public TypeHierarchyBuilder type(PartialRelation partialRelation, TypeInfo typeInfo) { 20 public TypeHierarchyBuilder type(PartialRelation partialRelation, TypeInfo typeInfo) {
20 if (partialRelation.arity() != 1) { 21 if (partialRelation.arity() != 1) {
21 throw new IllegalArgumentException("Only types of arity 1 are supported, got %s with %d instead" 22 throw new TranslationException(partialRelation,
22 .formatted(partialRelation, partialRelation.arity())); 23 "Only types of arity 1 are supported, got %s with %d instead"
24 .formatted(partialRelation, partialRelation.arity()));
23 } 25 }
24 var putResult = typeInfoMap.put(partialRelation, typeInfo); 26 var putResult = typeInfoMap.put(partialRelation, typeInfo);
25 if (putResult != null && !putResult.equals(typeInfo)) { 27 if (putResult != null && !putResult.equals(typeInfo)) {
26 throw new IllegalArgumentException("Duplicate type info for partial relation: " + partialRelation); 28 throw new TranslationException(partialRelation,
29 "Duplicate type info for partial relation: " + partialRelation);
27 } 30 }
28 return this; 31 return this;
29 } 32 }
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
index 115ba8cd..0f1a1006 100644
--- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
@@ -7,6 +7,7 @@ package tools.refinery.store.reasoning.translator.metamodel;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation; 9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; 11import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
11import tools.refinery.store.representation.cardinality.CardinalityIntervals; 12import tools.refinery.store.representation.cardinality.CardinalityIntervals;
12 13
@@ -26,7 +27,7 @@ class MetamodelBuilderTest {
26 .reference(courses, university, course, location) 27 .reference(courses, university, course, location)
27 .reference(location, course, university); 28 .reference(location, course, university);
28 29
29 assertThrows(IllegalArgumentException.class, builder::build); 30 assertThrows(TranslationException.class, builder::build);
30 } 31 }
31 32
32 @Test 33 @Test
@@ -37,7 +38,7 @@ class MetamodelBuilderTest {
37 .reference(courses, university, course, location) 38 .reference(courses, university, course, location)
38 .reference(location, course, course, courses); 39 .reference(location, course, course, courses);
39 40
40 assertThrows(IllegalArgumentException.class, builder::build); 41 assertThrows(TranslationException.class, builder::build);
41 } 42 }
42 43
43 @Test 44 @Test
@@ -52,6 +53,6 @@ class MetamodelBuilderTest {
52 ConstrainedMultiplicity.of(CardinalityIntervals.atLeast(2), invalidMultiplicity), 53 ConstrainedMultiplicity.of(CardinalityIntervals.atLeast(2), invalidMultiplicity),
53 university, courses); 54 university, courses);
54 55
55 assertThrows(IllegalArgumentException.class, builder::build); 56 assertThrows(TranslationException.class, builder::build);
56 } 57 }
57} 58}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java
index 9fbf2334..931c62dd 100644
--- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java
@@ -8,6 +8,7 @@ package tools.refinery.store.reasoning.translator.typehierarchy;
8import org.hamcrest.Matchers; 8import org.hamcrest.Matchers;
9import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
10import tools.refinery.store.reasoning.representation.PartialRelation; 10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.translator.TranslationException;
11import tools.refinery.store.representation.TruthValue; 12import tools.refinery.store.representation.TruthValue;
12 13
13import java.util.Set; 14import java.util.Set;
@@ -200,7 +201,7 @@ class TypeHierarchyTest {
200 .type(c1, c2) 201 .type(c1, c2)
201 .type(c2, c1); 202 .type(c2, c1);
202 203
203 assertThrows(IllegalArgumentException.class, builder::build); 204 assertThrows(TranslationException.class, builder::build);
204 } 205 }
205 206
206 @Test 207 @Test