diff options
author | Kristóf Marussy <kristof@marussy.com> | 2023-08-20 19:41:32 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2023-08-20 20:29:02 +0200 |
commit | a3f1e6872f4f768d14899a1e70bbdc14f32e478d (patch) | |
tree | b2daf0c81724f31ee190f5d63eb42988086dabf2 | |
parent | fix: nullary model initialization (diff) | |
download | refinery-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.
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 | |||
7 | import { reaction } from 'mobx'; | ||
8 | import { type SnackbarKey, useSnackbar } from 'notistack'; | ||
9 | import { useEffect, useState } from 'react'; | ||
10 | |||
11 | import type EditorStore from './EditorStore'; | ||
12 | |||
13 | function 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 | |||
40 | export 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 | ||
7 | import type { Diagnostic } from '@codemirror/lint'; | 7 | import type { Diagnostic } from '@codemirror/lint'; |
8 | import CancelIcon from '@mui/icons-material/Cancel'; | ||
8 | import CheckIcon from '@mui/icons-material/Check'; | 9 | import CheckIcon from '@mui/icons-material/Check'; |
9 | import ErrorIcon from '@mui/icons-material/Error'; | ||
10 | import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; | 10 | import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; |
11 | import FormatPaint from '@mui/icons-material/FormatPaint'; | 11 | import FormatPaint from '@mui/icons-material/FormatPaint'; |
12 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; | 12 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; |
@@ -28,7 +28,7 @@ import type EditorStore from './EditorStore'; | |||
28 | function getLintIcon(severity: Diagnostic['severity'] | undefined) { | 28 | function 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 | |||
7 | import { Diagnostic } from '@codemirror/lint'; | ||
8 | import { type IReactionDisposer, makeAutoObservable, reaction } from 'mobx'; | ||
9 | |||
10 | import type EditorStore from './EditorStore'; | ||
11 | |||
12 | const HYSTERESIS_TIME_MS = 250; | ||
13 | |||
14 | export interface State { | ||
15 | analyzing: boolean; | ||
16 | errorCount: number; | ||
17 | warningCount: number; | ||
18 | infoCount: number; | ||
19 | semanticsError: string | undefined; | ||
20 | } | ||
21 | |||
22 | export 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 | ||
14 | import { useRootStore } from '../RootStoreProvider'; | 14 | import { useRootStore } from '../RootStoreProvider'; |
15 | 15 | ||
16 | import AnalysisErrorNotification from './AnalysisErrorNotification'; | ||
16 | import ConnectionStatusNotification from './ConnectionStatusNotification'; | 17 | import ConnectionStatusNotification from './ConnectionStatusNotification'; |
17 | import EditorArea from './EditorArea'; | 18 | import EditorArea from './EditorArea'; |
18 | import EditorButtons from './EditorButtons'; | 19 | import 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'; | |||
29 | import getLogger from '../utils/getLogger'; | 29 | import getLogger from '../utils/getLogger'; |
30 | import type XtextClient from '../xtext/XtextClient'; | 30 | import type XtextClient from '../xtext/XtextClient'; |
31 | 31 | ||
32 | import EditorErrors from './EditorErrors'; | ||
32 | import LintPanelStore from './LintPanelStore'; | 33 | import LintPanelStore from './LintPanelStore'; |
33 | import SearchPanelStore from './SearchPanelStore'; | 34 | import SearchPanelStore from './SearchPanelStore'; |
34 | import createEditorState from './createEditorState'; | 35 | import 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 | ||
7 | import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; | 7 | import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; |
8 | import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; | 8 | import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; |
9 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; | 9 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; |
10 | import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; | 10 | import 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 | ||
7 | import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; | 7 | import CancelIcon from '@mui/icons-material/Cancel'; |
8 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; | 8 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; |
9 | import Button from '@mui/material/Button'; | ||
10 | import type { SxProps, Theme } from '@mui/material/styles'; | ||
11 | import { observer } from 'mobx-react-lite'; | 9 | import { observer } from 'mobx-react-lite'; |
12 | 10 | ||
13 | import AnimatedButton from './AnimatedButton'; | 11 | import AnimatedButton from './AnimatedButton'; |
@@ -18,26 +16,45 @@ const GENERATE_LABEL = 'Generate'; | |||
18 | const GenerateButton = observer(function GenerateButton({ | 16 | const 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… | 26 | Loading… |
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… | ||
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 | ||
77 | GenerateButton.defaultProps = { | 92 | GenerateButton.defaultProps = { |
78 | hideWarnings: false, | 93 | hideWarnings: false, |
79 | sx: undefined, | ||
80 | }; | 94 | }; |
81 | 95 | ||
82 | export default GenerateButton; | 96 | export 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 | |||
7 | import type EditorStore from '../editor/EditorStore'; | ||
8 | |||
9 | import type ValidationService from './ValidationService'; | ||
10 | import { SemanticsResult } from './xtextServiceResults'; | ||
11 | |||
12 | export 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'; | |||
9 | import type EditorStore from '../editor/EditorStore'; | 9 | import type EditorStore from '../editor/EditorStore'; |
10 | 10 | ||
11 | import type UpdateService from './UpdateService'; | 11 | import type UpdateService from './UpdateService'; |
12 | import { ValidationResult } from './xtextServiceResults'; | 12 | import { Issue, ValidationResult } from './xtextServiceResults'; |
13 | 13 | ||
14 | export default class ValidationService { | 14 | export 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'; | |||
17 | import ContentAssistService from './ContentAssistService'; | 17 | import ContentAssistService from './ContentAssistService'; |
18 | import HighlightingService from './HighlightingService'; | 18 | import HighlightingService from './HighlightingService'; |
19 | import OccurrencesService from './OccurrencesService'; | 19 | import OccurrencesService from './OccurrencesService'; |
20 | import SemanticsService from './SemanticsService'; | ||
20 | import UpdateService from './UpdateService'; | 21 | import UpdateService from './UpdateService'; |
21 | import ValidationService from './ValidationService'; | 22 | import ValidationService from './ValidationService'; |
22 | import XtextWebSocketClient from './XtextWebSocketClient'; | 23 | import 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 | ||
127 | export type FormattingResult = z.infer<typeof FormattingResult>; | 127 | export type FormattingResult = z.infer<typeof FormattingResult>; |
128 | |||
129 | export const SemanticsResult = z.object({ | ||
130 | error: z.string().optional(), | ||
131 | issues: Issue.array().optional(), | ||
132 | }); | ||
133 | |||
134 | export 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; | |||
15 | import tools.refinery.language.utils.ProblemUtil; | 15 | import tools.refinery.language.utils.ProblemUtil; |
16 | import tools.refinery.store.model.ModelStoreBuilder; | 16 | import tools.refinery.store.model.ModelStoreBuilder; |
17 | import tools.refinery.store.query.Constraint; | 17 | import tools.refinery.store.query.Constraint; |
18 | import tools.refinery.store.query.dnf.InvalidClauseException; | ||
18 | import tools.refinery.store.query.dnf.Query; | 19 | import tools.refinery.store.query.dnf.Query; |
19 | import tools.refinery.store.query.dnf.RelationalQuery; | 20 | import tools.refinery.store.query.dnf.RelationalQuery; |
20 | import tools.refinery.store.query.literal.*; | 21 | import tools.refinery.store.query.literal.*; |
21 | import tools.refinery.store.query.term.NodeVariable; | 22 | import tools.refinery.store.query.term.NodeVariable; |
22 | import tools.refinery.store.query.term.Variable; | 23 | import tools.refinery.store.query.term.Variable; |
23 | import tools.refinery.store.reasoning.ReasoningAdapter; | 24 | import tools.refinery.store.reasoning.ReasoningAdapter; |
25 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
24 | import tools.refinery.store.reasoning.representation.PartialRelation; | 26 | import tools.refinery.store.reasoning.representation.PartialRelation; |
25 | import tools.refinery.store.reasoning.seed.ModelSeed; | 27 | import tools.refinery.store.reasoning.seed.ModelSeed; |
26 | import tools.refinery.store.reasoning.seed.Seed; | 28 | import 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 | */ | ||
6 | package tools.refinery.language.semantics.model; | ||
7 | |||
8 | import org.eclipse.emf.ecore.EObject; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | import tools.refinery.store.map.AnyVersionedMap; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
12 | import tools.refinery.store.reasoning.seed.Seed; | ||
13 | import tools.refinery.store.tuple.Tuple; | ||
14 | import tools.refinery.viatra.runtime.CancellationToken; | ||
15 | |||
16 | import java.util.Set; | ||
17 | |||
18 | class 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 | */ |
6 | package tools.refinery.language.web.semantics; | 6 | package tools.refinery.language.web.semantics; |
7 | 7 | ||
8 | public record SemanticsErrorResult(String error) implements SemanticsResult { | 8 | public 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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | import org.eclipse.xtext.web.server.validation.ValidationResult; | ||
9 | |||
10 | import java.util.List; | ||
11 | |||
12 | public 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 | ||
8 | import org.eclipse.xtext.web.server.IServiceResult; | 8 | import org.eclipse.xtext.web.server.IServiceResult; |
9 | 9 | ||
10 | public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult, SemanticsErrorResult { | 10 | public 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 | */ |
6 | package tools.refinery.language.web.semantics; | 6 | package tools.refinery.language.web.semantics; |
7 | 7 | ||
8 | import com.google.gson.JsonObject; | ||
8 | import com.google.inject.Inject; | 9 | import com.google.inject.Inject; |
9 | import com.google.inject.Provider; | 10 | import com.google.inject.Provider; |
10 | import com.google.inject.Singleton; | 11 | import com.google.inject.Singleton; |
12 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
11 | import org.eclipse.xtext.service.OperationCanceledManager; | 13 | import org.eclipse.xtext.service.OperationCanceledManager; |
12 | import org.eclipse.xtext.util.CancelIndicator; | 14 | import org.eclipse.xtext.util.CancelIndicator; |
13 | import org.eclipse.xtext.web.server.model.AbstractCachedService; | 15 | import org.eclipse.xtext.web.server.model.AbstractCachedService; |
@@ -19,6 +21,7 @@ import org.slf4j.LoggerFactory; | |||
19 | import tools.refinery.language.model.problem.Problem; | 21 | import tools.refinery.language.model.problem.Problem; |
20 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; | 22 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; |
21 | 23 | ||
24 | import java.util.List; | ||
22 | import java.util.concurrent.*; | 25 | import 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; | |||
8 | import com.google.gson.JsonArray; | 8 | import com.google.gson.JsonArray; |
9 | import com.google.gson.JsonObject; | 9 | import com.google.gson.JsonObject; |
10 | import com.google.inject.Inject; | 10 | import com.google.inject.Inject; |
11 | import org.eclipse.emf.common.util.Diagnostic; | ||
12 | import org.eclipse.emf.ecore.EObject; | ||
11 | import org.eclipse.xtext.service.OperationCanceledManager; | 13 | import org.eclipse.xtext.service.OperationCanceledManager; |
12 | import org.eclipse.xtext.util.CancelIndicator; | 14 | import org.eclipse.xtext.util.CancelIndicator; |
13 | import org.slf4j.Logger; | 15 | import org.eclipse.xtext.validation.CheckType; |
14 | import org.slf4j.LoggerFactory; | 16 | import org.eclipse.xtext.validation.FeatureBasedDiagnostic; |
17 | import org.eclipse.xtext.validation.IDiagnosticConverter; | ||
18 | import org.eclipse.xtext.validation.Issue; | ||
19 | import org.eclipse.xtext.web.server.validation.ValidationResult; | ||
15 | import tools.refinery.language.model.problem.Problem; | 20 | import tools.refinery.language.model.problem.Problem; |
16 | import tools.refinery.language.semantics.model.ModelInitializer; | 21 | import tools.refinery.language.semantics.model.ModelInitializer; |
17 | import tools.refinery.language.semantics.model.SemanticsUtils; | 22 | import tools.refinery.language.semantics.model.SemanticsUtils; |
23 | import tools.refinery.language.semantics.model.TracedException; | ||
18 | import tools.refinery.store.model.Model; | 24 | import tools.refinery.store.model.Model; |
19 | import tools.refinery.store.model.ModelStore; | 25 | import tools.refinery.store.model.ModelStore; |
20 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | 26 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; |
@@ -22,17 +28,19 @@ import tools.refinery.store.reasoning.ReasoningAdapter; | |||
22 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | 28 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; |
23 | import tools.refinery.store.reasoning.literal.Concreteness; | 29 | import tools.refinery.store.reasoning.literal.Concreteness; |
24 | import tools.refinery.store.reasoning.representation.PartialRelation; | 30 | import tools.refinery.store.reasoning.representation.PartialRelation; |
31 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
25 | import tools.refinery.store.representation.TruthValue; | 32 | import tools.refinery.store.representation.TruthValue; |
26 | import tools.refinery.store.tuple.Tuple; | 33 | import tools.refinery.store.tuple.Tuple; |
27 | import tools.refinery.viatra.runtime.CancellationToken; | 34 | import tools.refinery.viatra.runtime.CancellationToken; |
28 | 35 | ||
36 | import java.util.ArrayList; | ||
29 | import java.util.Arrays; | 37 | import java.util.Arrays; |
30 | import java.util.List; | 38 | import java.util.List; |
31 | import java.util.TreeMap; | 39 | import java.util.TreeMap; |
32 | import java.util.concurrent.Callable; | 40 | import java.util.concurrent.Callable; |
33 | 41 | ||
34 | class SemanticsWorker implements Callable<SemanticsResult> { | 42 | class 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; | |||
18 | import org.junit.jupiter.api.extension.ExtendWith; | 18 | import org.junit.jupiter.api.extension.ExtendWith; |
19 | import org.mockito.ArgumentCaptor; | 19 | import org.mockito.ArgumentCaptor; |
20 | import org.mockito.junit.jupiter.MockitoExtension; | 20 | import org.mockito.junit.jupiter.MockitoExtension; |
21 | import tools.refinery.language.web.semantics.SemanticsService; | ||
21 | import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider; | 22 | import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider; |
22 | import tools.refinery.language.web.tests.ProblemWebInjectorProvider; | 23 | import tools.refinery.language.web.tests.ProblemWebInjectorProvider; |
23 | import tools.refinery.language.web.xtext.server.ResponseHandler; | 24 | import 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 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | public 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 | ||
8 | import org.jetbrains.annotations.NotNull; | 8 | import org.jetbrains.annotations.NotNull; |
9 | import tools.refinery.store.query.Constraint; | 9 | import tools.refinery.store.query.Constraint; |
10 | import tools.refinery.store.query.InvalidQueryException; | ||
10 | import tools.refinery.store.query.literal.*; | 11 | import tools.refinery.store.query.literal.*; |
11 | import tools.refinery.store.query.substitution.MapBasedSubstitution; | 12 | import tools.refinery.store.query.substitution.MapBasedSubstitution; |
12 | import tools.refinery.store.query.substitution.StatelessSubstitution; | 13 | import 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 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.DnfEqualityChecker; | 10 | import tools.refinery.store.query.equality.DnfEqualityChecker; |
10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 11 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
11 | import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper; | 12 | import 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 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.dnf.callback.*; | 9 | import tools.refinery.store.query.dnf.callback.*; |
9 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
10 | import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.literal.Literal; | 10 | import tools.refinery.store.query.literal.Literal; |
13 | import tools.refinery.store.query.term.*; | 11 | import 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
10 | import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper; | ||
11 | import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.ParameterDirection; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.*; | ||
17 | |||
18 | class 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 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | |||
8 | import java.util.HashSet; | 10 | import java.util.HashSet; |
9 | import java.util.Set; | 11 | import 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 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.literal.CallPolarity; | 9 | import tools.refinery.store.query.literal.CallPolarity; |
9 | import tools.refinery.store.query.term.Aggregator; | 10 | import tools.refinery.store.query.term.Aggregator; |
10 | import tools.refinery.store.query.term.AssignedValue; | 11 | import 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 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | |||
10 | public 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 | */ |
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.literal.CallLiteral; | 9 | import tools.refinery.store.query.literal.CallLiteral; |
9 | import tools.refinery.store.query.literal.CallPolarity; | 10 | import tools.refinery.store.query.literal.CallPolarity; |
10 | import tools.refinery.store.query.term.AssignedValue; | 11 | import 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 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
11 | import tools.refinery.store.query.substitution.Substitution; | 12 | import 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 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
11 | import tools.refinery.store.query.term.ConstantTerm; | 12 | import 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 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
11 | import tools.refinery.store.query.substitution.Substitution; | 12 | import 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 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
@@ -16,18 +17,20 @@ import java.util.Collections; | |||
16 | import java.util.Objects; | 17 | import java.util.Objects; |
17 | import java.util.Set; | 18 | import java.util.Set; |
18 | 19 | ||
20 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
21 | @SuppressWarnings("squid:S2160") | ||
19 | public class AssignLiteral<T> extends AbstractLiteral { | 22 | public 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 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
11 | import tools.refinery.store.query.substitution.Substitution; | 12 | import 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 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | |||
8 | public enum CallPolarity { | 10 | public 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 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 11 | import tools.refinery.store.query.substitution.Substitution; |
@@ -18,12 +19,14 @@ import java.util.Collections; | |||
18 | import java.util.Objects; | 19 | import java.util.Objects; |
19 | import java.util.Set; | 20 | import java.util.Set; |
20 | 21 | ||
22 | // {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}. | ||
23 | @SuppressWarnings("squid:S2160") | ||
21 | public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> { | 24 | public 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 | */ |
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 11 | import 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 @@ | |||
6 | package tools.refinery.store.query.literal; | 6 | package tools.refinery.store.query.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
11 | import tools.refinery.store.query.substitution.Substitution; | 12 | import 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 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import org.jetbrains.annotations.Nullable; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | 11 | ||
11 | import java.util.Optional; | 12 | import 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 | */ |
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 11 | import 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 | */ |
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 11 | import 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 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import org.jetbrains.annotations.Nullable; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 11 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
11 | import tools.refinery.store.query.literal.EquivalenceLiteral; | 12 | import 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 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import org.jetbrains.annotations.Nullable; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.literal.ConstantLiteral; | 10 | import tools.refinery.store.query.literal.ConstantLiteral; |
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | 11 | import 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 @@ | |||
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | public enum ParameterDirection { | 8 | public 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 | */ |
6 | package tools.refinery.store.query.term; | 6 | package tools.refinery.store.query.term; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
9 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; | 10 | import tools.refinery.store.query.equality.LiteralHashCodeHelper; |
10 | import tools.refinery.store.query.substitution.Substitution; | 11 | import 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 | */ |
6 | package tools.refinery.store.query.view; | 6 | package tools.refinery.store.query.view; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.tuple.Tuple; | 9 | import tools.refinery.store.tuple.Tuple; |
9 | import tools.refinery.store.representation.Symbol; | 10 | import 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 @@ | |||
6 | package tools.refinery.store.query.dnf; | 6 | package tools.refinery.store.query.dnf; |
7 | 7 | ||
8 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | 10 | import tools.refinery.store.query.term.NodeVariable; |
10 | import tools.refinery.store.query.term.ParameterDirection; | 11 | import tools.refinery.store.query.term.ParameterDirection; |
11 | import tools.refinery.store.query.term.Variable; | 12 | import 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 | ||
8 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
9 | import tools.refinery.store.query.Constraint; | 9 | import tools.refinery.store.query.Constraint; |
10 | import tools.refinery.store.query.InvalidQueryException; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | 11 | import tools.refinery.store.query.dnf.Dnf; |
12 | import tools.refinery.store.query.dnf.InvalidClauseException; | ||
11 | import tools.refinery.store.query.term.*; | 13 | import tools.refinery.store.query.term.*; |
12 | 14 | ||
13 | import java.util.List; | 15 | import java.util.List; |
14 | import java.util.Set; | 16 | import java.util.Set; |
15 | 17 | ||
16 | import static org.hamcrest.MatcherAssert.assertThat; | 18 | import static org.hamcrest.MatcherAssert.assertThat; |
17 | import static org.hamcrest.Matchers.containsInAnyOrder; | 19 | import static org.hamcrest.Matchers.*; |
18 | import static org.hamcrest.Matchers.empty; | ||
19 | import static org.junit.jupiter.api.Assertions.assertAll; | 20 | import static org.junit.jupiter.api.Assertions.assertAll; |
20 | import static org.junit.jupiter.api.Assertions.assertThrows; | 21 | import static org.junit.jupiter.api.Assertions.assertThrows; |
21 | import static tools.refinery.store.query.literal.Literals.not; | 22 | import 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 @@ | |||
6 | package tools.refinery.store.reasoning.literal; | 6 | package tools.refinery.store.reasoning.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.literal.Reduction; | 11 | import tools.refinery.store.query.literal.Reduction; |
11 | import tools.refinery.store.query.term.Parameter; | 12 | import 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 | */ |
6 | package tools.refinery.store.reasoning.literal; | 6 | package tools.refinery.store.reasoning.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.literal.CallLiteral; | 9 | import tools.refinery.store.query.literal.CallLiteral; |
9 | 10 | ||
10 | public final class PartialLiterals { | 11 | public 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; | |||
13 | import java.util.Collections; | 13 | import java.util.Collections; |
14 | import java.util.LinkedHashMap; | 14 | import java.util.LinkedHashMap; |
15 | import java.util.Map; | 15 | import java.util.Map; |
16 | import java.util.Set; | ||
16 | import java.util.function.Consumer; | 17 | import java.util.function.Consumer; |
17 | 18 | ||
18 | public class ModelSeed { | 19 | public 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 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
9 | |||
10 | public 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 @@ | |||
6 | package tools.refinery.store.reasoning.translator.containment; | 6 | package tools.refinery.store.reasoning.translator.containment; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 10 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
10 | 11 | ||
11 | public record ContainmentInfo(PartialRelation sourceType, Multiplicity multiplicity, | 12 | public 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 @@ | |||
6 | package tools.refinery.store.reasoning.translator.metamodel; | 6 | package tools.refinery.store.reasoning.translator.metamodel; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | 10 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; |
10 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyBuilder; | 11 | import 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 @@ | |||
6 | package tools.refinery.store.reasoning.translator.metamodel; | 6 | package tools.refinery.store.reasoning.translator.metamodel; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | 10 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; |
10 | import tools.refinery.store.reasoning.translator.containment.ContainmentInfo; | 11 | import tools.refinery.store.reasoning.translator.containment.ContainmentInfo; |
11 | import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo; | 12 | import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo; |
@@ -13,7 +14,6 @@ import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossR | |||
13 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 14 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
14 | import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity; | 15 | import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity; |
15 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; | 16 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; |
16 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
17 | 17 | ||
18 | import java.util.*; | 18 | import 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; | |||
10 | import tools.refinery.store.reasoning.ReasoningAdapter; | 10 | import tools.refinery.store.reasoning.ReasoningAdapter; |
11 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | 11 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; |
12 | import tools.refinery.store.reasoning.seed.ModelSeed; | 12 | import tools.refinery.store.reasoning.seed.ModelSeed; |
13 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
13 | import tools.refinery.store.representation.Symbol; | 14 | import tools.refinery.store.representation.Symbol; |
14 | import tools.refinery.store.representation.TruthValue; | 15 | import tools.refinery.store.representation.TruthValue; |
15 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | 16 | import 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 @@ | |||
6 | package tools.refinery.store.reasoning.translator.multiplicity; | 6 | package tools.refinery.store.reasoning.translator.multiplicity; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | 10 | import tools.refinery.store.representation.cardinality.CardinalityInterval; |
10 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | 11 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; |
11 | import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval; | 12 | import 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; | |||
14 | import tools.refinery.store.reasoning.literal.*; | 14 | import tools.refinery.store.reasoning.literal.*; |
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | 15 | import tools.refinery.store.reasoning.representation.PartialRelation; |
16 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 16 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
17 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
17 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | 18 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; |
18 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | 19 | import tools.refinery.store.representation.cardinality.UpperCardinalities; |
19 | import tools.refinery.store.representation.cardinality.UpperCardinality; | 20 | import 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; | |||
17 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | 17 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; |
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | 18 | import tools.refinery.store.reasoning.representation.PartialRelation; |
19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
20 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
20 | 21 | ||
21 | import java.util.List; | 22 | import java.util.List; |
22 | import java.util.Set; | 23 | import 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; | |||
17 | import tools.refinery.store.query.view.MustView; | 17 | import tools.refinery.store.query.view.MustView; |
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | 18 | import tools.refinery.store.reasoning.representation.PartialRelation; |
19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
20 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
20 | import tools.refinery.store.representation.Symbol; | 21 | import tools.refinery.store.representation.Symbol; |
21 | import tools.refinery.store.representation.TruthValue; | 22 | import 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 @@ | |||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | 6 | package tools.refinery.store.reasoning.translator.typehierarchy; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | 10 | ||
10 | import java.util.*; | 11 | import 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 @@ | |||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | 6 | package tools.refinery.store.reasoning.translator.typehierarchy; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | 10 | ||
10 | import java.util.*; | 11 | import 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 | ||
8 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | 9 | import tools.refinery.store.reasoning.representation.PartialRelation; |
10 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | 11 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; |
11 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | 12 | import 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; | |||
8 | import org.hamcrest.Matchers; | 8 | import org.hamcrest.Matchers; |
9 | import org.junit.jupiter.api.Test; | 9 | import org.junit.jupiter.api.Test; |
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | 10 | import tools.refinery.store.reasoning.representation.PartialRelation; |
11 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
11 | import tools.refinery.store.representation.TruthValue; | 12 | import tools.refinery.store.representation.TruthValue; |
12 | 13 | ||
13 | import java.util.Set; | 14 | import 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 |