diff options
author | 2023-07-24 14:37:16 +0200 | |
---|---|---|
committer | 2023-07-24 14:37:16 +0200 | |
commit | 8789304690384d19ad829286560aec5ed0917976 (patch) | |
tree | b157e78e8b6a3f2fb3d0eee6e1aa10d2c16e0204 /subprojects | |
parent | decreasing steps in fast fuzz tests (diff) | |
parent | Merge pull request #27 from kris7t/ordered-result-set (diff) | |
download | refinery-8789304690384d19ad829286560aec5ed0917976.tar.gz refinery-8789304690384d19ad829286560aec5ed0917976.tar.zst refinery-8789304690384d19ad829286560aec5ed0917976.zip |
Merge branch 'graphs4value:main' into datastructure
Diffstat (limited to 'subprojects')
634 files changed, 20558 insertions, 3932 deletions
diff --git a/subprojects/frontend/.eslintrc.cjs b/subprojects/frontend/.eslintrc.cjs index 8a7b474a..25b86a83 100644 --- a/subprojects/frontend/.eslintrc.cjs +++ b/subprojects/frontend/.eslintrc.cjs | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | const path = require('node:path'); | 7 | const path = require('node:path'); |
2 | 8 | ||
3 | // Allow the Codium ESLint plugin to find `tsconfig.json` from the repository root. | 9 | // Allow the Codium ESLint plugin to find `tsconfig.json` from the repository root. |
@@ -43,6 +49,15 @@ module.exports = { | |||
43 | // In typescript, some class methods implementing an inderface do not use `this`: | 49 | // In typescript, some class methods implementing an inderface do not use `this`: |
44 | // https://github.com/typescript-eslint/typescript-eslint/issues/1103 | 50 | // https://github.com/typescript-eslint/typescript-eslint/issues/1103 |
45 | 'class-methods-use-this': 'off', | 51 | 'class-methods-use-this': 'off', |
52 | // Disable rules with a high performance cost. | ||
53 | // See https://typescript-eslint.io/linting/troubleshooting/performance-troubleshooting/ | ||
54 | 'import/default': 'off', | ||
55 | 'import/extensions': 'off', | ||
56 | 'import/named': 'off', | ||
57 | 'import/namespace': 'off', | ||
58 | 'import/no-named-as-default': 'off', | ||
59 | 'import/no-named-as-default-member': 'off', | ||
60 | '@typescript-eslint/indent': 'off', | ||
46 | // Make sure every import can be resolved by `eslint-import-resolver-typescript`. | 61 | // Make sure every import can be resolved by `eslint-import-resolver-typescript`. |
47 | 'import/no-unresolved': 'error', | 62 | 'import/no-unresolved': 'error', |
48 | // Organize imports automatically. | 63 | // Organize imports automatically. |
@@ -90,6 +105,7 @@ module.exports = { | |||
90 | files: [ | 105 | files: [ |
91 | '.eslintrc.cjs', | 106 | '.eslintrc.cjs', |
92 | 'config/*.ts', | 107 | 'config/*.ts', |
108 | 'config/*.cjs', | ||
93 | 'prettier.config.cjs', | 109 | 'prettier.config.cjs', |
94 | 'vite.config.ts', | 110 | 'vite.config.ts', |
95 | ], | 111 | ], |
@@ -103,6 +119,8 @@ module.exports = { | |||
103 | 'error', | 119 | 'error', |
104 | { devDependencies: true }, | 120 | { devDependencies: true }, |
105 | ], | 121 | ], |
122 | // Allow writing to the console in ad-hoc scripts. | ||
123 | 'no-console': 'off', | ||
106 | // Access to the environment in configuration files. | 124 | // Access to the environment in configuration files. |
107 | 'no-process-env': 'off', | 125 | 'no-process-env': 'off', |
108 | }, | 126 | }, |
diff --git a/subprojects/frontend/assets-src/favicon.svg.license b/subprojects/frontend/assets-src/favicon.svg.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/assets-src/favicon.svg.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/assets-src/icon.svg.license b/subprojects/frontend/assets-src/icon.svg.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/assets-src/icon.svg.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/assets-src/mask-icon.svg.license b/subprojects/frontend/assets-src/mask-icon.svg.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/assets-src/mask-icon.svg.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/build.gradle b/subprojects/frontend/build.gradle deleted file mode 100644 index 4cc2c5d7..00000000 --- a/subprojects/frontend/build.gradle +++ /dev/null | |||
@@ -1,131 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-frontend-workspace' | ||
3 | id 'refinery-sonarqube' | ||
4 | } | ||
5 | |||
6 | import org.siouan.frontendgradleplugin.infrastructure.gradle.RunYarn | ||
7 | |||
8 | def viteOutputDir = "${buildDir}/vite" | ||
9 | def productionResources = file("${viteOutputDir}/production") | ||
10 | |||
11 | frontend { | ||
12 | assembleScript = 'run build' | ||
13 | } | ||
14 | |||
15 | configurations { | ||
16 | productionAssets { | ||
17 | canBeConsumed = true | ||
18 | canBeResolved = false | ||
19 | } | ||
20 | } | ||
21 | |||
22 | def installFrontend = tasks.named('installFrontend') | ||
23 | |||
24 | def sourcesWithoutTypegen = fileTree('src') { | ||
25 | exclude '**/*.typegen.ts' | ||
26 | } | ||
27 | |||
28 | def assembleFrontend = tasks.named('assembleFrontend') | ||
29 | assembleFrontend.configure { | ||
30 | dependsOn generateXStateTypes | ||
31 | inputs.dir 'public' | ||
32 | inputs.files sourcesWithoutTypegen | ||
33 | inputs.file 'index.html' | ||
34 | inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'vite.config.ts') | ||
35 | inputs.file rootProject.file('yarn.lock') | ||
36 | outputs.dir productionResources | ||
37 | } | ||
38 | |||
39 | artifacts { | ||
40 | productionAssets(productionResources) { | ||
41 | builtBy assembleFrontend | ||
42 | } | ||
43 | } | ||
44 | |||
45 | def generateXStateTypes = tasks.register('generateXStateTypes', RunYarn) { | ||
46 | dependsOn installFrontend | ||
47 | inputs.files sourcesWithoutTypegen | ||
48 | inputs.file 'package.json' | ||
49 | inputs.file rootProject.file('yarn.lock') | ||
50 | outputs.dir 'src' | ||
51 | script = 'run typegen' | ||
52 | description = 'Generate TypeScript typings for XState state machines.' | ||
53 | } | ||
54 | |||
55 | def typecheckFrontend = tasks.register('typecheckFrontend', RunYarn) { | ||
56 | dependsOn installFrontend | ||
57 | dependsOn generateXStateTypes | ||
58 | inputs.dir 'src' | ||
59 | inputs.dir 'types' | ||
60 | inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.node.json') | ||
61 | inputs.file rootProject.file('yarn.lock') | ||
62 | outputs.dir "${buildDir}/typescript" | ||
63 | script = 'run typecheck' | ||
64 | group = 'verification' | ||
65 | description = 'Check for TypeScript type errors.' | ||
66 | } | ||
67 | |||
68 | def lintFrontend = tasks.register('lintFrontend', RunYarn) { | ||
69 | dependsOn installFrontend | ||
70 | dependsOn generateXStateTypes | ||
71 | dependsOn typecheckFrontend | ||
72 | inputs.dir 'src' | ||
73 | inputs.dir 'types' | ||
74 | inputs.files('.eslintrc.cjs', 'prettier.config.cjs') | ||
75 | inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.node.json') | ||
76 | inputs.file rootProject.file('yarn.lock') | ||
77 | if (project.hasProperty('ci')) { | ||
78 | outputs.file "${buildDir}/eslint.json" | ||
79 | script = 'run lint:ci' | ||
80 | } else { | ||
81 | script = 'run lint' | ||
82 | } | ||
83 | group = 'verification' | ||
84 | description = 'Check for TypeScript lint errors and warnings.' | ||
85 | } | ||
86 | |||
87 | def prettier = tasks.register('fixFrontend', RunYarn) { | ||
88 | dependsOn installFrontend | ||
89 | dependsOn generateXStateTypes | ||
90 | dependsOn typecheckFrontend | ||
91 | inputs.dir 'src' | ||
92 | inputs.dir 'types' | ||
93 | inputs.files('.eslintrc.cjs', 'prettier.config.cjs') | ||
94 | inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.node.json') | ||
95 | inputs.file rootProject.file('yarn.lock') | ||
96 | script = 'run lint:fix' | ||
97 | group = 'verification' | ||
98 | description = 'Fix TypeScript lint errors and warnings.' | ||
99 | } | ||
100 | |||
101 | tasks.named('check') { | ||
102 | dependsOn(typecheckFrontend) | ||
103 | dependsOn(lintFrontend) | ||
104 | } | ||
105 | |||
106 | tasks.register('serveFrontend', RunYarn) { | ||
107 | dependsOn installFrontend | ||
108 | dependsOn generateXStateTypes | ||
109 | inputs.dir 'public' | ||
110 | inputs.files sourcesWithoutTypegen | ||
111 | inputs.file 'index.html' | ||
112 | inputs.files('package.json', 'tsconfig.json', 'tsconfig.base.json', 'vite.config.ts') | ||
113 | inputs.file rootProject.file('yarn.lock') | ||
114 | outputs.dir "${viteOutputDir}/development" | ||
115 | script = 'run serve' | ||
116 | group = 'run' | ||
117 | description = 'Start a Vite dev server with hot module replacement.' | ||
118 | } | ||
119 | |||
120 | tasks.named('clean') { | ||
121 | delete 'dev-dist' | ||
122 | delete fileTree('src') { | ||
123 | include '**/*.typegen.ts' | ||
124 | } | ||
125 | } | ||
126 | |||
127 | sonarqube.properties { | ||
128 | properties['sonar.sources'] = 'src' | ||
129 | property 'sonar.nodejs.executable', "${frontend.nodeInstallDirectory.get()}/bin/node" | ||
130 | property 'sonar.eslint.reportPaths', "${buildDir}/eslint.json" | ||
131 | } | ||
diff --git a/subprojects/frontend/build.gradle.kts b/subprojects/frontend/build.gradle.kts new file mode 100644 index 00000000..d0839371 --- /dev/null +++ b/subprojects/frontend/build.gradle.kts | |||
@@ -0,0 +1,144 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import org.siouan.frontendgradleplugin.infrastructure.gradle.RunYarn | ||
8 | import tools.refinery.gradle.utils.SonarPropertiesUtils | ||
9 | |||
10 | plugins { | ||
11 | id("tools.refinery.gradle.frontend-workspace") | ||
12 | id("tools.refinery.gradle.sonarqube") | ||
13 | } | ||
14 | |||
15 | frontend { | ||
16 | assembleScript.set("run build") | ||
17 | } | ||
18 | |||
19 | val viteOutputDir = "$buildDir/vite" | ||
20 | |||
21 | val productionResources = file("$viteOutputDir/production") | ||
22 | |||
23 | val productionAssets: Configuration by configurations.creating { | ||
24 | isCanBeConsumed = true | ||
25 | isCanBeResolved = false | ||
26 | } | ||
27 | |||
28 | val sourcesWithoutTypes = fileTree("src") { | ||
29 | exclude("**/*.typegen.ts") | ||
30 | } | ||
31 | |||
32 | val sourcesWithTypes: FileCollection = fileTree("src") + fileTree("types") | ||
33 | |||
34 | val buildScripts: FileCollection = fileTree("config") + files( | ||
35 | ".eslintrc.cjs", | ||
36 | "prettier.config.cjs", | ||
37 | "vite.config.ts", | ||
38 | ) | ||
39 | |||
40 | val installationState = files( | ||
41 | rootProject.file("yarn.lock"), | ||
42 | rootProject.file("package.json"), | ||
43 | "package.json", | ||
44 | ) | ||
45 | |||
46 | val sharedConfigFiles: FileCollection = installationState + files( | ||
47 | "tsconfig.json", | ||
48 | "tsconfig.base.json", | ||
49 | "tsconfig.node.json", | ||
50 | "tsconfig.shared.json", | ||
51 | ) | ||
52 | |||
53 | val assembleConfigFiles = sharedConfigFiles + file("vite.config.ts") + fileTree("config") { | ||
54 | include("**/*.ts") | ||
55 | } | ||
56 | |||
57 | val assembleSources = sourcesWithTypes + fileTree("public") + file("index.html") | ||
58 | |||
59 | val assembleFiles = assembleSources + assembleConfigFiles | ||
60 | |||
61 | val lintingFiles: FileCollection = sourcesWithTypes + buildScripts + sharedConfigFiles | ||
62 | |||
63 | tasks { | ||
64 | val generateXStateTypes by registering(RunYarn::class) { | ||
65 | dependsOn(installFrontend) | ||
66 | inputs.files(sourcesWithoutTypes) | ||
67 | inputs.files(installationState) | ||
68 | outputs.dir("src") | ||
69 | script.set("run typegen") | ||
70 | description = "Generate TypeScript typings for XState state machines." | ||
71 | } | ||
72 | |||
73 | assembleFrontend { | ||
74 | dependsOn(generateXStateTypes) | ||
75 | inputs.files(assembleFiles) | ||
76 | outputs.dir(productionResources) | ||
77 | } | ||
78 | |||
79 | |||
80 | val typeCheckFrontend by registering(RunYarn::class) { | ||
81 | dependsOn(installFrontend) | ||
82 | dependsOn(generateXStateTypes) | ||
83 | inputs.files(lintingFiles) | ||
84 | outputs.dir("$buildDir/typescript") | ||
85 | script.set("run typecheck") | ||
86 | group = "verification" | ||
87 | description = "Check for TypeScript type errors." | ||
88 | } | ||
89 | |||
90 | val lintFrontend by registering(RunYarn::class) { | ||
91 | dependsOn(installFrontend) | ||
92 | dependsOn(generateXStateTypes) | ||
93 | dependsOn(typeCheckFrontend) | ||
94 | inputs.files(lintingFiles) | ||
95 | outputs.file("$buildDir/eslint.json") | ||
96 | script.set("run lint") | ||
97 | group = "verification" | ||
98 | description = "Check for TypeScript lint errors and warnings." | ||
99 | } | ||
100 | |||
101 | register<RunYarn>("fixFrontend") { | ||
102 | dependsOn(installFrontend) | ||
103 | dependsOn(generateXStateTypes) | ||
104 | dependsOn(typeCheckFrontend) | ||
105 | inputs.files(lintingFiles) | ||
106 | script.set("run lint:fix") | ||
107 | group = "verification" | ||
108 | description = "Fix TypeScript lint errors and warnings." | ||
109 | } | ||
110 | |||
111 | check { | ||
112 | dependsOn(typeCheckFrontend) | ||
113 | dependsOn(lintFrontend) | ||
114 | } | ||
115 | |||
116 | register<RunYarn>("serveFrontend") { | ||
117 | dependsOn(installFrontend) | ||
118 | dependsOn(generateXStateTypes) | ||
119 | inputs.files(assembleFiles) | ||
120 | outputs.dir("$viteOutputDir/development") | ||
121 | script.set("run serve") | ||
122 | group = "run" | ||
123 | description = "Start a Vite dev server with hot module replacement." | ||
124 | } | ||
125 | |||
126 | clean { | ||
127 | delete("dev-dist") | ||
128 | delete(fileTree("src") { | ||
129 | include("**/*.typegen.ts") | ||
130 | }) | ||
131 | } | ||
132 | } | ||
133 | |||
134 | artifacts { | ||
135 | add("productionAssets", productionResources) { | ||
136 | builtBy(tasks.assembleFrontend) | ||
137 | } | ||
138 | } | ||
139 | |||
140 | sonarqube.properties { | ||
141 | SonarPropertiesUtils.addToList(properties, "sonar.sources", "src") | ||
142 | property("sonar.nodejs.executable", "${frontend.nodeInstallDirectory.get()}/bin/node") | ||
143 | property("sonar.eslint.reportPaths", "$buildDir/eslint.json") | ||
144 | } | ||
diff --git a/subprojects/frontend/config/backendConfigVitePlugin.ts b/subprojects/frontend/config/backendConfigVitePlugin.ts index 7a6bc3db..3bffce3a 100644 --- a/subprojects/frontend/config/backendConfigVitePlugin.ts +++ b/subprojects/frontend/config/backendConfigVitePlugin.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { PluginOption } from 'vite'; | 7 | import type { PluginOption } from 'vite'; |
2 | 8 | ||
3 | import type BackendConfig from '../src/xtext/BackendConfig'; | 9 | import type BackendConfig from '../src/xtext/BackendConfig'; |
diff --git a/subprojects/frontend/config/detectDevModeOptions.ts b/subprojects/frontend/config/detectDevModeOptions.ts index b3696241..665204dc 100644 --- a/subprojects/frontend/config/detectDevModeOptions.ts +++ b/subprojects/frontend/config/detectDevModeOptions.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { PluginOption, ServerOptions } from 'vite'; | 7 | import type { PluginOption, ServerOptions } from 'vite'; |
2 | 8 | ||
3 | import backendConfigVitePlugin, { | 9 | import backendConfigVitePlugin, { |
diff --git a/subprojects/frontend/config/eslintReport.cjs b/subprojects/frontend/config/eslintReport.cjs new file mode 100644 index 00000000..7c4b7bd6 --- /dev/null +++ b/subprojects/frontend/config/eslintReport.cjs | |||
@@ -0,0 +1,58 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | const { writeFile } = require('node:fs/promises'); | ||
8 | const path = require('node:path'); | ||
9 | const { Readable } = require('node:stream'); | ||
10 | const { pipeline } = require('node:stream/promises'); | ||
11 | |||
12 | const { ESLint } = require('eslint'); | ||
13 | |||
14 | const rootDir = path.join(__dirname, '..'); | ||
15 | |||
16 | /** | ||
17 | * Write ESLint report to console. | ||
18 | * | ||
19 | * @param cli {import('eslint').ESLint} The ESLint CLI. | ||
20 | * @param report {import('eslint').ESLint.LintResult[]} The ESLint report. | ||
21 | * @return {Promise<void>} A promise that resolves when the report is finished. | ||
22 | */ | ||
23 | async function reportToConsole(cli, report) { | ||
24 | const stylishFormatter = await cli.loadFormatter('stylish'); | ||
25 | const output = new Readable(); | ||
26 | output.push(await stylishFormatter.format(report)); | ||
27 | output.push(null); | ||
28 | return pipeline(output, process.stdout); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * Write ESLint report to the <code>build</code> directory. | ||
33 | * | ||
34 | * @param cli {import('eslint').ESLint} The ESLint CLI. | ||
35 | * @param report {import('eslint').ESLint.LintResult[]} The ESLint report. | ||
36 | * @return {Promise<void>} A promise that resolves when the report is finished. | ||
37 | */ | ||
38 | async function reportToJson(cli, report) { | ||
39 | const jsonFormatter = await cli.loadFormatter('json'); | ||
40 | const json = await jsonFormatter.format(report); | ||
41 | const reportPath = path.join(rootDir, 'build', 'eslint.json'); | ||
42 | return writeFile(reportPath, json, 'utf-8'); | ||
43 | } | ||
44 | |||
45 | async function createReport() { | ||
46 | const cli = new ESLint({ | ||
47 | useEslintrc: true, | ||
48 | cwd: rootDir, | ||
49 | }); | ||
50 | const report = await cli.lintFiles('.'); | ||
51 | await Promise.all([reportToConsole(cli, report), reportToJson(cli, report)]); | ||
52 | |||
53 | if (report.some((entry) => entry.errorCount > 0)) { | ||
54 | process.exitCode = 1; | ||
55 | } | ||
56 | } | ||
57 | |||
58 | createReport().catch(console.error); | ||
diff --git a/subprojects/frontend/config/fetchPackageMetadata.ts b/subprojects/frontend/config/fetchPackageMetadata.ts index 50807b03..02e16d57 100644 --- a/subprojects/frontend/config/fetchPackageMetadata.ts +++ b/subprojects/frontend/config/fetchPackageMetadata.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { readFile } from 'node:fs/promises'; | 7 | import { readFile } from 'node:fs/promises'; |
2 | import path from 'node:path'; | 8 | import path from 'node:path'; |
3 | 9 | ||
diff --git a/subprojects/frontend/config/manifest.ts b/subprojects/frontend/config/manifest.ts index 3cec777c..1822dc7c 100644 --- a/subprojects/frontend/config/manifest.ts +++ b/subprojects/frontend/config/manifest.ts | |||
@@ -1,10 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { ManifestOptions } from 'vite-plugin-pwa'; | 7 | import type { ManifestOptions } from 'vite-plugin-pwa'; |
2 | 8 | ||
3 | const manifest: Partial<ManifestOptions> = { | 9 | const manifest: Partial<ManifestOptions> = { |
4 | lang: 'en-US', | 10 | lang: 'en-US', |
5 | name: 'Refinery', | 11 | name: 'Refinery', |
6 | short_name: 'Refinery', | 12 | short_name: 'Refinery', |
7 | description: 'An efficient graph sovler for generating well-formed models', | 13 | description: 'An efficient graph solver for generating well-formed models', |
8 | theme_color: '#f5f5f5', | 14 | theme_color: '#f5f5f5', |
9 | display_override: ['window-controls-overlay'], | 15 | display_override: ['window-controls-overlay'], |
10 | display: 'standalone', | 16 | display: 'standalone', |
diff --git a/subprojects/frontend/config/minifyHTMLVitePlugin.ts b/subprojects/frontend/config/minifyHTMLVitePlugin.ts index 18336d4d..7c08c488 100644 --- a/subprojects/frontend/config/minifyHTMLVitePlugin.ts +++ b/subprojects/frontend/config/minifyHTMLVitePlugin.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { minify, type Options as TerserOptions } from 'html-minifier-terser'; | 7 | import { minify, type Options as TerserOptions } from 'html-minifier-terser'; |
2 | import type { PluginOption } from 'vite'; | 8 | import type { PluginOption } from 'vite'; |
3 | 9 | ||
diff --git a/subprojects/frontend/config/preloadFontsVitePlugin.ts b/subprojects/frontend/config/preloadFontsVitePlugin.ts index bc6f8eaf..5c04477a 100644 --- a/subprojects/frontend/config/preloadFontsVitePlugin.ts +++ b/subprojects/frontend/config/preloadFontsVitePlugin.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import micromatch from 'micromatch'; | 7 | import micromatch from 'micromatch'; |
2 | import type { PluginOption } from 'vite'; | 8 | import type { PluginOption } from 'vite'; |
3 | 9 | ||
diff --git a/subprojects/frontend/index.html b/subprojects/frontend/index.html index 8b6814eb..1bf3472e 100644 --- a/subprojects/frontend/index.html +++ b/subprojects/frontend/index.html | |||
@@ -1,4 +1,9 @@ | |||
1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
2 | <!-- | ||
3 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
4 | |||
5 | SPDX-License-Identifier: EPL-2.0 | ||
6 | --> | ||
2 | <html lang="en-US"> | 7 | <html lang="en-US"> |
3 | <head> | 8 | <head> |
4 | <meta charset="utf-8"> | 9 | <meta charset="utf-8"> |
@@ -13,9 +18,9 @@ | |||
13 | <meta name="theme-color" media="(prefers-color-scheme:light)" content="#f5f5f5"> | 18 | <meta name="theme-color" media="(prefers-color-scheme:light)" content="#f5f5f5"> |
14 | <meta name="theme-color" media="(prefers-color-scheme:dark)" content="#21252b"> | 19 | <meta name="theme-color" media="(prefers-color-scheme:dark)" content="#21252b"> |
15 | <style> | 20 | <style> |
16 | @import '@fontsource/inter/variable.css'; | 21 | @import '@fontsource-variable/inter/wght.css'; |
17 | @import '@fontsource/jetbrains-mono/variable.css'; | 22 | @import '@fontsource-variable/jetbrains-mono/wght.css'; |
18 | @import '@fontsource/jetbrains-mono/variable-italic.css'; | 23 | @import '@fontsource-variable/jetbrains-mono/wght-italic.css'; |
19 | </style> | 24 | </style> |
20 | </head> | 25 | </head> |
21 | <body> | 26 | <body> |
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index a9153cd4..ba8a0a58 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -1,94 +1,99 @@ | |||
1 | { | 1 | { |
2 | "//": [ | ||
3 | "SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>", | ||
4 | "", | ||
5 | "SPDX-License-Identifier: EPL-2.0" | ||
6 | ], | ||
2 | "name": "@refinery/frontend", | 7 | "name": "@refinery/frontend", |
3 | "version": "0.0.0", | 8 | "version": "0.0.0", |
4 | "description": "Web frontend for Refinery", | 9 | "description": "Web frontend for Refinery", |
10 | "type": "module", | ||
5 | "private": true, | 11 | "private": true, |
6 | "scripts": { | 12 | "scripts": { |
7 | "build": "cross-env MODE=production vite build", | 13 | "build": "cross-env MODE=production vite build", |
8 | "serve": "cross-env MODE=development vite serve", | 14 | "serve": "cross-env MODE=development vite serve", |
9 | "typegen": "xstate typegen \"src/**/*.ts?(x)\"", | 15 | "typegen": "xstate typegen \"src/**/*.ts?(x)\"", |
10 | "typecheck": "tsc -p tsconfig.shared.json && tsc -p tsconfig.node.json && tsc -p tsconfig.json", | 16 | "typecheck": "tsc -p tsconfig.shared.json && tsc -p tsconfig.node.json && tsc -p tsconfig.json", |
11 | "lint": "eslint .", | 17 | "lint": "node config/eslintReport.cjs", |
12 | "lint:ci": "eslint -f json -o build/eslint.json .", | ||
13 | "lint:fix": "yarn run lint --fix" | 18 | "lint:fix": "yarn run lint --fix" |
14 | }, | 19 | }, |
15 | "repository": { | 20 | "repository": { |
16 | "type": "git", | 21 | "type": "git", |
17 | "url": "git+https://github.com/graphs4value/refinery.git" | 22 | "url": "git+https://github.com/graphs4value/refinery.git" |
18 | }, | 23 | }, |
19 | "author": "Refinery authors", | 24 | "author": "The Refinery Authors <https://refinery.tools/>", |
20 | "license": "EPL-2.0", | 25 | "license": "EPL-2.0", |
21 | "bugs": { | 26 | "bugs": { |
22 | "url": "https://github.com/graphs4value/issues" | 27 | "url": "https://github.com/graphs4value/refinery/issues" |
23 | }, | 28 | }, |
24 | "homepage": "https://refinery.tools", | 29 | "homepage": "https://refinery.tools", |
25 | "dependencies": { | 30 | "dependencies": { |
26 | "@codemirror/autocomplete": "^6.4.0", | 31 | "@codemirror/autocomplete": "^6.8.0", |
27 | "@codemirror/commands": "^6.2.0", | 32 | "@codemirror/commands": "^6.2.4", |
28 | "@codemirror/language": "^6.4.0", | 33 | "@codemirror/language": "^6.8.0", |
29 | "@codemirror/lint": "^6.1.0", | 34 | "@codemirror/lint": "^6.2.2", |
30 | "@codemirror/search": "^6.2.3", | 35 | "@codemirror/search": "^6.5.0", |
31 | "@codemirror/state": "^6.2.0", | 36 | "@codemirror/state": "^6.2.1", |
32 | "@codemirror/view": "^6.7.3", | 37 | "@codemirror/view": "^6.13.2", |
33 | "@emotion/react": "^11.10.5", | 38 | "@emotion/react": "^11.11.1", |
34 | "@emotion/styled": "^11.10.5", | 39 | "@emotion/styled": "^11.11.0", |
35 | "@fontsource/inter": "^4.5.15", | 40 | "@fontsource-variable/inter": "^5.0.3", |
36 | "@fontsource/jetbrains-mono": "^4.5.12", | 41 | "@fontsource-variable/jetbrains-mono": "^5.0.3", |
37 | "@lezer/common": "^1.0.2", | 42 | "@lezer/common": "^1.0.3", |
38 | "@lezer/highlight": "^1.1.3", | 43 | "@lezer/highlight": "^1.1.6", |
39 | "@lezer/lr": "^1.3.3", | 44 | "@lezer/lr": "^1.3.6", |
40 | "@material-icons/svg": "^1.0.33", | 45 | "@material-icons/svg": "^1.0.33", |
41 | "@mui/icons-material": "5.11.0", | 46 | "@mui/icons-material": "5.11.16", |
42 | "@mui/material": "5.11.7", | 47 | "@mui/material": "5.13.5", |
43 | "@vitejs/plugin-react-swc": "^3.1.0", | 48 | "@vitejs/plugin-react-swc": "^3.3.2", |
44 | "ansi-styles": "^6.2.1", | 49 | "ansi-styles": "^6.2.1", |
50 | "csstype": "^3.1.2", | ||
45 | "escape-string-regexp": "^5.0.0", | 51 | "escape-string-regexp": "^5.0.0", |
46 | "lodash-es": "^4.17.21", | 52 | "lodash-es": "^4.17.21", |
47 | "loglevel": "^1.8.1", | 53 | "loglevel": "^1.8.1", |
48 | "loglevel-plugin-prefix": "^0.8.4", | 54 | "loglevel-plugin-prefix": "^0.8.4", |
49 | "mobx": "^6.7.0", | 55 | "mobx": "^6.9.0", |
50 | "mobx-react-lite": "^3.4.0", | 56 | "mobx-react-lite": "^3.4.3", |
51 | "ms": "^2.1.3", | 57 | "ms": "^2.1.3", |
52 | "nanoid": "^4.0.0", | 58 | "nanoid": "^4.0.2", |
53 | "notistack": "^2.0.8", | 59 | "notistack": "^3.0.1", |
54 | "react": "^18.2.0", | 60 | "react": "^18.2.0", |
55 | "react-dom": "^18.2.0", | 61 | "react-dom": "^18.2.0", |
56 | "xstate": "^4.35.4", | 62 | "xstate": "^4.37.2", |
57 | "zod": "^3.20.2" | 63 | "zod": "^3.21.4" |
58 | }, | 64 | }, |
59 | "devDependencies": { | 65 | "devDependencies": { |
60 | "@lezer/generator": "^1.2.2", | 66 | "@lezer/generator": "^1.3.0", |
61 | "@tsconfig/node18-strictest-esm": "^1.0.1", | 67 | "@types/eslint": "^8.40.2", |
62 | "@types/eslint": "^8.21.0", | ||
63 | "@types/html-minifier-terser": "^7.0.0", | 68 | "@types/html-minifier-terser": "^7.0.0", |
64 | "@types/lodash-es": "^4.17.6", | 69 | "@types/lodash-es": "^4.17.7", |
65 | "@types/micromatch": "^4.0.2", | 70 | "@types/micromatch": "^4.0.2", |
66 | "@types/ms": "^0.7.31", | 71 | "@types/ms": "^0.7.31", |
67 | "@types/node": "^18.11.18", | 72 | "@types/node": "^18.16.18", |
68 | "@types/prettier": "^2.7.2", | 73 | "@types/prettier": "^2.7.3", |
69 | "@types/react": "^18.0.27", | 74 | "@types/react": "^18.2.12", |
70 | "@types/react-dom": "^18.0.10", | 75 | "@types/react-dom": "^18.2.5", |
71 | "@typescript-eslint/eslint-plugin": "^5.50.0", | 76 | "@typescript-eslint/eslint-plugin": "^5.59.11", |
72 | "@typescript-eslint/parser": "^5.50.0", | 77 | "@typescript-eslint/parser": "^5.59.11", |
73 | "@xstate/cli": "^0.4.2", | 78 | "@xstate/cli": "^0.5.1", |
74 | "cross-env": "^7.0.3", | 79 | "cross-env": "^7.0.3", |
75 | "eslint": "^8.33.0", | 80 | "eslint": "^8.43.0", |
76 | "eslint-config-airbnb": "^19.0.4", | 81 | "eslint-config-airbnb": "^19.0.4", |
77 | "eslint-config-airbnb-typescript": "^17.0.0", | 82 | "eslint-config-airbnb-typescript": "^17.0.0", |
78 | "eslint-config-prettier": "^8.6.0", | 83 | "eslint-config-prettier": "^8.8.0", |
79 | "eslint-import-resolver-typescript": "^3.5.3", | 84 | "eslint-import-resolver-typescript": "^3.5.5", |
80 | "eslint-plugin-import": "^2.27.5", | 85 | "eslint-plugin-import": "^2.27.5", |
81 | "eslint-plugin-jsx-a11y": "^6.7.1", | 86 | "eslint-plugin-jsx-a11y": "^6.7.1", |
82 | "eslint-plugin-mobx": "^0.0.9", | 87 | "eslint-plugin-mobx": "^0.0.9", |
83 | "eslint-plugin-prettier": "^4.2.1", | 88 | "eslint-plugin-prettier": "^4.2.1", |
84 | "eslint-plugin-react": "^7.32.2", | 89 | "eslint-plugin-react": "^7.32.2", |
85 | "eslint-plugin-react-hooks": "^4.6.0", | 90 | "eslint-plugin-react-hooks": "^4.6.0", |
86 | "html-minifier-terser": "^7.1.0", | 91 | "html-minifier-terser": "^7.2.0", |
87 | "micromatch": "^4.0.5", | 92 | "micromatch": "^4.0.5", |
88 | "prettier": "^2.8.3", | 93 | "prettier": "^2.8.8", |
89 | "typescript": "4.9.5", | 94 | "typescript": "5.1.3", |
90 | "vite": "^4.1.1", | 95 | "vite": "^4.3.9", |
91 | "vite-plugin-pwa": "^0.14.1", | 96 | "vite-plugin-pwa": "^0.16.4", |
92 | "workbox-window": "^6.5.4" | 97 | "workbox-window": "^7.0.0" |
93 | } | 98 | } |
94 | } | 99 | } |
diff --git a/subprojects/frontend/prettier.config.cjs b/subprojects/frontend/prettier.config.cjs index 75f5c54d..6f9ff7ad 100644 --- a/subprojects/frontend/prettier.config.cjs +++ b/subprojects/frontend/prettier.config.cjs | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | /** @type {import('prettier').Config} */ | 7 | /** @type {import('prettier').Config} */ |
2 | module.exports = { | 8 | module.exports = { |
3 | singleQuote: true, | 9 | singleQuote: true, |
diff --git a/subprojects/frontend/public/apple-touch-icon.png.license b/subprojects/frontend/public/apple-touch-icon.png.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/apple-touch-icon.png.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/favicon-96x96.png.license b/subprojects/frontend/public/favicon-96x96.png.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/favicon-96x96.png.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/favicon.png.license b/subprojects/frontend/public/favicon.png.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/favicon.png.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/favicon.svg.license b/subprojects/frontend/public/favicon.svg.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/favicon.svg.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/icon-192x192.png.license b/subprojects/frontend/public/icon-192x192.png.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/icon-192x192.png.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/icon-512x512.png.license b/subprojects/frontend/public/icon-512x512.png.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/icon-512x512.png.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/icon-any.svg.license b/subprojects/frontend/public/icon-any.svg.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/icon-any.svg.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/mask-icon.svg.license b/subprojects/frontend/public/mask-icon.svg.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/frontend/public/mask-icon.svg.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/frontend/public/robots.txt b/subprojects/frontend/public/robots.txt index c2a49f4f..e7c73099 100644 --- a/subprojects/frontend/public/robots.txt +++ b/subprojects/frontend/public/robots.txt | |||
@@ -1,2 +1,6 @@ | |||
1 | # SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | # | ||
3 | # SPDX-License-Identifier: CC0-1.0 | ||
4 | |||
1 | User-agent: * | 5 | User-agent: * |
2 | Allow: / | 6 | Allow: / |
diff --git a/subprojects/frontend/src/App.tsx b/subprojects/frontend/src/App.tsx index cd394345..7f242529 100644 --- a/subprojects/frontend/src/App.tsx +++ b/subprojects/frontend/src/App.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Box from '@mui/material/Box'; | 7 | import Box from '@mui/material/Box'; |
2 | import CssBaseline from '@mui/material/CssBaseline'; | 8 | import CssBaseline from '@mui/material/CssBaseline'; |
3 | import { throttle } from 'lodash-es'; | 9 | import { throttle } from 'lodash-es'; |
diff --git a/subprojects/frontend/src/Loading.tsx b/subprojects/frontend/src/Loading.tsx index 489563e0..adee4f0e 100644 --- a/subprojects/frontend/src/Loading.tsx +++ b/subprojects/frontend/src/Loading.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import CircularProgress from '@mui/material/CircularProgress'; | 7 | import CircularProgress from '@mui/material/CircularProgress'; |
2 | import { styled } from '@mui/material/styles'; | 8 | import { styled } from '@mui/material/styles'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/PWAStore.ts b/subprojects/frontend/src/PWAStore.ts index e9f99e2a..a1b3ffd9 100644 --- a/subprojects/frontend/src/PWAStore.ts +++ b/subprojects/frontend/src/PWAStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { makeAutoObservable, observable } from 'mobx'; | 7 | import { makeAutoObservable, observable } from 'mobx'; |
2 | import ms from 'ms'; | 8 | import ms from 'ms'; |
3 | // eslint-disable-next-line import/no-unresolved -- Importing virtual module. | 9 | // eslint-disable-next-line import/no-unresolved -- Importing virtual module. |
diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx index 93a82ee1..b5ff94e1 100644 --- a/subprojects/frontend/src/Refinery.tsx +++ b/subprojects/frontend/src/Refinery.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Grow from '@mui/material/Grow'; | 7 | import Grow from '@mui/material/Grow'; |
2 | import Stack from '@mui/material/Stack'; | 8 | import Stack from '@mui/material/Stack'; |
3 | import { SnackbarProvider } from 'notistack'; | 9 | import { SnackbarProvider } from 'notistack'; |
@@ -8,7 +14,6 @@ import EditorPane from './editor/EditorPane'; | |||
8 | 14 | ||
9 | export default function Refinery(): JSX.Element { | 15 | export default function Refinery(): JSX.Element { |
10 | return ( | 16 | return ( |
11 | // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes | ||
12 | <SnackbarProvider TransitionComponent={Grow}> | 17 | <SnackbarProvider TransitionComponent={Grow}> |
13 | <UpdateNotification /> | 18 | <UpdateNotification /> |
14 | <Stack direction="column" height="100%" overflow="auto"> | 19 | <Stack direction="column" height="100%" overflow="auto"> |
diff --git a/subprojects/frontend/src/RootStore.ts b/subprojects/frontend/src/RootStore.ts index 2e76d66d..b84c0ce0 100644 --- a/subprojects/frontend/src/RootStore.ts +++ b/subprojects/frontend/src/RootStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { getLogger } from 'loglevel'; | 7 | import { getLogger } from 'loglevel'; |
2 | import { makeAutoObservable, runInAction } from 'mobx'; | 8 | import { makeAutoObservable, runInAction } from 'mobx'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/RootStoreProvider.tsx b/subprojects/frontend/src/RootStoreProvider.tsx index 2c11a0f9..7cb89af1 100644 --- a/subprojects/frontend/src/RootStoreProvider.tsx +++ b/subprojects/frontend/src/RootStoreProvider.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { type ReactNode, createContext, useContext } from 'react'; | 7 | import { type ReactNode, createContext, useContext } from 'react'; |
2 | 8 | ||
3 | import type RootStore from './RootStore'; | 9 | import type RootStore from './RootStore'; |
diff --git a/subprojects/frontend/src/ToggleDarkModeButton.tsx b/subprojects/frontend/src/ToggleDarkModeButton.tsx index 59714f20..7a835e61 100644 --- a/subprojects/frontend/src/ToggleDarkModeButton.tsx +++ b/subprojects/frontend/src/ToggleDarkModeButton.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import DarkModeIcon from '@mui/icons-material/DarkMode'; | 7 | import DarkModeIcon from '@mui/icons-material/DarkMode'; |
2 | import LightModeIcon from '@mui/icons-material/LightMode'; | 8 | import LightModeIcon from '@mui/icons-material/LightMode'; |
3 | import IconButton from '@mui/material/IconButton'; | 9 | import IconButton from '@mui/material/IconButton'; |
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index 5a825512..f2542b14 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import GitHubIcon from '@mui/icons-material/GitHub'; | 7 | import GitHubIcon from '@mui/icons-material/GitHub'; |
2 | import AppBar from '@mui/material/AppBar'; | 8 | import AppBar from '@mui/material/AppBar'; |
3 | import Button from '@mui/material/Button'; | 9 | import Button from '@mui/material/Button'; |
diff --git a/subprojects/frontend/src/UpdateNotification.tsx b/subprojects/frontend/src/UpdateNotification.tsx index 07f7f5f7..d86c0703 100644 --- a/subprojects/frontend/src/UpdateNotification.tsx +++ b/subprojects/frontend/src/UpdateNotification.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Button from '@mui/material/Button'; | 7 | import Button from '@mui/material/Button'; |
2 | import { observer } from 'mobx-react-lite'; | 8 | import { observer } from 'mobx-react-lite'; |
3 | import { useEffect } from 'react'; | 9 | import { useEffect } from 'react'; |
@@ -32,14 +38,14 @@ export default observer(function UpdateNotification(): null { | |||
32 | return enqueueLater('Failed to download update', { | 38 | return enqueueLater('Failed to download update', { |
33 | variant: 'error', | 39 | variant: 'error', |
34 | action: ( | 40 | action: ( |
35 | <> | 41 | <ContrastThemeProvider> |
36 | <Button color="inherit" onClick={() => pwaStore.checkForUpdates()}> | 42 | <Button color="inherit" onClick={() => pwaStore.checkForUpdates()}> |
37 | Try again | 43 | Try again |
38 | </Button> | 44 | </Button> |
39 | <Button color="inherit" onClick={() => pwaStore.dismissError()}> | 45 | <Button color="inherit" onClick={() => pwaStore.dismissError()}> |
40 | Dismiss | 46 | Dismiss |
41 | </Button> | 47 | </Button> |
42 | </> | 48 | </ContrastThemeProvider> |
43 | ), | 49 | ), |
44 | }); | 50 | }); |
45 | } | 51 | } |
diff --git a/subprojects/frontend/src/WindowControlsOverlayColor.tsx b/subprojects/frontend/src/WindowControlsOverlayColor.tsx index 14eda566..cfa468ea 100644 --- a/subprojects/frontend/src/WindowControlsOverlayColor.tsx +++ b/subprojects/frontend/src/WindowControlsOverlayColor.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { useTheme } from '@mui/material/styles'; | 7 | import { useTheme } from '@mui/material/styles'; |
2 | import { useEffect } from 'react'; | 8 | import { useEffect } from 'react'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/editor/AnimatedButton.tsx b/subprojects/frontend/src/editor/AnimatedButton.tsx index f75d4617..dbbda618 100644 --- a/subprojects/frontend/src/editor/AnimatedButton.tsx +++ b/subprojects/frontend/src/editor/AnimatedButton.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Box from '@mui/material/Box'; | 7 | import Box from '@mui/material/Box'; |
2 | import Button from '@mui/material/Button'; | 8 | import Button from '@mui/material/Button'; |
3 | import { styled, type SxProps, type Theme } from '@mui/material/styles'; | 9 | import { styled, type SxProps, type Theme } from '@mui/material/styles'; |
diff --git a/subprojects/frontend/src/editor/ConnectButton.tsx b/subprojects/frontend/src/editor/ConnectButton.tsx index e2d251f3..eed6fbc7 100644 --- a/subprojects/frontend/src/editor/ConnectButton.tsx +++ b/subprojects/frontend/src/editor/ConnectButton.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import CloudIcon from '@mui/icons-material/Cloud'; | 7 | import CloudIcon from '@mui/icons-material/Cloud'; |
2 | import CloudOffIcon from '@mui/icons-material/CloudOff'; | 8 | import CloudOffIcon from '@mui/icons-material/CloudOff'; |
3 | import SyncIcon from '@mui/icons-material/Sync'; | 9 | import SyncIcon from '@mui/icons-material/Sync'; |
diff --git a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx index 9b27f45c..b7b962ab 100644 --- a/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx +++ b/subprojects/frontend/src/editor/ConnectionStatusNotification.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Button from '@mui/material/Button'; | 7 | import Button from '@mui/material/Button'; |
2 | import { observer } from 'mobx-react-lite'; | 8 | import { observer } from 'mobx-react-lite'; |
3 | import { useEffect } from 'react'; | 9 | import { useEffect } from 'react'; |
diff --git a/subprojects/frontend/src/editor/DiagnosticValue.ts b/subprojects/frontend/src/editor/DiagnosticValue.ts index b4e0b165..20478262 100644 --- a/subprojects/frontend/src/editor/DiagnosticValue.ts +++ b/subprojects/frontend/src/editor/DiagnosticValue.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { Diagnostic } from '@codemirror/lint'; | 7 | import type { Diagnostic } from '@codemirror/lint'; |
2 | import { RangeValue } from '@codemirror/state'; | 8 | import { RangeValue } from '@codemirror/state'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx index cfb988b2..905fa2ec 100644 --- a/subprojects/frontend/src/editor/EditorArea.tsx +++ b/subprojects/frontend/src/editor/EditorArea.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Box from '@mui/material/Box'; | 7 | import Box from '@mui/material/Box'; |
2 | import { useTheme } from '@mui/material/styles'; | 8 | import { useTheme } from '@mui/material/styles'; |
3 | import { observer } from 'mobx-react-lite'; | 9 | import { observer } from 'mobx-react-lite'; |
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx index 53b06e23..9b187e5c 100644 --- a/subprojects/frontend/src/editor/EditorButtons.tsx +++ b/subprojects/frontend/src/editor/EditorButtons.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { Diagnostic } from '@codemirror/lint'; | 7 | import type { Diagnostic } from '@codemirror/lint'; |
2 | import CheckIcon from '@mui/icons-material/Check'; | 8 | import CheckIcon from '@mui/icons-material/Check'; |
3 | import ErrorIcon from '@mui/icons-material/Error'; | 9 | import ErrorIcon from '@mui/icons-material/Error'; |
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx index f7f8241a..87f408fe 100644 --- a/subprojects/frontend/src/editor/EditorPane.tsx +++ b/subprojects/frontend/src/editor/EditorPane.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Box from '@mui/material/Box'; | 7 | import Box from '@mui/material/Box'; |
2 | import Skeleton from '@mui/material/Skeleton'; | 8 | import Skeleton from '@mui/material/Skeleton'; |
3 | import Stack from '@mui/material/Stack'; | 9 | import Stack from '@mui/material/Stack'; |
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts index 0a0d885d..b98f085e 100644 --- a/subprojects/frontend/src/editor/EditorStore.ts +++ b/subprojects/frontend/src/editor/EditorStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { | 7 | import type { |
2 | CompletionContext, | 8 | CompletionContext, |
3 | CompletionResult, | 9 | CompletionResult, |
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts index 01b65a7e..e057ce18 100644 --- a/subprojects/frontend/src/editor/EditorTheme.ts +++ b/subprojects/frontend/src/editor/EditorTheme.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; | 7 | import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; |
2 | 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'; |
3 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; | 9 | import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; |
@@ -8,32 +14,6 @@ function svgURL(svg: string): string { | |||
8 | return `url('data:image/svg+xml;utf8,${svg}')`; | 14 | return `url('data:image/svg+xml;utf8,${svg}')`; |
9 | } | 15 | } |
10 | 16 | ||
11 | function radialShadowTheme( | ||
12 | origin: string, | ||
13 | scaleX: boolean, | ||
14 | scaleY: boolean, | ||
15 | ): CSSObject { | ||
16 | function radialGradient(opacity: number, scale: string): string { | ||
17 | return `radial-gradient( | ||
18 | farthest-side at ${origin}, | ||
19 | rgba(0, 0, 0, ${opacity}), | ||
20 | rgba(0, 0, 0, 0) | ||
21 | ) | ||
22 | ${origin} / | ||
23 | ${scaleX ? scale : '100%'} | ||
24 | ${scaleY ? scale : '100%'} | ||
25 | no-repeat`; | ||
26 | } | ||
27 | |||
28 | return { | ||
29 | background: ` | ||
30 | ${radialGradient(0.2, '40%')}, | ||
31 | ${radialGradient(0.14, '50%')}, | ||
32 | ${radialGradient(0.12, '100%')} | ||
33 | `, | ||
34 | }; | ||
35 | } | ||
36 | |||
37 | export default styled('div', { | 17 | export default styled('div', { |
38 | name: 'EditorTheme', | 18 | name: 'EditorTheme', |
39 | shouldForwardProp: (propName) => | 19 | shouldForwardProp: (propName) => |
@@ -52,98 +32,13 @@ export default styled('div', { | |||
52 | }, | 32 | }, |
53 | }; | 33 | }; |
54 | 34 | ||
55 | const scrollerThumbOpacity = theme.palette.mode === 'dark' ? 0.16 : 0.28; | ||
56 | |||
57 | const generalStyle: CSSObject = { | 35 | const generalStyle: CSSObject = { |
58 | background: theme.palette.background.default, | 36 | background: theme.palette.background.default, |
59 | '&, .cm-editor': { | 37 | '&, .cm-editor': { |
60 | height: '100%', | 38 | height: '100%', |
61 | }, | 39 | }, |
62 | '.cm-scroller-holder': { | ||
63 | display: 'flex', | ||
64 | position: 'relative', | ||
65 | flexDirection: 'column', | ||
66 | overflow: 'hidden', | ||
67 | flex: '1 1', | ||
68 | }, | ||
69 | '.cm-scroller-spacer': { | ||
70 | position: 'sticky', | ||
71 | flexShrink: 0, | ||
72 | zIndex: 300, | ||
73 | width: 1, | ||
74 | marginRight: -1, | ||
75 | pointerEvents: 'none', | ||
76 | }, | ||
77 | '.cm-scroller': { | 40 | '.cm-scroller': { |
78 | color: theme.palette.text.secondary, | 41 | color: theme.palette.text.secondary, |
79 | scrollbarWidth: 'none', | ||
80 | MsOverflowStyle: 'none', | ||
81 | '&::-webkit-scrollbar': { | ||
82 | width: 0, | ||
83 | height: 0, | ||
84 | background: 'transparent', | ||
85 | }, | ||
86 | }, | ||
87 | '.cm-scroller-track': { | ||
88 | position: 'absolute', | ||
89 | zIndex: 300, | ||
90 | touchAction: 'none', | ||
91 | }, | ||
92 | '.cm-scroller-thumb': { | ||
93 | position: 'absolute', | ||
94 | background: theme.palette.text.secondary, | ||
95 | opacity: scrollerThumbOpacity, | ||
96 | transition: theme.transitions.create('opacity', { | ||
97 | duration: theme.transitions.duration.shortest, | ||
98 | }), | ||
99 | touchAction: 'none', | ||
100 | WebkitTapHighlightColor: 'transparent', | ||
101 | '&:hover': { | ||
102 | opacity: 0.75, | ||
103 | '@media (hover: none)': { | ||
104 | opacity: scrollerThumbOpacity, | ||
105 | }, | ||
106 | }, | ||
107 | '&.active': { | ||
108 | opacity: 1, | ||
109 | pointerEvents: 'none', | ||
110 | userSelect: 'none', | ||
111 | }, | ||
112 | }, | ||
113 | '.cm-scroller-track-y, .cm-scroller-thumb-y': { | ||
114 | top: 0, | ||
115 | right: 0, | ||
116 | width: 12, | ||
117 | }, | ||
118 | '.cm-scroller-track-x, .cm-scroller-thumb-x': { | ||
119 | left: 0, | ||
120 | bottom: 0, | ||
121 | height: 12, | ||
122 | }, | ||
123 | '.cm-scroller-track-x': { | ||
124 | right: 12, | ||
125 | }, | ||
126 | '.cm-scroller-gutter-decoration': { | ||
127 | position: 'absolute', | ||
128 | top: 0, | ||
129 | bottom: 0, | ||
130 | left: 0, | ||
131 | width: 0, | ||
132 | transition: theme.transitions.create('width', { | ||
133 | duration: theme.transitions.duration.shortest, | ||
134 | }), | ||
135 | ...radialShadowTheme('0 50%', true, false), | ||
136 | }, | ||
137 | '.cm-scroller-top-decoration': { | ||
138 | position: 'absolute', | ||
139 | top: 0, | ||
140 | left: 0, | ||
141 | right: 0, | ||
142 | height: 0, | ||
143 | transition: theme.transitions.create('height', { | ||
144 | duration: theme.transitions.duration.shortest, | ||
145 | }), | ||
146 | ...radialShadowTheme('50% 0', false, true), | ||
147 | }, | 42 | }, |
148 | '.cm-gutters': { | 43 | '.cm-gutters': { |
149 | background: theme.palette.background.default, | 44 | background: theme.palette.background.default, |
@@ -162,7 +57,6 @@ export default styled('div', { | |||
162 | background: 'transparent', | 57 | background: 'transparent', |
163 | }, | 58 | }, |
164 | '.cm-cursor, .cm-cursor-primary': { | 59 | '.cm-cursor, .cm-cursor-primary': { |
165 | marginLeft: -1, | ||
166 | borderLeft: `2px solid ${theme.palette.info.main}`, | 60 | borderLeft: `2px solid ${theme.palette.info.main}`, |
167 | }, | 61 | }, |
168 | '.cm-selectionBackground': { | 62 | '.cm-selectionBackground': { |
@@ -175,7 +69,6 @@ export default styled('div', { | |||
175 | }, | 69 | }, |
176 | }, | 70 | }, |
177 | '.cm-line': { | 71 | '.cm-line': { |
178 | position: 'relative', // For indentation highlights | ||
179 | padding: '0 12px 0 0px', | 72 | padding: '0 12px 0 0px', |
180 | }, | 73 | }, |
181 | }; | 74 | }; |
@@ -265,13 +158,6 @@ export default styled('div', { | |||
265 | '.cm-searchMatch-selected': { | 158 | '.cm-searchMatch-selected': { |
266 | background: theme.palette.highlight.search.selected, | 159 | background: theme.palette.highlight.search.selected, |
267 | }, | 160 | }, |
268 | '.cm-indentation-marker': { | ||
269 | display: 'inline-block', | ||
270 | boxShadow: `1px 0 0 ${theme.palette.text.disabled} inset`, | ||
271 | '&.active': { | ||
272 | boxShadow: `1px 0 0 ${theme.palette.text.primary} inset`, | ||
273 | }, | ||
274 | }, | ||
275 | '.cm-scroller-selection': { | 161 | '.cm-scroller-selection': { |
276 | position: 'absolute', | 162 | position: 'absolute', |
277 | right: 0, | 163 | right: 0, |
@@ -452,11 +338,11 @@ export default styled('div', { | |||
452 | 338 | ||
453 | const foldStyle = { | 339 | const foldStyle = { |
454 | '.cm-foldGutter': { | 340 | '.cm-foldGutter': { |
455 | width: 17, | 341 | width: 16, |
456 | }, | 342 | }, |
457 | '.problem-editor-foldMarker': { | 343 | '.problem-editor-foldMarker': { |
458 | display: 'block', | 344 | display: 'block', |
459 | margin: '4px 1px 4px 0', | 345 | margin: '4px 0 4px 0', |
460 | padding: 0, | 346 | padding: 0, |
461 | maskImage: svgURL(expandMoreSVG), | 347 | maskImage: svgURL(expandMoreSVG), |
462 | maskSize: '16px 16px', | 348 | maskSize: '16px 16px', |
@@ -467,7 +353,7 @@ export default styled('div', { | |||
467 | cursor: 'pointer', | 353 | cursor: 'pointer', |
468 | WebkitTapHighlightColor: 'transparent', | 354 | WebkitTapHighlightColor: 'transparent', |
469 | [theme.breakpoints.down('sm')]: { | 355 | [theme.breakpoints.down('sm')]: { |
470 | margin: '2px 1px 2px 0', | 356 | margin: '2px 0 2px 0', |
471 | }, | 357 | }, |
472 | }, | 358 | }, |
473 | '.problem-editor-foldMarker-open': { | 359 | '.problem-editor-foldMarker-open': { |
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx index 2036fc28..3837ef8e 100644 --- a/subprojects/frontend/src/editor/GenerateButton.tsx +++ b/subprojects/frontend/src/editor/GenerateButton.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; | 7 | import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; |
2 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; | 8 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; |
3 | import Button from '@mui/material/Button'; | 9 | import Button from '@mui/material/Button'; |
diff --git a/subprojects/frontend/src/editor/LintPanelStore.ts b/subprojects/frontend/src/editor/LintPanelStore.ts index 502f9c59..f81587fa 100644 --- a/subprojects/frontend/src/editor/LintPanelStore.ts +++ b/subprojects/frontend/src/editor/LintPanelStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { closeLintPanel, openLintPanel } from '@codemirror/lint'; | 7 | import { closeLintPanel, openLintPanel } from '@codemirror/lint'; |
2 | 8 | ||
3 | import type EditorStore from './EditorStore'; | 9 | import type EditorStore from './EditorStore'; |
diff --git a/subprojects/frontend/src/editor/PanelStore.ts b/subprojects/frontend/src/editor/PanelStore.ts index 4f827280..25ef8b6c 100644 --- a/subprojects/frontend/src/editor/PanelStore.ts +++ b/subprojects/frontend/src/editor/PanelStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { Command } from '@codemirror/view'; | 7 | import type { Command } from '@codemirror/view'; |
2 | import { action, makeObservable, observable } from 'mobx'; | 8 | import { action, makeObservable, observable } from 'mobx'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/editor/SearchPanel.ts b/subprojects/frontend/src/editor/SearchPanel.ts index c9df41b7..b63d5eed 100644 --- a/subprojects/frontend/src/editor/SearchPanel.ts +++ b/subprojects/frontend/src/editor/SearchPanel.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | type EditorView, | 8 | type EditorView, |
3 | type Panel, | 9 | type Panel, |
diff --git a/subprojects/frontend/src/editor/SearchPanelPortal.tsx b/subprojects/frontend/src/editor/SearchPanelPortal.tsx index 5cf1c90e..b4b07c74 100644 --- a/subprojects/frontend/src/editor/SearchPanelPortal.tsx +++ b/subprojects/frontend/src/editor/SearchPanelPortal.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import Portal from '@mui/material/Portal'; | 7 | import Portal from '@mui/material/Portal'; |
2 | import { observer } from 'mobx-react-lite'; | 8 | import { observer } from 'mobx-react-lite'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/editor/SearchPanelStore.ts b/subprojects/frontend/src/editor/SearchPanelStore.ts index 65d595a8..6a97baf1 100644 --- a/subprojects/frontend/src/editor/SearchPanelStore.ts +++ b/subprojects/frontend/src/editor/SearchPanelStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | closeSearchPanel, | 8 | closeSearchPanel, |
3 | findNext, | 9 | findNext, |
diff --git a/subprojects/frontend/src/editor/SearchToolbar.tsx b/subprojects/frontend/src/editor/SearchToolbar.tsx index 54f3dba7..4ae7e893 100644 --- a/subprojects/frontend/src/editor/SearchToolbar.tsx +++ b/subprojects/frontend/src/editor/SearchToolbar.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import CloseIcon from '@mui/icons-material/Close'; | 7 | import CloseIcon from '@mui/icons-material/Close'; |
2 | import FindReplaceIcon from '@mui/icons-material/FindReplace'; | 8 | import FindReplaceIcon from '@mui/icons-material/FindReplace'; |
3 | import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; | 9 | import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; |
diff --git a/subprojects/frontend/src/editor/createEditorState.ts b/subprojects/frontend/src/editor/createEditorState.ts index ce1efa4f..67b8fb9e 100644 --- a/subprojects/frontend/src/editor/createEditorState.ts +++ b/subprojects/frontend/src/editor/createEditorState.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | closeBrackets, | 8 | closeBrackets, |
3 | closeBracketsKeymap, | 9 | closeBracketsKeymap, |
@@ -38,8 +44,6 @@ import type EditorStore from './EditorStore'; | |||
38 | import SearchPanel from './SearchPanel'; | 44 | import SearchPanel from './SearchPanel'; |
39 | import exposeDiagnostics from './exposeDiagnostics'; | 45 | import exposeDiagnostics from './exposeDiagnostics'; |
40 | import findOccurrences from './findOccurrences'; | 46 | import findOccurrences from './findOccurrences'; |
41 | import indentationMarkerViewPlugin from './indentationMarkerViewPlugin'; | ||
42 | import scrollbarViewPlugin from './scrollbarViewPlugin'; | ||
43 | import semanticHighlighting from './semanticHighlighting'; | 47 | import semanticHighlighting from './semanticHighlighting'; |
44 | 48 | ||
45 | export default function createEditorState( | 49 | export default function createEditorState( |
@@ -64,7 +68,6 @@ export default function createEditorState( | |||
64 | highlightSpecialChars(), | 68 | highlightSpecialChars(), |
65 | history(), | 69 | history(), |
66 | indentOnInput(), | 70 | indentOnInput(), |
67 | indentationMarkerViewPlugin(), | ||
68 | rectangularSelection(), | 71 | rectangularSelection(), |
69 | search({ | 72 | search({ |
70 | createPanel(view) { | 73 | createPanel(view) { |
@@ -123,7 +126,6 @@ export default function createEditorState( | |||
123 | ...defaultKeymap, | 126 | ...defaultKeymap, |
124 | ]), | 127 | ]), |
125 | problemLanguageSupport(), | 128 | problemLanguageSupport(), |
126 | scrollbarViewPlugin(store), | ||
127 | ], | 129 | ], |
128 | }); | 130 | }); |
129 | } | 131 | } |
diff --git a/subprojects/frontend/src/editor/defineDecorationSetExtension.ts b/subprojects/frontend/src/editor/defineDecorationSetExtension.ts index d9c7bc7d..0887c92e 100644 --- a/subprojects/frontend/src/editor/defineDecorationSetExtension.ts +++ b/subprojects/frontend/src/editor/defineDecorationSetExtension.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { StateEffect, StateField, TransactionSpec } from '@codemirror/state'; | 7 | import { StateEffect, StateField, TransactionSpec } from '@codemirror/state'; |
2 | import { EditorView, Decoration, DecorationSet } from '@codemirror/view'; | 8 | import { EditorView, Decoration, DecorationSet } from '@codemirror/view'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/editor/exposeDiagnostics.ts b/subprojects/frontend/src/editor/exposeDiagnostics.ts index 82f24c93..c4dcbb87 100644 --- a/subprojects/frontend/src/editor/exposeDiagnostics.ts +++ b/subprojects/frontend/src/editor/exposeDiagnostics.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { setDiagnosticsEffect } from '@codemirror/lint'; | 7 | import { setDiagnosticsEffect } from '@codemirror/lint'; |
2 | import { | 8 | import { |
3 | StateField, | 9 | StateField, |
diff --git a/subprojects/frontend/src/editor/findOccurrences.ts b/subprojects/frontend/src/editor/findOccurrences.ts index 08c078c2..00dffc96 100644 --- a/subprojects/frontend/src/editor/findOccurrences.ts +++ b/subprojects/frontend/src/editor/findOccurrences.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | type Range, | 8 | type Range, |
3 | RangeSet, | 9 | RangeSet, |
diff --git a/subprojects/frontend/src/editor/indentationMarkerViewPlugin.ts b/subprojects/frontend/src/editor/indentationMarkerViewPlugin.ts deleted file mode 100644 index 730fa6e3..00000000 --- a/subprojects/frontend/src/editor/indentationMarkerViewPlugin.ts +++ /dev/null | |||
@@ -1,341 +0,0 @@ | |||
1 | /** | ||
2 | * @file CodeMirror plugin to highlight indentation | ||
3 | * | ||
4 | * This file is based on the | ||
5 | * [@replit/codemirror-indentation-markers](https://github.com/replit/codemirror-indentation-markers) | ||
6 | * package, which is available under the | ||
7 | * [MIT License](https://github.com/replit/codemirror-indentation-markers/blob/543cc508ca5cef5d8350af23973eb1425e31525c/LICENSE). | ||
8 | * | ||
9 | * The highlighting heuristics were adjusted to make them more suitable | ||
10 | * for logic programming. | ||
11 | * | ||
12 | * @see https://github.com/replit/codemirror-indentation-markers/blob/543cc508ca5cef5d8350af23973eb1425e31525c/src/index.ts | ||
13 | */ | ||
14 | |||
15 | import { getIndentUnit } from '@codemirror/language'; | ||
16 | import { Text, RangeSet, EditorState } from '@codemirror/state'; | ||
17 | import { | ||
18 | ViewPlugin, | ||
19 | Decoration, | ||
20 | EditorView, | ||
21 | WidgetType, | ||
22 | PluginValue, | ||
23 | } from '@codemirror/view'; | ||
24 | |||
25 | export const INDENTATION_MARKER_CLASS = 'cm-indentation-marker'; | ||
26 | |||
27 | export const INDENTATION_MARKER_ACTIVE_CLASS = 'active'; | ||
28 | |||
29 | const indentationMark = Decoration.mark({ | ||
30 | class: INDENTATION_MARKER_CLASS, | ||
31 | tagName: 'span', | ||
32 | }); | ||
33 | |||
34 | const activeIndentationMark = Decoration.mark({ | ||
35 | class: `${INDENTATION_MARKER_CLASS} ${INDENTATION_MARKER_ACTIVE_CLASS}`, | ||
36 | tagName: 'span', | ||
37 | }); | ||
38 | |||
39 | /** | ||
40 | * Widget used to simulate N indentation markers on empty lines. | ||
41 | */ | ||
42 | class IndentationWidget extends WidgetType { | ||
43 | constructor( | ||
44 | readonly numIndent: number, | ||
45 | readonly indentSize: number, | ||
46 | readonly activeIndent?: number, | ||
47 | ) { | ||
48 | super(); | ||
49 | } | ||
50 | |||
51 | override eq(other: IndentationWidget) { | ||
52 | return ( | ||
53 | this.numIndent === other.numIndent && | ||
54 | this.indentSize === other.indentSize && | ||
55 | this.activeIndent === other.activeIndent | ||
56 | ); | ||
57 | } | ||
58 | |||
59 | override toDOM(view: EditorView) { | ||
60 | const indentSize = getIndentUnit(view.state); | ||
61 | |||
62 | const wrapper = document.createElement('span'); | ||
63 | wrapper.style.top = '0'; | ||
64 | wrapper.style.left = '0'; | ||
65 | wrapper.style.position = 'absolute'; | ||
66 | wrapper.style.pointerEvents = 'none'; | ||
67 | |||
68 | for (let indent = 0; indent < this.numIndent; indent += 1) { | ||
69 | const element = document.createElement('span'); | ||
70 | element.className = INDENTATION_MARKER_CLASS; | ||
71 | element.classList.toggle( | ||
72 | INDENTATION_MARKER_ACTIVE_CLASS, | ||
73 | indent === this.activeIndent, | ||
74 | ); | ||
75 | element.innerHTML = ' '.repeat(indentSize); | ||
76 | wrapper.appendChild(element); | ||
77 | } | ||
78 | |||
79 | return wrapper; | ||
80 | } | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Returns the number of indentation markers a non-empty line should have | ||
85 | * based on the text in the line and the size of the indent. | ||
86 | */ | ||
87 | function getNumIndentMarkersForNonEmptyLine( | ||
88 | text: string, | ||
89 | indentSize: number, | ||
90 | onIndentMarker?: (pos: number) => void, | ||
91 | ) { | ||
92 | let numIndents = 0; | ||
93 | let numConsecutiveSpaces = 0; | ||
94 | let prevChar: string | undefined; | ||
95 | |||
96 | for (let char = 0; char < text.length; char += 1) { | ||
97 | // Bail if we encounter a non-whitespace character | ||
98 | if (text[char] !== ' ' && text[char] !== '\t') { | ||
99 | // We still increment the indentation level if we would | ||
100 | // have added a marker here had this been a space or tab. | ||
101 | if (numConsecutiveSpaces % indentSize === 0 && char !== 0) { | ||
102 | numIndents += 1; | ||
103 | } | ||
104 | |||
105 | return numIndents; | ||
106 | } | ||
107 | |||
108 | // Every tab and N space has an indentation marker | ||
109 | const shouldAddIndent = | ||
110 | prevChar === '\t' || numConsecutiveSpaces % indentSize === 0; | ||
111 | |||
112 | if (shouldAddIndent) { | ||
113 | numIndents += 1; | ||
114 | |||
115 | if (onIndentMarker) { | ||
116 | onIndentMarker(char); | ||
117 | } | ||
118 | } | ||
119 | |||
120 | if (text[char] === ' ') { | ||
121 | numConsecutiveSpaces += 1; | ||
122 | } else { | ||
123 | numConsecutiveSpaces = 0; | ||
124 | } | ||
125 | |||
126 | prevChar = text[char]; | ||
127 | } | ||
128 | |||
129 | return numIndents; | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Returns the number of indent markers an empty line should have | ||
134 | * based on the number of indent markers of the previous | ||
135 | * and next non-empty lines. | ||
136 | */ | ||
137 | function getNumIndentMarkersForEmptyLine(prev: number, next: number) { | ||
138 | const min = Math.min(prev, next); | ||
139 | const max = Math.max(prev, next); | ||
140 | |||
141 | // If only one side is non-zero, we omit markers, | ||
142 | // because in logic programming, a block often ends with an empty line. | ||
143 | if (min === 0 && max > 0) { | ||
144 | return 0; | ||
145 | } | ||
146 | |||
147 | // Else, default to the minimum of the two | ||
148 | return min; | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Returns the next non-empty line and its indent level. | ||
153 | */ | ||
154 | function findNextNonEmptyLineAndIndentLevel( | ||
155 | doc: Text, | ||
156 | startLine: number, | ||
157 | indentSize: number, | ||
158 | ): [number, number] { | ||
159 | const numLines = doc.lines; | ||
160 | let lineNo = startLine; | ||
161 | |||
162 | while (lineNo <= numLines) { | ||
163 | const { text } = doc.line(lineNo); | ||
164 | |||
165 | if (text.trim().length === 0) { | ||
166 | lineNo += 1; | ||
167 | } else { | ||
168 | const indent = getNumIndentMarkersForNonEmptyLine(text, indentSize); | ||
169 | return [lineNo, indent]; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | // Reached the end of the doc | ||
174 | return [numLines + 1, 0]; | ||
175 | } | ||
176 | |||
177 | interface IndentationMarkerDesc { | ||
178 | lineNumber: number; | ||
179 | from: number; | ||
180 | to: number; | ||
181 | create(activeIndentIndex?: number): Decoration; | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * Returns a range of lines with an active indent marker. | ||
186 | */ | ||
187 | function getLinesWithActiveIndentMarker( | ||
188 | state: EditorState, | ||
189 | indentMap: Map<number, number>, | ||
190 | ): { start: number; end: number; activeIndent: number } { | ||
191 | const currentLine = state.doc.lineAt(state.selection.main.head); | ||
192 | const currentIndent = indentMap.get(currentLine.number); | ||
193 | const currentLineNo = currentLine.number; | ||
194 | |||
195 | if (!currentIndent) { | ||
196 | return { start: -1, end: -1, activeIndent: NaN }; | ||
197 | } | ||
198 | |||
199 | let start: number; | ||
200 | let end: number; | ||
201 | |||
202 | for (start = currentLineNo; start >= 0; start -= 1) { | ||
203 | const indent = indentMap.get(start - 1); | ||
204 | if (!indent || indent < currentIndent) { | ||
205 | break; | ||
206 | } | ||
207 | } | ||
208 | |||
209 | for (end = currentLineNo; ; end += 1) { | ||
210 | const indent = indentMap.get(end + 1); | ||
211 | if (!indent || indent < currentIndent) { | ||
212 | break; | ||
213 | } | ||
214 | } | ||
215 | |||
216 | return { start, end, activeIndent: currentIndent }; | ||
217 | } | ||
218 | /** | ||
219 | * Adds indentation markers to all lines within view. | ||
220 | */ | ||
221 | function addIndentationMarkers(view: EditorView) { | ||
222 | const indentSize = getIndentUnit(view.state); | ||
223 | const indentSizeMap = new Map</* lineNumber */ number, number>(); | ||
224 | const decorations: Array<IndentationMarkerDesc> = []; | ||
225 | |||
226 | view.visibleRanges.forEach(({ from, to }) => { | ||
227 | let pos = from; | ||
228 | |||
229 | let prevIndentMarkers = 0; | ||
230 | let nextIndentMarkers = 0; | ||
231 | let nextNonEmptyLine = 0; | ||
232 | |||
233 | while (pos <= to) { | ||
234 | const line = view.state.doc.lineAt(pos); | ||
235 | const { text } = line; | ||
236 | |||
237 | // If a line is empty, we match the indentation according | ||
238 | // to a heuristic based on the indentations of the | ||
239 | // previous and next non-empty lines. | ||
240 | if (text.trim().length === 0) { | ||
241 | // To retrieve the next non-empty indentation level, | ||
242 | // we perform a lookahead and cache the result. | ||
243 | if (nextNonEmptyLine < line.number) { | ||
244 | const [nextLine, nextIndent] = findNextNonEmptyLineAndIndentLevel( | ||
245 | view.state.doc, | ||
246 | line.number + 1, | ||
247 | indentSize, | ||
248 | ); | ||
249 | |||
250 | nextNonEmptyLine = nextLine; | ||
251 | nextIndentMarkers = nextIndent; | ||
252 | } | ||
253 | |||
254 | const numIndentMarkers = getNumIndentMarkersForEmptyLine( | ||
255 | prevIndentMarkers, | ||
256 | nextIndentMarkers, | ||
257 | ); | ||
258 | |||
259 | // Add the indent widget and move on to next line | ||
260 | indentSizeMap.set(line.number, numIndentMarkers); | ||
261 | decorations.push({ | ||
262 | from: pos, | ||
263 | to: pos, | ||
264 | lineNumber: line.number, | ||
265 | create: (activeIndentIndex) => | ||
266 | Decoration.widget({ | ||
267 | widget: new IndentationWidget( | ||
268 | numIndentMarkers, | ||
269 | indentSize, | ||
270 | activeIndentIndex, | ||
271 | ), | ||
272 | }), | ||
273 | }); | ||
274 | } else { | ||
275 | const indices: Array<number> = []; | ||
276 | |||
277 | prevIndentMarkers = getNumIndentMarkersForNonEmptyLine( | ||
278 | text, | ||
279 | indentSize, | ||
280 | (char) => indices.push(char), | ||
281 | ); | ||
282 | |||
283 | indentSizeMap.set(line.number, indices.length); | ||
284 | decorations.push( | ||
285 | ...indices.map( | ||
286 | (char, i): IndentationMarkerDesc => ({ | ||
287 | from: line.from + char, | ||
288 | to: line.from + char + 1, | ||
289 | lineNumber: line.number, | ||
290 | create: (activeIndentIndex) => | ||
291 | activeIndentIndex === i | ||
292 | ? activeIndentationMark | ||
293 | : indentationMark, | ||
294 | }), | ||
295 | ), | ||
296 | ); | ||
297 | } | ||
298 | |||
299 | // Move on to the next line | ||
300 | pos = line.to + 1; | ||
301 | } | ||
302 | }); | ||
303 | |||
304 | const activeBlockRange = getLinesWithActiveIndentMarker( | ||
305 | view.state, | ||
306 | indentSizeMap, | ||
307 | ); | ||
308 | |||
309 | return RangeSet.of<Decoration>( | ||
310 | Array.from(decorations).map(({ lineNumber, from, to, create }) => { | ||
311 | const activeIndent = | ||
312 | lineNumber >= activeBlockRange.start && | ||
313 | lineNumber <= activeBlockRange.end | ||
314 | ? activeBlockRange.activeIndent - 1 | ||
315 | : undefined; | ||
316 | |||
317 | return { from, to, value: create(activeIndent) }; | ||
318 | }), | ||
319 | true, | ||
320 | ); | ||
321 | } | ||
322 | |||
323 | export default function indentationMarkerViewPlugin() { | ||
324 | return ViewPlugin.define<PluginValue & { decorations: RangeSet<Decoration> }>( | ||
325 | (view) => ({ | ||
326 | decorations: addIndentationMarkers(view), | ||
327 | update(update) { | ||
328 | if ( | ||
329 | update.docChanged || | ||
330 | update.viewportChanged || | ||
331 | update.selectionSet | ||
332 | ) { | ||
333 | this.decorations = addIndentationMarkers(update.view); | ||
334 | } | ||
335 | }, | ||
336 | }), | ||
337 | { | ||
338 | decorations: (v) => v.decorations, | ||
339 | }, | ||
340 | ); | ||
341 | } | ||
diff --git a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts deleted file mode 100644 index f54251a9..00000000 --- a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts +++ /dev/null | |||
@@ -1,358 +0,0 @@ | |||
1 | import { EditorSelection } from '@codemirror/state'; | ||
2 | import { | ||
3 | type EditorView, | ||
4 | type PluginValue, | ||
5 | ViewPlugin, | ||
6 | } from '@codemirror/view'; | ||
7 | import { reaction } from 'mobx'; | ||
8 | |||
9 | import type EditorStore from './EditorStore'; | ||
10 | import { getDiagnostics } from './exposeDiagnostics'; | ||
11 | import findOccurrences from './findOccurrences'; | ||
12 | |||
13 | export const HOLDER_CLASS = 'cm-scroller-holder'; | ||
14 | export const SPACER_CLASS = 'cm-scroller-spacer'; | ||
15 | export const TRACK_CLASS = 'cm-scroller-track'; | ||
16 | export const THUMB_CLASS = 'cm-scroller-thumb'; | ||
17 | export const THUMB_ACTIVE_CLASS = 'active'; | ||
18 | export const GUTTER_DECORATION_CLASS = 'cm-scroller-gutter-decoration'; | ||
19 | export const TOP_DECORATION_CLASS = 'cm-scroller-top-decoration'; | ||
20 | export const ANNOTATION_SELECTION_CLASS = 'cm-scroller-selection'; | ||
21 | export const ANNOTATION_DIAGNOSTIC_CLASS = 'cm-scroller-diagnostic'; | ||
22 | export const ANNOTATION_OCCURRENCE_CLASS = 'cm-scroller-occurrence'; | ||
23 | export const SHADOW_WIDTH = 10; | ||
24 | export const SCROLLBAR_WIDTH = 12; | ||
25 | export const ANNOTATION_WIDTH = SCROLLBAR_WIDTH / 2; | ||
26 | export const MIN_ANNOTATION_HEIGHT = 1; | ||
27 | |||
28 | function createScrollbar( | ||
29 | holder: HTMLElement, | ||
30 | direction: 'x' | 'y', | ||
31 | touchCallback: (offsetX: number, offsetY: number) => void, | ||
32 | moveCallback: (movementX: number, movementY: number) => void, | ||
33 | ): { track: HTMLElement; thumb: HTMLElement } { | ||
34 | const track = holder.ownerDocument.createElement('div'); | ||
35 | track.className = `${TRACK_CLASS} ${TRACK_CLASS}-${direction}`; | ||
36 | holder.appendChild(track); | ||
37 | |||
38 | const thumb = holder.ownerDocument.createElement('div'); | ||
39 | thumb.className = `${THUMB_CLASS} ${THUMB_CLASS}-${direction}`; | ||
40 | track.appendChild(thumb); | ||
41 | |||
42 | let pointerId: number | undefined; | ||
43 | track.addEventListener('pointerdown', (event) => { | ||
44 | if (pointerId !== undefined) { | ||
45 | event.preventDefault(); | ||
46 | return; | ||
47 | } | ||
48 | ({ pointerId } = event); | ||
49 | thumb.classList.add(THUMB_ACTIVE_CLASS); | ||
50 | if (event.target === thumb) { | ||
51 | // Prevent implicit pointer capture on mobile. | ||
52 | thumb.releasePointerCapture(pointerId); | ||
53 | } else { | ||
54 | touchCallback(event.offsetX, event.offsetY); | ||
55 | } | ||
56 | track.setPointerCapture(pointerId); | ||
57 | }); | ||
58 | |||
59 | track.addEventListener('pointermove', (event) => { | ||
60 | if (event.pointerId !== pointerId) { | ||
61 | return; | ||
62 | } | ||
63 | moveCallback(event.movementX, event.movementY); | ||
64 | event.preventDefault(); | ||
65 | }); | ||
66 | |||
67 | function scrollEnd(event: PointerEvent) { | ||
68 | if (event.pointerId !== pointerId) { | ||
69 | return; | ||
70 | } | ||
71 | pointerId = undefined; | ||
72 | thumb.classList.remove(THUMB_ACTIVE_CLASS); | ||
73 | } | ||
74 | |||
75 | track.addEventListener('pointerup', scrollEnd, { passive: true }); | ||
76 | track.addEventListener('pointercancel', scrollEnd, { passive: true }); | ||
77 | |||
78 | return { track, thumb }; | ||
79 | } | ||
80 | |||
81 | function rebuildAnnotations( | ||
82 | view: EditorView, | ||
83 | scrollHeight: number, | ||
84 | trackYHeight: number, | ||
85 | holder: HTMLElement, | ||
86 | annotations: HTMLDivElement[], | ||
87 | ) { | ||
88 | const { state } = view; | ||
89 | const overlayAnnotationsHeight = | ||
90 | (view.contentHeight / scrollHeight) * trackYHeight; | ||
91 | const lineHeight = overlayAnnotationsHeight / state.doc.lines; | ||
92 | |||
93 | let i = 0; | ||
94 | |||
95 | function getOrCreateAnnotation(from: number, to?: number): HTMLDivElement { | ||
96 | const startLine = state.doc.lineAt(from).number; | ||
97 | const endLine = to === undefined ? startLine : state.doc.lineAt(to).number; | ||
98 | const top = (startLine - 1) * lineHeight; | ||
99 | const height = Math.max( | ||
100 | MIN_ANNOTATION_HEIGHT, | ||
101 | Math.max(1, endLine - startLine) * lineHeight, | ||
102 | ); | ||
103 | |||
104 | let annotation: HTMLDivElement | undefined; | ||
105 | if (i < annotations.length) { | ||
106 | annotation = annotations[i]; | ||
107 | } | ||
108 | if (annotation === undefined) { | ||
109 | annotation = holder.ownerDocument.createElement('div'); | ||
110 | annotations.push(annotation); | ||
111 | holder.appendChild(annotation); | ||
112 | } | ||
113 | i += 1; | ||
114 | |||
115 | annotation.style.top = `${top}px`; | ||
116 | annotation.style.height = `${height}px`; | ||
117 | |||
118 | return annotation; | ||
119 | } | ||
120 | |||
121 | state.selection.ranges.forEach(({ head }) => { | ||
122 | const selectionAnnotation = getOrCreateAnnotation(head); | ||
123 | selectionAnnotation.className = ANNOTATION_SELECTION_CLASS; | ||
124 | selectionAnnotation.style.width = `${SCROLLBAR_WIDTH}px`; | ||
125 | }); | ||
126 | |||
127 | const diagnosticsIter = getDiagnostics(state).iter(); | ||
128 | while (diagnosticsIter.value !== null) { | ||
129 | const diagnosticAnnotation = getOrCreateAnnotation( | ||
130 | diagnosticsIter.from, | ||
131 | diagnosticsIter.to, | ||
132 | ); | ||
133 | diagnosticAnnotation.className = `${ANNOTATION_DIAGNOSTIC_CLASS} ${ANNOTATION_DIAGNOSTIC_CLASS}-${diagnosticsIter.value.severity}`; | ||
134 | diagnosticAnnotation.style.width = `${ANNOTATION_WIDTH}px`; | ||
135 | diagnosticsIter.next(); | ||
136 | } | ||
137 | |||
138 | const occurrences = view.state.field(findOccurrences); | ||
139 | const occurrencesIter = occurrences.iter(); | ||
140 | while (occurrencesIter.value !== null) { | ||
141 | const occurrenceAnnotation = getOrCreateAnnotation( | ||
142 | occurrencesIter.from, | ||
143 | occurrencesIter.to, | ||
144 | ); | ||
145 | occurrenceAnnotation.className = ANNOTATION_OCCURRENCE_CLASS; | ||
146 | occurrenceAnnotation.style.width = `${ANNOTATION_WIDTH}px`; | ||
147 | occurrenceAnnotation.style.right = `${ANNOTATION_WIDTH}px`; | ||
148 | occurrencesIter.next(); | ||
149 | } | ||
150 | |||
151 | annotations | ||
152 | .splice(i) | ||
153 | .forEach((staleAnnotation) => holder.removeChild(staleAnnotation)); | ||
154 | } | ||
155 | |||
156 | export default function scrollbarViewPlugin( | ||
157 | editorStore: EditorStore, | ||
158 | ): ViewPlugin<PluginValue> { | ||
159 | return ViewPlugin.define((view) => { | ||
160 | const { scrollDOM } = view; | ||
161 | const { ownerDocument, parentElement: parentDOM } = scrollDOM; | ||
162 | if (parentDOM === null) { | ||
163 | return {}; | ||
164 | } | ||
165 | |||
166 | const holder = ownerDocument.createElement('div'); | ||
167 | holder.className = HOLDER_CLASS; | ||
168 | parentDOM.replaceChild(holder, scrollDOM); | ||
169 | holder.appendChild(scrollDOM); | ||
170 | |||
171 | const spacer = ownerDocument.createElement('div'); | ||
172 | spacer.className = SPACER_CLASS; | ||
173 | scrollDOM.insertBefore(spacer, scrollDOM.firstChild); | ||
174 | |||
175 | let gutterWidth = 0; | ||
176 | |||
177 | scrollDOM.addEventListener('click', (event) => { | ||
178 | const scrollX = scrollDOM.scrollLeft + event.offsetX; | ||
179 | const scrollY = scrollDOM.scrollTop + event.offsetY; | ||
180 | if (scrollX > gutterWidth && scrollY > view.contentHeight) { | ||
181 | event.preventDefault(); | ||
182 | view.focus(); | ||
183 | editorStore.dispatch({ | ||
184 | scrollIntoView: true, | ||
185 | selection: EditorSelection.create([ | ||
186 | EditorSelection.cursor(view.state.doc.length), | ||
187 | ]), | ||
188 | }); | ||
189 | } | ||
190 | }); | ||
191 | |||
192 | let factorY = 1; | ||
193 | let factorX = 1; | ||
194 | |||
195 | const { track: trackY, thumb: thumbY } = createScrollbar( | ||
196 | holder, | ||
197 | 'y', | ||
198 | (_offsetX, offsetY) => { | ||
199 | const scaledOffset = offsetY / factorY; | ||
200 | const { height: scrollerHeight } = scrollDOM.getBoundingClientRect(); | ||
201 | const target = Math.max(0, scaledOffset - scrollerHeight / 2); | ||
202 | scrollDOM.scrollTo({ top: target }); | ||
203 | }, | ||
204 | (_movementX, movementY) => { | ||
205 | scrollDOM.scrollBy({ top: movementY / factorY }); | ||
206 | }, | ||
207 | ); | ||
208 | |||
209 | const { track: trackX, thumb: thumbX } = createScrollbar( | ||
210 | holder, | ||
211 | 'x', | ||
212 | (offsetX) => { | ||
213 | const scaledOffset = offsetX / factorX; | ||
214 | const { width: scrollerWidth } = scrollDOM.getBoundingClientRect(); | ||
215 | const target = Math.max(0, scaledOffset - scrollerWidth / 2); | ||
216 | scrollDOM.scrollTo({ left: target }); | ||
217 | }, | ||
218 | (movementX) => { | ||
219 | scrollDOM.scrollBy({ left: movementX / factorX }); | ||
220 | }, | ||
221 | ); | ||
222 | |||
223 | const gutterDecoration = ownerDocument.createElement('div'); | ||
224 | gutterDecoration.className = GUTTER_DECORATION_CLASS; | ||
225 | holder.appendChild(gutterDecoration); | ||
226 | |||
227 | const topDecoration = ownerDocument.createElement('div'); | ||
228 | topDecoration.className = TOP_DECORATION_CLASS; | ||
229 | holder.appendChild(topDecoration); | ||
230 | |||
231 | const disposePanelReaction = reaction( | ||
232 | () => editorStore.searchPanel.state, | ||
233 | (panelOpen) => { | ||
234 | topDecoration.style.display = panelOpen ? 'none' : 'block'; | ||
235 | }, | ||
236 | { fireImmediately: true }, | ||
237 | ); | ||
238 | |||
239 | let gutters: Element | undefined; | ||
240 | |||
241 | let firstRun = true; | ||
242 | let firstRunTimeout: number | undefined; | ||
243 | let requested = false; | ||
244 | let rebuildRequested = false; | ||
245 | |||
246 | const annotations: HTMLDivElement[] = []; | ||
247 | |||
248 | let observer: ResizeObserver | undefined; | ||
249 | |||
250 | function update() { | ||
251 | requested = false; | ||
252 | |||
253 | if (gutters === undefined) { | ||
254 | gutters = scrollDOM.querySelector('.cm-gutters') ?? undefined; | ||
255 | if (gutters !== undefined && observer !== undefined) { | ||
256 | observer.observe(gutters); | ||
257 | } | ||
258 | } | ||
259 | |||
260 | const { height: scrollerHeight, width: scrollerWidth } = | ||
261 | scrollDOM.getBoundingClientRect(); | ||
262 | const { scrollTop, scrollLeft, scrollWidth } = scrollDOM; | ||
263 | const scrollHeight = | ||
264 | view.contentHeight + scrollerHeight - view.defaultLineHeight; | ||
265 | if (firstRun) { | ||
266 | if (firstRunTimeout !== undefined) { | ||
267 | clearTimeout(firstRunTimeout); | ||
268 | } | ||
269 | // @ts-expect-error `@types/node` typings should not be in effect here. | ||
270 | firstRunTimeout = setTimeout(() => { | ||
271 | spacer.style.minHeight = `${scrollHeight}px`; | ||
272 | firstRun = false; | ||
273 | }, 0); | ||
274 | } else { | ||
275 | spacer.style.minHeight = `${scrollHeight}px`; | ||
276 | } | ||
277 | gutterWidth = gutters?.clientWidth ?? 0; | ||
278 | let trackYHeight = scrollerHeight; | ||
279 | |||
280 | // Prevent spurious horizontal scrollbar by rounding up to the nearest pixel. | ||
281 | if (scrollWidth > Math.ceil(scrollerWidth)) { | ||
282 | // Leave space for horizontal scrollbar. | ||
283 | trackYHeight -= SCROLLBAR_WIDTH; | ||
284 | // Alwalys leave space for annotation in the vertical scrollbar. | ||
285 | const trackXWidth = scrollerWidth - gutterWidth - SCROLLBAR_WIDTH; | ||
286 | const thumbWidth = trackXWidth * (scrollerWidth / scrollWidth); | ||
287 | factorX = (trackXWidth - thumbWidth) / (scrollWidth - scrollerWidth); | ||
288 | trackY.style.bottom = `${SCROLLBAR_WIDTH}px`; | ||
289 | trackX.style.display = 'block'; | ||
290 | trackX.style.left = `${gutterWidth}px`; | ||
291 | thumbX.style.width = `${thumbWidth}px`; | ||
292 | thumbX.style.left = `${scrollLeft * factorX}px`; | ||
293 | scrollDOM.style.overflowX = 'scroll'; | ||
294 | } else { | ||
295 | trackY.style.bottom = '0px'; | ||
296 | trackX.style.display = 'none'; | ||
297 | scrollDOM.style.overflowX = 'hidden'; | ||
298 | } | ||
299 | |||
300 | const thumbHeight = trackYHeight * (scrollerHeight / scrollHeight); | ||
301 | factorY = (trackYHeight - thumbHeight) / (scrollHeight - scrollerHeight); | ||
302 | thumbY.style.display = 'block'; | ||
303 | thumbY.style.height = `${thumbHeight}px`; | ||
304 | thumbY.style.top = `${scrollTop * factorY}px`; | ||
305 | |||
306 | gutterDecoration.style.left = `${gutterWidth}px`; | ||
307 | gutterDecoration.style.width = `${Math.max( | ||
308 | 0, | ||
309 | Math.min(scrollLeft, SHADOW_WIDTH), | ||
310 | )}px`; | ||
311 | |||
312 | topDecoration.style.height = `${Math.max( | ||
313 | 0, | ||
314 | Math.min(scrollTop, SHADOW_WIDTH), | ||
315 | )}px`; | ||
316 | |||
317 | if (rebuildRequested) { | ||
318 | rebuildAnnotations( | ||
319 | view, | ||
320 | scrollHeight, | ||
321 | trackYHeight, | ||
322 | holder, | ||
323 | annotations, | ||
324 | ); | ||
325 | rebuildRequested = false; | ||
326 | } | ||
327 | } | ||
328 | |||
329 | function requestUpdate() { | ||
330 | if (!requested) { | ||
331 | requested = true; | ||
332 | view.requestMeasure({ read: update }); | ||
333 | } | ||
334 | } | ||
335 | |||
336 | function requestRebuild() { | ||
337 | requestUpdate(); | ||
338 | rebuildRequested = true; | ||
339 | } | ||
340 | |||
341 | observer = new ResizeObserver(requestRebuild); | ||
342 | observer.observe(holder); | ||
343 | |||
344 | scrollDOM.addEventListener('scroll', requestUpdate); | ||
345 | |||
346 | requestRebuild(); | ||
347 | |||
348 | return { | ||
349 | update: requestRebuild, | ||
350 | destroy() { | ||
351 | disposePanelReaction(); | ||
352 | observer?.disconnect(); | ||
353 | scrollDOM.removeEventListener('scroll', requestUpdate); | ||
354 | parentDOM.replaceChild(holder, holder); | ||
355 | }, | ||
356 | }; | ||
357 | }); | ||
358 | } | ||
diff --git a/subprojects/frontend/src/editor/semanticHighlighting.ts b/subprojects/frontend/src/editor/semanticHighlighting.ts index 2c1bd67d..1f2e564c 100644 --- a/subprojects/frontend/src/editor/semanticHighlighting.ts +++ b/subprojects/frontend/src/editor/semanticHighlighting.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { RangeSet, type TransactionSpec } from '@codemirror/state'; | 7 | import { RangeSet, type TransactionSpec } from '@codemirror/state'; |
2 | import { Decoration } from '@codemirror/view'; | 8 | import { Decoration } from '@codemirror/view'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx index 29b2b196..cb11e6c3 100644 --- a/subprojects/frontend/src/index.tsx +++ b/subprojects/frontend/src/index.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { configure } from 'mobx'; | 7 | import { configure } from 'mobx'; |
2 | import { type Root, createRoot } from 'react-dom/client'; | 8 | import { type Root, createRoot } from 'react-dom/client'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/language/folding.ts b/subprojects/frontend/src/language/folding.ts index 4dabfa27..b4d4ca22 100644 --- a/subprojects/frontend/src/language/folding.ts +++ b/subprojects/frontend/src/language/folding.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { EditorState } from '@codemirror/state'; | 7 | import type { EditorState } from '@codemirror/state'; |
2 | import type { SyntaxNode } from '@lezer/common'; | 8 | import type { SyntaxNode } from '@lezer/common'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/language/indentation.ts b/subprojects/frontend/src/language/indentation.ts index a0f7032d..8446d7fa 100644 --- a/subprojects/frontend/src/language/indentation.ts +++ b/subprojects/frontend/src/language/indentation.ts | |||
@@ -1,3 +1,10 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others | ||
3 | * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | */ | ||
7 | |||
1 | import type { TreeIndentContext } from '@codemirror/language'; | 8 | import type { TreeIndentContext } from '@codemirror/language'; |
2 | 9 | ||
3 | /** | 10 | /** |
diff --git a/subprojects/frontend/src/language/problem.grammar b/subprojects/frontend/src/language/problem.grammar index 704badab..a7b1fb0a 100644 --- a/subprojects/frontend/src/language/problem.grammar +++ b/subprojects/frontend/src/language/problem.grammar | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | @detectDelim | 7 | @detectDelim |
2 | 8 | ||
3 | @external prop implicitCompletion from './props' | 9 | @external prop implicitCompletion from './props' |
diff --git a/subprojects/frontend/src/language/problemLanguageSupport.ts b/subprojects/frontend/src/language/problemLanguageSupport.ts index c3ae7ed9..2121e05f 100644 --- a/subprojects/frontend/src/language/problemLanguageSupport.ts +++ b/subprojects/frontend/src/language/problemLanguageSupport.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | foldInside, | 8 | foldInside, |
3 | foldNodeProp, | 9 | foldNodeProp, |
diff --git a/subprojects/frontend/src/language/props.ts b/subprojects/frontend/src/language/props.ts index 65392e75..aa67145a 100644 --- a/subprojects/frontend/src/language/props.ts +++ b/subprojects/frontend/src/language/props.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | /* eslint-disable import/prefer-default-export -- Lezer needs non-default exports */ | 7 | /* eslint-disable import/prefer-default-export -- Lezer needs non-default exports */ |
2 | 8 | ||
3 | import { NodeProp } from '@lezer/common'; | 9 | import { NodeProp } from '@lezer/common'; |
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 7bda1ede..78146f25 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | alpha, | 8 | alpha, |
3 | createTheme, | 9 | createTheme, |
@@ -6,7 +12,6 @@ import { | |||
6 | type ThemeOptions, | 12 | type ThemeOptions, |
7 | ThemeProvider as MaterialUiThemeProvider, | 13 | ThemeProvider as MaterialUiThemeProvider, |
8 | type TypographyStyle, | 14 | type TypographyStyle, |
9 | useTheme, | ||
10 | type CSSObject, | 15 | type CSSObject, |
11 | } from '@mui/material/styles'; | 16 | } from '@mui/material/styles'; |
12 | import { observer } from 'mobx-react-lite'; | 17 | import { observer } from 'mobx-react-lite'; |
@@ -70,7 +75,7 @@ function createResponsiveTheme( | |||
70 | ...options, | 75 | ...options, |
71 | typography: { | 76 | typography: { |
72 | fontFamily: | 77 | fontFamily: |
73 | '"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', | 78 | '"Inter Variable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', |
74 | fontWeightMedium: 600, | 79 | fontWeightMedium: 600, |
75 | fontWeightEditorNormal: 400, | 80 | fontWeightEditorNormal: 400, |
76 | fontWeightEditorBold: 700, | 81 | fontWeightEditorBold: 700, |
@@ -80,7 +85,7 @@ function createResponsiveTheme( | |||
80 | }, | 85 | }, |
81 | editor: { | 86 | editor: { |
82 | fontFamily: | 87 | fontFamily: |
83 | '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', | 88 | '"JetBrains Mono Variable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', |
84 | fontFeatureSettings: '"liga", "calt"', | 89 | fontFeatureSettings: '"liga", "calt"', |
85 | // `rem` for JetBrains MonoVariable make the text too large in Safari. | 90 | // `rem` for JetBrains MonoVariable make the text too large in Safari. |
86 | fontSize: '16px', | 91 | fontSize: '16px', |
@@ -350,15 +355,14 @@ export function ContrastThemeProvider({ | |||
350 | }: { | 355 | }: { |
351 | children?: ReactNode; | 356 | children?: ReactNode; |
352 | }): JSX.Element { | 357 | }): JSX.Element { |
353 | const theme = useTheme(); | ||
354 | const contrastTheme = useContext(ContrastThemeContext); | 358 | const contrastTheme = useContext(ContrastThemeContext); |
355 | if (!contrastTheme) { | 359 | if (!contrastTheme) { |
356 | throw new Error('ContrastThemeProvider must be used within ThemeProvider'); | 360 | throw new Error('ContrastThemeProvider must be used within ThemeProvider'); |
357 | } | 361 | } |
358 | return ( | 362 | return ( |
359 | <ThemeAndContrastThemeProvider theme={contrastTheme} contrastTheme={theme}> | 363 | <MaterialUiThemeProvider theme={contrastTheme}> |
360 | {children} | 364 | {children} |
361 | </ThemeAndContrastThemeProvider> | 365 | </MaterialUiThemeProvider> |
362 | ); | 366 | ); |
363 | } | 367 | } |
364 | 368 | ||
@@ -378,7 +382,7 @@ const ThemeProvider = observer(function ThemeProvider({ | |||
378 | return ( | 382 | return ( |
379 | <ThemeAndContrastThemeProvider | 383 | <ThemeAndContrastThemeProvider |
380 | theme={darkMode ? darkTheme : lightTheme} | 384 | theme={darkMode ? darkTheme : lightTheme} |
381 | contrastTheme={darkMode ? lightTheme : darkTheme} | 385 | contrastTheme={darkTheme} |
382 | > | 386 | > |
383 | {children} | 387 | {children} |
384 | </ThemeAndContrastThemeProvider> | 388 | </ThemeAndContrastThemeProvider> |
diff --git a/subprojects/frontend/src/theme/ThemeStore.ts b/subprojects/frontend/src/theme/ThemeStore.ts index e09d8d99..7c657449 100644 --- a/subprojects/frontend/src/theme/ThemeStore.ts +++ b/subprojects/frontend/src/theme/ThemeStore.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { makeAutoObservable } from 'mobx'; | 7 | import { makeAutoObservable } from 'mobx'; |
2 | 8 | ||
3 | export enum ThemePreference { | 9 | export enum ThemePreference { |
diff --git a/subprojects/frontend/src/utils/CancelledError.ts b/subprojects/frontend/src/utils/CancelledError.ts index ee23676f..96b67af7 100644 --- a/subprojects/frontend/src/utils/CancelledError.ts +++ b/subprojects/frontend/src/utils/CancelledError.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | export default class CancelledError extends Error { | 7 | export default class CancelledError extends Error { |
2 | constructor(message = 'Operation cancelled') { | 8 | constructor(message = 'Operation cancelled') { |
3 | super(message); | 9 | super(message); |
diff --git a/subprojects/frontend/src/utils/PendingTask.ts b/subprojects/frontend/src/utils/PendingTask.ts index fd52cef1..80d1a346 100644 --- a/subprojects/frontend/src/utils/PendingTask.ts +++ b/subprojects/frontend/src/utils/PendingTask.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import TimeoutError from './TimeoutError'; | 7 | import TimeoutError from './TimeoutError'; |
2 | import getLogger from './getLogger'; | 8 | import getLogger from './getLogger'; |
3 | 9 | ||
@@ -20,7 +26,6 @@ export default class PendingTask<T> { | |||
20 | ) { | 26 | ) { |
21 | this.resolveCallback = resolveCallback; | 27 | this.resolveCallback = resolveCallback; |
22 | this.rejectCallback = rejectCallback; | 28 | this.rejectCallback = rejectCallback; |
23 | // @ts-expect-error See https://github.com/mobxjs/mobx/issues/3582 on `@types/node` pollution | ||
24 | this.timeout = setTimeout(() => { | 29 | this.timeout = setTimeout(() => { |
25 | if (!this.resolved) { | 30 | if (!this.resolved) { |
26 | this.reject(new TimeoutError()); | 31 | this.reject(new TimeoutError()); |
diff --git a/subprojects/frontend/src/utils/PriorityMutex.ts b/subprojects/frontend/src/utils/PriorityMutex.ts index 78736141..c1215c76 100644 --- a/subprojects/frontend/src/utils/PriorityMutex.ts +++ b/subprojects/frontend/src/utils/PriorityMutex.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import CancelledError from './CancelledError'; | 7 | import CancelledError from './CancelledError'; |
2 | import PendingTask from './PendingTask'; | 8 | import PendingTask from './PendingTask'; |
3 | import getLogger from './getLogger'; | 9 | import getLogger from './getLogger'; |
diff --git a/subprojects/frontend/src/utils/TimeoutError.ts b/subprojects/frontend/src/utils/TimeoutError.ts index eb800f40..21365502 100644 --- a/subprojects/frontend/src/utils/TimeoutError.ts +++ b/subprojects/frontend/src/utils/TimeoutError.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | export default class TimeoutError extends Error { | 7 | export default class TimeoutError extends Error { |
2 | constructor() { | 8 | constructor() { |
3 | super('Operation timed out'); | 9 | super('Operation timed out'); |
diff --git a/subprojects/frontend/src/utils/getLogger.ts b/subprojects/frontend/src/utils/getLogger.ts index 301fd76d..09b0b17f 100644 --- a/subprojects/frontend/src/utils/getLogger.ts +++ b/subprojects/frontend/src/utils/getLogger.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import styles, { type CSPair } from 'ansi-styles'; | 7 | import styles, { type CSPair } from 'ansi-styles'; |
2 | import log from 'loglevel'; | 8 | import log from 'loglevel'; |
3 | import prefix from 'loglevel-plugin-prefix'; | 9 | import prefix from 'loglevel-plugin-prefix'; |
diff --git a/subprojects/frontend/src/utils/useDelayedSnackbar.ts b/subprojects/frontend/src/utils/useDelayedSnackbar.ts index 54716c0c..3d6df3e3 100644 --- a/subprojects/frontend/src/utils/useDelayedSnackbar.ts +++ b/subprojects/frontend/src/utils/useDelayedSnackbar.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | useSnackbar, | 8 | useSnackbar, |
3 | type SnackbarKey, | 9 | type SnackbarKey, |
@@ -21,7 +27,6 @@ export default function useDelayedSnackbar( | |||
21 | delay = defaultDelay, | 27 | delay = defaultDelay, |
22 | ) => { | 28 | ) => { |
23 | let key: SnackbarKey | undefined; | 29 | let key: SnackbarKey | undefined; |
24 | // @ts-expect-error See https://github.com/mobxjs/mobx/issues/3582 on `@types/node` pollution | ||
25 | let timeout: number | undefined = setTimeout(() => { | 30 | let timeout: number | undefined = setTimeout(() => { |
26 | timeout = undefined; | 31 | timeout = undefined; |
27 | key = enqueueSnackbar(message, options); | 32 | key = enqueueSnackbar(message, options); |
diff --git a/subprojects/frontend/src/xtext/BackendConfig.ts b/subprojects/frontend/src/xtext/BackendConfig.ts index 41737c0b..4c7eac5f 100644 --- a/subprojects/frontend/src/xtext/BackendConfig.ts +++ b/subprojects/frontend/src/xtext/BackendConfig.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ | 7 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ |
2 | 8 | ||
3 | import { z } from 'zod'; | 9 | import { z } from 'zod'; |
diff --git a/subprojects/frontend/src/xtext/ContentAssistService.ts b/subprojects/frontend/src/xtext/ContentAssistService.ts index 78f61c06..fd30c4f9 100644 --- a/subprojects/frontend/src/xtext/ContentAssistService.ts +++ b/subprojects/frontend/src/xtext/ContentAssistService.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { | 7 | import type { |
2 | Completion, | 8 | Completion, |
3 | CompletionContext, | 9 | CompletionContext, |
diff --git a/subprojects/frontend/src/xtext/HighlightingService.ts b/subprojects/frontend/src/xtext/HighlightingService.ts index a126ee40..447f1401 100644 --- a/subprojects/frontend/src/xtext/HighlightingService.ts +++ b/subprojects/frontend/src/xtext/HighlightingService.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type EditorStore from '../editor/EditorStore'; | 7 | import type EditorStore from '../editor/EditorStore'; |
2 | import type { IHighlightRange } from '../editor/semanticHighlighting'; | 8 | import type { IHighlightRange } from '../editor/semanticHighlighting'; |
3 | 9 | ||
diff --git a/subprojects/frontend/src/xtext/OccurrencesService.ts b/subprojects/frontend/src/xtext/OccurrencesService.ts index fc72ead2..c9c6c699 100644 --- a/subprojects/frontend/src/xtext/OccurrencesService.ts +++ b/subprojects/frontend/src/xtext/OccurrencesService.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { Transaction } from '@codemirror/state'; | 7 | import type { Transaction } from '@codemirror/state'; |
2 | import { debounce } from 'lodash-es'; | 8 | import { debounce } from 'lodash-es'; |
3 | import ms from 'ms'; | 9 | import ms from 'ms'; |
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts index 63e28652..ee5ebde2 100644 --- a/subprojects/frontend/src/xtext/UpdateService.ts +++ b/subprojects/frontend/src/xtext/UpdateService.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { ChangeDesc, Transaction } from '@codemirror/state'; | 7 | import type { ChangeDesc, Transaction } from '@codemirror/state'; |
2 | import { debounce } from 'lodash-es'; | 8 | import { debounce } from 'lodash-es'; |
3 | import { nanoid } from 'nanoid'; | 9 | import { nanoid } from 'nanoid'; |
diff --git a/subprojects/frontend/src/xtext/UpdateStateTracker.ts b/subprojects/frontend/src/xtext/UpdateStateTracker.ts index 5d4ce49e..4ce93ed6 100644 --- a/subprojects/frontend/src/xtext/UpdateStateTracker.ts +++ b/subprojects/frontend/src/xtext/UpdateStateTracker.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { | 7 | import { |
2 | type ChangeDesc, | 8 | type ChangeDesc, |
3 | ChangeSet, | 9 | ChangeSet, |
diff --git a/subprojects/frontend/src/xtext/ValidationService.ts b/subprojects/frontend/src/xtext/ValidationService.ts index 72414590..64fb63eb 100644 --- a/subprojects/frontend/src/xtext/ValidationService.ts +++ b/subprojects/frontend/src/xtext/ValidationService.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { Diagnostic } from '@codemirror/lint'; | 7 | import type { Diagnostic } from '@codemirror/lint'; |
2 | 8 | ||
3 | import type EditorStore from '../editor/EditorStore'; | 9 | import type EditorStore from '../editor/EditorStore'; |
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts index 14fb2430..e8181af0 100644 --- a/subprojects/frontend/src/xtext/XtextClient.ts +++ b/subprojects/frontend/src/xtext/XtextClient.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import type { | 7 | import type { |
2 | CompletionContext, | 8 | CompletionContext, |
3 | CompletionResult, | 9 | CompletionResult, |
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts index 6b734546..6bb7eec8 100644 --- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts +++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import { createAtom, makeAutoObservable, observable } from 'mobx'; | 7 | import { createAtom, makeAutoObservable, observable } from 'mobx'; |
2 | import ms from 'ms'; | 8 | import ms from 'ms'; |
3 | import { nanoid } from 'nanoid'; | 9 | import { nanoid } from 'nanoid'; |
diff --git a/subprojects/frontend/src/xtext/fetchBackendConfig.ts b/subprojects/frontend/src/xtext/fetchBackendConfig.ts index 15e976d8..71ff2e63 100644 --- a/subprojects/frontend/src/xtext/fetchBackendConfig.ts +++ b/subprojects/frontend/src/xtext/fetchBackendConfig.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import BackendConfig, { ENDPOINT } from './BackendConfig'; | 7 | import BackendConfig, { ENDPOINT } from './BackendConfig'; |
2 | 8 | ||
3 | export default async function fetchBackendConfig(): Promise<BackendConfig> { | 9 | export default async function fetchBackendConfig(): Promise<BackendConfig> { |
diff --git a/subprojects/frontend/src/xtext/webSocketMachine.ts b/subprojects/frontend/src/xtext/webSocketMachine.ts index 216ed86a..2fb1f52f 100644 --- a/subprojects/frontend/src/xtext/webSocketMachine.ts +++ b/subprojects/frontend/src/xtext/webSocketMachine.ts | |||
@@ -1,5 +1,11 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import ms from 'ms'; | 7 | import ms from 'ms'; |
2 | import { actions, assign, createMachine, RaiseAction } from 'xstate'; | 8 | import { actions, assign, createMachine } from 'xstate'; |
3 | 9 | ||
4 | const { raise } = actions; | 10 | const { raise } = actions; |
5 | 11 | ||
@@ -217,16 +223,15 @@ export default createMachine( | |||
217 | ...context, | 223 | ...context, |
218 | errors: [], | 224 | errors: [], |
219 | })), | 225 | })), |
220 | // Workaround from https://github.com/statelyai/xstate/issues/1414#issuecomment-699972485 | ||
221 | raiseTimeoutError: raise({ | 226 | raiseTimeoutError: raise({ |
222 | type: 'ERROR', | 227 | type: 'ERROR', |
223 | message: 'Open timeout', | 228 | message: 'Open timeout', |
224 | }) as RaiseAction<WebSocketEvent>, | 229 | }), |
225 | raisePromiseRejectionError: (_context, { data }) => | 230 | raisePromiseRejectionError: (_context, { data }) => |
226 | raise({ | 231 | raise<WebSocketContext, WebSocketEvent>({ |
227 | type: 'ERROR', | 232 | type: 'ERROR', |
228 | message: data, | 233 | message: String(data), |
229 | }) as RaiseAction<WebSocketEvent>, | 234 | }), |
230 | }, | 235 | }, |
231 | }, | 236 | }, |
232 | ); | 237 | ); |
diff --git a/subprojects/frontend/src/xtext/xtextMessages.ts b/subprojects/frontend/src/xtext/xtextMessages.ts index ec7a2a31..bbbff064 100644 --- a/subprojects/frontend/src/xtext/xtextMessages.ts +++ b/subprojects/frontend/src/xtext/xtextMessages.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ | 7 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ |
2 | 8 | ||
3 | import { z } from 'zod'; | 9 | import { z } from 'zod'; |
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts index e93c6714..d3b467ad 100644 --- a/subprojects/frontend/src/xtext/xtextServiceResults.ts +++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ | 7 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ |
2 | 8 | ||
3 | import { z } from 'zod'; | 9 | import { z } from 'zod'; |
diff --git a/subprojects/frontend/tsconfig.base.json b/subprojects/frontend/tsconfig.base.json index 585866f1..5ef50b5e 100644 --- a/subprojects/frontend/tsconfig.base.json +++ b/subprojects/frontend/tsconfig.base.json | |||
@@ -1,7 +1,41 @@ | |||
1 | /* | ||
2 | * Copyright (c) Microsoft Corporation. | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | * | ||
7 | * This file is based on | ||
8 | * https://github.com/tsconfig/bases/blob/7db25a41bc5a9c0f66d91f6f3aa28438afcb2f18/bases/strictest.json | ||
9 | * but we moved it inside the project for better tooling support. | ||
10 | */ | ||
1 | { | 11 | { |
2 | "extends": "@tsconfig/node18-strictest-esm/tsconfig.json", | ||
3 | "compilerOptions": { | 12 | "compilerOptions": { |
4 | "useDefineForClassFields": true, | 13 | "strict": true, |
14 | "allowUnusedLabels": false, | ||
15 | "allowUnreachableCode": false, | ||
16 | "exactOptionalPropertyTypes": true, | ||
17 | "noFallthroughCasesInSwitch": true, | ||
18 | "noImplicitOverride": true, | ||
19 | "noImplicitReturns": true, | ||
20 | "noPropertyAccessFromIndexSignature": true, | ||
21 | "noUncheckedIndexedAccess": true, | ||
22 | "noUnusedLocals": true, | ||
23 | "noUnusedParameters": true, | ||
24 | // "verbatimModuleSyntax" is incompatible with `import` syntax in modules | ||
25 | // with CommonJS import resolution, so we use "isolatedModules" only. | ||
26 | // "verbatimModuleSyntax": false, | ||
5 | "isolatedModules": true, | 27 | "isolatedModules": true, |
28 | "checkJs": true, | ||
29 | "esModuleInterop": true, | ||
30 | "skipLibCheck": true, | ||
31 | "forceConsistentCasingInFileNames": true, | ||
32 | "useDefineForClassFields": true, | ||
33 | // Project-specific configuration below. | ||
34 | "module": "es2022", | ||
35 | "moduleResolution": "node", | ||
36 | "incremental": true, | ||
37 | "declaration": true, | ||
38 | "emitDeclarationOnly": true, | ||
39 | "outDir": "build/typescript" | ||
6 | } | 40 | } |
7 | } | 41 | } |
diff --git a/subprojects/frontend/tsconfig.json b/subprojects/frontend/tsconfig.json index 35abd789..06f6d8fe 100644 --- a/subprojects/frontend/tsconfig.json +++ b/subprojects/frontend/tsconfig.json | |||
@@ -1,8 +1,12 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | { | 6 | { |
2 | "extends": "./tsconfig.base.json", | 7 | "extends": "./tsconfig.base.json", |
3 | "compilerOptions": { | 8 | "compilerOptions": { |
4 | "jsx": "react-jsx", | 9 | "jsx": "react-jsx", |
5 | "noEmit": true, | ||
6 | "lib": ["DOM", "DOM.Iterable", "ES2022"], | 10 | "lib": ["DOM", "DOM.Iterable", "ES2022"], |
7 | "types": ["vite/client", "vite-plugin-pwa/client"] | 11 | "types": ["vite/client", "vite-plugin-pwa/client"] |
8 | }, | 12 | }, |
diff --git a/subprojects/frontend/tsconfig.node.json b/subprojects/frontend/tsconfig.node.json index f4908bcb..47feaf97 100644 --- a/subprojects/frontend/tsconfig.node.json +++ b/subprojects/frontend/tsconfig.node.json | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | { | 6 | { |
2 | "extends": "./tsconfig.base.json", | 7 | "extends": "./tsconfig.base.json", |
3 | "compilerOptions": { | 8 | "compilerOptions": { |
@@ -10,6 +15,7 @@ | |||
10 | "include": [ | 15 | "include": [ |
11 | ".eslintrc.cjs", | 16 | ".eslintrc.cjs", |
12 | "config/*.ts", | 17 | "config/*.ts", |
18 | "config/*.cjs", | ||
13 | "prettier.config.cjs", | 19 | "prettier.config.cjs", |
14 | "types/node", | 20 | "types/node", |
15 | "vite.config.ts" | 21 | "vite.config.ts" |
diff --git a/subprojects/frontend/tsconfig.shared.json b/subprojects/frontend/tsconfig.shared.json index b7e1de55..154fe122 100644 --- a/subprojects/frontend/tsconfig.shared.json +++ b/subprojects/frontend/tsconfig.shared.json | |||
@@ -1,13 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | { | 6 | { |
2 | "extends": "./tsconfig.base.json", | 7 | "extends": "./tsconfig.base.json", |
3 | "compilerOptions": { | 8 | "compilerOptions": { |
4 | "composite": true, | 9 | "composite": true, |
5 | "lib": ["ES2022"], | 10 | "lib": ["ES2022"], |
6 | "types": [], | 11 | "types": [], |
7 | "emitDeclarationOnly": true, | ||
8 | "outDir": "build/typescript" | ||
9 | }, | 12 | }, |
10 | "include": [ | 13 | "include": [ |
11 | "src/xtext/BackendConfig.ts", | 14 | "src/xtext/BackendConfig.ts" |
12 | ] | 15 | ] |
13 | } | 16 | } |
diff --git a/subprojects/frontend/types/ImportMeta.d.ts b/subprojects/frontend/types/ImportMeta.d.ts index c32b48f5..f5a32ef1 100644 --- a/subprojects/frontend/types/ImportMeta.d.ts +++ b/subprojects/frontend/types/ImportMeta.d.ts | |||
@@ -1,3 +1,10 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors | ||
3 | * Copyright (c) 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | */ | ||
7 | |||
1 | interface ImportMeta { | 8 | interface ImportMeta { |
2 | env: { | 9 | env: { |
3 | BASE_URL: string; | 10 | BASE_URL: string; |
diff --git a/subprojects/frontend/types/grammar.d.ts b/subprojects/frontend/types/grammar.d.ts index 1480085b..e7a7eebf 100644 --- a/subprojects/frontend/types/grammar.d.ts +++ b/subprojects/frontend/types/grammar.d.ts | |||
@@ -1,3 +1,10 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others | ||
3 | * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | */ | ||
7 | |||
1 | declare module '*.grammar' { | 8 | declare module '*.grammar' { |
2 | import type { LRParser } from '@lezer/lr'; | 9 | import type { LRParser } from '@lezer/lr'; |
3 | 10 | ||
diff --git a/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts b/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts index 9c1ff03e..4ef9f4e3 100644 --- a/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts +++ b/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts | |||
@@ -1,3 +1,10 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others | ||
3 | * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | */ | ||
7 | |||
1 | // We have to explicitly redeclare the type of the `./rollup` ESM export of `@lezer/generator`, | 8 | // We have to explicitly redeclare the type of the `./rollup` ESM export of `@lezer/generator`, |
2 | // because TypeScript can't find it on its own even with `"moduleResolution": "Node16"`. | 9 | // because TypeScript can't find it on its own even with `"moduleResolution": "Node16"`. |
3 | declare module '@lezer/generator/rollup' { | 10 | declare module '@lezer/generator/rollup' { |
diff --git a/subprojects/frontend/types/windowControlsOverlay.d.ts b/subprojects/frontend/types/windowControlsOverlay.d.ts index d8f3182f..2513d620 100644 --- a/subprojects/frontend/types/windowControlsOverlay.d.ts +++ b/subprojects/frontend/types/windowControlsOverlay.d.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | interface WindowControlsOverlayGeometryChangeEvent extends Event { | 7 | interface WindowControlsOverlayGeometryChangeEvent extends Event { |
2 | titlebarAreaRect: DOMRect; | 8 | titlebarAreaRect: DOMRect; |
3 | 9 | ||
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index cd9993cc..9e08ccc4 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -1,3 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
1 | import path from 'node:path'; | 7 | import path from 'node:path'; |
2 | import { fileURLToPath } from 'node:url'; | 8 | import { fileURLToPath } from 'node:url'; |
3 | 9 | ||
diff --git a/subprojects/language-ide/build.gradle b/subprojects/language-ide/build.gradle deleted file mode 100644 index 3786762b..00000000 --- a/subprojects/language-ide/build.gradle +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-library' | ||
3 | id 'refinery-xtext-conventions' | ||
4 | } | ||
5 | |||
6 | dependencies { | ||
7 | api project(':refinery-language') | ||
8 | api libs.xtext.ide | ||
9 | api libs.xtext.xbase.ide | ||
10 | } | ||
11 | |||
12 | def generateXtextLanguage = project(':refinery-language').tasks.named('generateXtextLanguage') | ||
13 | |||
14 | for (taskName in ['compileJava', 'processResources']) { | ||
15 | tasks.named(taskName) { | ||
16 | dependsOn generateXtextLanguage | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/language-ide/build.gradle.kts b/subprojects/language-ide/build.gradle.kts new file mode 100644 index 00000000..1259cd67 --- /dev/null +++ b/subprojects/language-ide/build.gradle.kts | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-library") | ||
9 | id("tools.refinery.gradle.xtext-generated") | ||
10 | } | ||
11 | |||
12 | dependencies { | ||
13 | api(project(":refinery-language")) | ||
14 | api(libs.xtext.ide) | ||
15 | api(libs.xtext.xbase.ide) | ||
16 | xtextGenerated(project(":refinery-language", "generatedIdeSources")) | ||
17 | } | ||
18 | |||
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java index fb620065..122fe874 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.ide; | 10 | package tools.refinery.language.ide; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeSetup.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeSetup.java index 5b88d41f..9d77e022 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeSetup.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeSetup.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.ide; | 10 | package tools.refinery.language.ide; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java index fe722ca1..4511223b 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.ide.contentassist; | 6 | package tools.refinery.language.ide.contentassist; |
2 | 7 | ||
3 | import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; | 8 | import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java index 8f04ed00..e194ee31 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.ide.contentassist; | 6 | package tools.refinery.language.ide.contentassist; |
2 | 7 | ||
3 | import java.util.Objects; | 8 | import java.util.Objects; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingPartialProblemContentAssistParser.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingPartialProblemContentAssistParser.java index 3ece6f67..146bd8da 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingPartialProblemContentAssistParser.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingPartialProblemContentAssistParser.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.ide.contentassist; | 6 | package tools.refinery.language.ide.contentassist; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingProblemParser.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingProblemParser.java index 80dfee5c..f906d881 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingProblemParser.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingProblemParser.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.ide.contentassist; | 6 | package tools.refinery.language.ide.contentassist; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/antlr/ProblemTokenSource.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/antlr/ProblemTokenSource.java index c6c7f41f..fdc17c4f 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/antlr/ProblemTokenSource.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/antlr/ProblemTokenSource.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.29.0.M2 | 8 | * generated by Xtext 2.29.0.M2 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.ide.contentassist.antlr; | 10 | package tools.refinery.language.ide.contentassist.antlr; |
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java index 7703e4e3..e8f97d51 100644 --- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java +++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.ide.syntaxcoloring; | 6 | package tools.refinery.language.ide.syntaxcoloring; |
2 | 7 | ||
3 | import com.google.common.collect.ImmutableList; | 8 | import com.google.common.collect.ImmutableList; |
diff --git a/subprojects/language-model/META-INF/MANIFEST.MF b/subprojects/language-model/META-INF/MANIFEST.MF index a9c6340a..5d36a475 100644 --- a/subprojects/language-model/META-INF/MANIFEST.MF +++ b/subprojects/language-model/META-INF/MANIFEST.MF | |||
@@ -7,7 +7,7 @@ Bundle-Version: 0.0.0.qualifier | |||
7 | Bundle-ClassPath: . | 7 | Bundle-ClassPath: . |
8 | Bundle-Vendor: %providerName | 8 | Bundle-Vendor: %providerName |
9 | Bundle-Localization: plugin | 9 | Bundle-Localization: plugin |
10 | Bundle-RequiredExecutionEnvironment: JavaSE-17 | 10 | Bundle-RequiredExecutionEnvironment: J2SE-1.5 |
11 | Export-Package: tools.refinery.language.model, | 11 | Export-Package: tools.refinery.language.model, |
12 | tools.refinery.language.model.problem, | 12 | tools.refinery.language.model.problem, |
13 | tools.refinery.language.model.problem.impl, | 13 | tools.refinery.language.model.problem.impl, |
diff --git a/subprojects/language-model/META-INF/MANIFEST.MF.license b/subprojects/language-model/META-INF/MANIFEST.MF.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/language-model/META-INF/MANIFEST.MF.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/language-model/build.gradle b/subprojects/language-model/build.gradle deleted file mode 100644 index 275db188..00000000 --- a/subprojects/language-model/build.gradle +++ /dev/null | |||
@@ -1,55 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-library' | ||
3 | id 'refinery-mwe2' | ||
4 | id 'refinery-sonarqube' | ||
5 | } | ||
6 | |||
7 | dependencies { | ||
8 | api libs.ecore | ||
9 | api libs.ecore.xmi | ||
10 | mwe2 libs.ecore.codegen | ||
11 | mwe2 libs.mwe.utils | ||
12 | mwe2 libs.mwe2.lib | ||
13 | mwe2 libs.xtext.core | ||
14 | mwe2 libs.xtext.xbase | ||
15 | } | ||
16 | |||
17 | sourceSets { | ||
18 | main { | ||
19 | java.srcDirs += ['src/main/emf-gen'] | ||
20 | } | ||
21 | } | ||
22 | |||
23 | def generateEPackage = tasks.register('generateEPackage', JavaExec) { | ||
24 | mainClass = 'org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher' | ||
25 | classpath = configurations.mwe2 | ||
26 | inputs.file 'src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2' | ||
27 | inputs.file 'src/main/resources/model/problem.ecore' | ||
28 | inputs.file 'src/main/resources/model/problem.genmodel' | ||
29 | outputs.dir 'src/main/emf-gen' | ||
30 | args += 'src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2' | ||
31 | args += '-p' | ||
32 | args += "rootPath=/${projectDir}" | ||
33 | } | ||
34 | |||
35 | for (taskName in ['compileJava', 'processResources', 'generateEclipseSourceFolders']) { | ||
36 | tasks.named(taskName) { | ||
37 | dependsOn generateEPackage | ||
38 | } | ||
39 | } | ||
40 | |||
41 | tasks.named('clean') { | ||
42 | delete 'src/main/emf-gen' | ||
43 | } | ||
44 | |||
45 | sonarqube.properties { | ||
46 | properties['sonar.exclusions'] += [ | ||
47 | 'src/main/emf-gen/**', | ||
48 | ] | ||
49 | } | ||
50 | |||
51 | eclipse.project.natures += [ | ||
52 | 'org.eclipse.sirius.nature.modelingproject', | ||
53 | 'org.eclipse.pde.PluginNature', | ||
54 | 'org.eclipse.xtext.ui.shared.xtextNature' | ||
55 | ] | ||
diff --git a/subprojects/language-model/build.gradle.kts b/subprojects/language-model/build.gradle.kts new file mode 100644 index 00000000..59ff9046 --- /dev/null +++ b/subprojects/language-model/build.gradle.kts | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import tools.refinery.gradle.utils.SonarPropertiesUtils | ||
8 | |||
9 | plugins { | ||
10 | id("tools.refinery.gradle.java-library") | ||
11 | id("tools.refinery.gradle.mwe2") | ||
12 | id("tools.refinery.gradle.sonarqube") | ||
13 | } | ||
14 | |||
15 | dependencies { | ||
16 | api(libs.ecore) | ||
17 | api(libs.ecore.xmi) | ||
18 | mwe2(libs.ecore.codegen) | ||
19 | mwe2(libs.mwe.utils) | ||
20 | mwe2(libs.mwe2.lib) | ||
21 | mwe2(libs.xtext.core) | ||
22 | mwe2(libs.xtext.xbase) | ||
23 | } | ||
24 | |||
25 | sourceSets { | ||
26 | main { | ||
27 | java.srcDir("src/main/emf-gen") | ||
28 | } | ||
29 | } | ||
30 | |||
31 | tasks { | ||
32 | val generateEPackage by registering(JavaExec::class) { | ||
33 | mainClass.set("org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher") | ||
34 | classpath(configurations.mwe2) | ||
35 | inputs.file("src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2") | ||
36 | inputs.file("src/main/resources/model/problem.ecore") | ||
37 | inputs.file("src/main/resources/model/problem.genmodel") | ||
38 | outputs.file("build.properties") | ||
39 | outputs.file("META-INF/MANIFEST.MF") | ||
40 | outputs.file("plugin.xml") | ||
41 | outputs.file("plugin.properties") | ||
42 | outputs.dir("src/main/emf-gen") | ||
43 | args("src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2", "-p", "rootPath=/$projectDir") | ||
44 | } | ||
45 | |||
46 | for (taskName in listOf("compileJava", "processResources", "generateEclipseSourceFolders")) { | ||
47 | named(taskName) { | ||
48 | dependsOn(generateEPackage) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | clean { | ||
53 | delete("src/main/emf-gen") | ||
54 | } | ||
55 | } | ||
56 | |||
57 | sonarqube.properties { | ||
58 | SonarPropertiesUtils.addToList(properties, "sonar.exclusions", "src/main/emf-gen/**") | ||
59 | } | ||
60 | |||
61 | eclipse.project.natures.plusAssign(listOf( | ||
62 | "org.eclipse.sirius.nature.modelingproject", | ||
63 | "org.eclipse.pde.PluginNature", | ||
64 | "org.eclipse.xtext.ui.shared.xtextNature", | ||
65 | )) | ||
diff --git a/subprojects/language-model/build.properties b/subprojects/language-model/build.properties index 65dfc7c4..9b9859ff 100644 --- a/subprojects/language-model/build.properties +++ b/subprojects/language-model/build.properties | |||
@@ -1,4 +1,6 @@ | |||
1 | # SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
1 | # | 2 | # |
3 | # SPDX-License-Identifier: EPL-2.0 | ||
2 | 4 | ||
3 | bin.includes = .,\ | 5 | bin.includes = .,\ |
4 | src/main/resources/model/,\ | 6 | src/main/resources/model/,\ |
diff --git a/subprojects/language-model/plugin.properties b/subprojects/language-model/plugin.properties index c4fb7e23..c410feb7 100644 --- a/subprojects/language-model/plugin.properties +++ b/subprojects/language-model/plugin.properties | |||
@@ -1,4 +1,6 @@ | |||
1 | # SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
1 | # | 2 | # |
3 | # SPDX-License-Identifier: EPL-2.0 | ||
2 | 4 | ||
3 | pluginName = tools.refinery.language.model | 5 | pluginName = tools.refinery.language.model |
4 | providerName = refinery.tools | 6 | providerName = refinery.tools |
diff --git a/subprojects/language-model/plugin.xml b/subprojects/language-model/plugin.xml index 4ca005a8..ef1e21f4 100644 --- a/subprojects/language-model/plugin.xml +++ b/subprojects/language-model/plugin.xml | |||
@@ -2,6 +2,9 @@ | |||
2 | <?eclipse version="3.0"?> | 2 | <?eclipse version="3.0"?> |
3 | 3 | ||
4 | <!-- | 4 | <!-- |
5 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
6 | |||
7 | SPDX-License-Identifier: EPL-2.0 | ||
5 | --> | 8 | --> |
6 | 9 | ||
7 | <plugin> | 10 | <plugin> |
diff --git a/subprojects/language-model/problem.aird.license b/subprojects/language-model/problem.aird.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/language-model/problem.aird.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/language-model/src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2 b/subprojects/language-model/src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2 index 15198d69..074b6b71 100644 --- a/subprojects/language-model/src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2 +++ b/subprojects/language-model/src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe2 | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | module tools.refinery.language.model.GenerateProblemModel | 6 | module tools.refinery.language.model.GenerateProblemModel |
2 | 7 | ||
3 | Workflow { | 8 | Workflow { |
diff --git a/subprojects/language-model/src/main/resources/model/problem.ecore.license b/subprojects/language-model/src/main/resources/model/problem.ecore.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/language-model/src/main/resources/model/problem.ecore.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/language-model/src/main/resources/model/problem.genmodel.license b/subprojects/language-model/src/main/resources/model/problem.genmodel.license new file mode 100644 index 00000000..e5db6ccd --- /dev/null +++ b/subprojects/language-model/src/main/resources/model/problem.genmodel.license | |||
@@ -0,0 +1,3 @@ | |||
1 | SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | |||
3 | SPDX-License-Identifier: EPL-2.0 | ||
diff --git a/subprojects/language-semantics/build.gradle b/subprojects/language-semantics/build.gradle deleted file mode 100644 index 4f43ad24..00000000 --- a/subprojects/language-semantics/build.gradle +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-library' | ||
3 | } | ||
4 | |||
5 | dependencies { | ||
6 | implementation libs.eclipseCollections | ||
7 | implementation libs.eclipseCollections.api | ||
8 | api project(':refinery-language') | ||
9 | api project(':refinery-store') | ||
10 | testImplementation testFixtures(project(':refinery-language')) | ||
11 | } | ||
diff --git a/subprojects/language-semantics/build.gradle.kts b/subprojects/language-semantics/build.gradle.kts new file mode 100644 index 00000000..38cd9e0d --- /dev/null +++ b/subprojects/language-semantics/build.gradle.kts | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-library") | ||
9 | } | ||
10 | |||
11 | dependencies { | ||
12 | implementation(libs.eclipseCollections) | ||
13 | implementation(libs.eclipseCollections.api) | ||
14 | api(project(":refinery-language")) | ||
15 | api(project(":refinery-store")) | ||
16 | testImplementation(testFixtures(project(":refinery-language"))) | ||
17 | } | ||
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 a6712a89..06b8ad77 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 | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model; | 6 | package tools.refinery.language.semantics.model; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
@@ -39,8 +44,8 @@ public class ModelInitializer { | |||
39 | var isEqualsRelation = relation == builtinSymbols.equals(); | 44 | var isEqualsRelation = relation == builtinSymbols.equals(); |
40 | var decisionTree = mergeAssertions(relationInfo, isEqualsRelation); | 45 | var decisionTree = mergeAssertions(relationInfo, isEqualsRelation); |
41 | var defaultValue = isEqualsRelation ? TruthValue.FALSE : TruthValue.UNKNOWN; | 46 | var defaultValue = isEqualsRelation ? TruthValue.FALSE : TruthValue.UNKNOWN; |
42 | relationTrace.put(relation, new Symbol<>(relationInfo.name(), relationInfo.arity(), TruthValue.class, defaultValue | 47 | relationTrace.put(relation, Symbol.of( |
43 | )); | 48 | relationInfo.name(), relationInfo.arity(), TruthValue.class, defaultValue)); |
44 | } | 49 | } |
45 | } | 50 | } |
46 | 51 | ||
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java index 55edee6d..c1afecf9 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.internal; | 6 | package tools.refinery.language.semantics.model.internal; |
2 | 7 | ||
3 | import org.eclipse.collections.api.factory.primitive.IntObjectMaps; | 8 | import org.eclipse.collections.api.factory.primitive.IntObjectMaps; |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java index fdf8e452..9a1e15a3 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.internal; | 6 | package tools.refinery.language.semantics.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java index b81ea3fe..3c54e3c5 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.internal; | 6 | package tools.refinery.language.semantics.model.internal; |
2 | 7 | ||
3 | import org.eclipse.collections.api.LazyIntIterable; | 8 | import org.eclipse.collections.api.LazyIntIterable; |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java index 495a53dd..915ae2bf 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.internal; | 6 | package tools.refinery.language.semantics.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.representation.TruthValue; | 8 | import tools.refinery.store.representation.TruthValue; |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java index c4200509..e6f01d48 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.internal; | 6 | package tools.refinery.language.semantics.model.internal; |
2 | 7 | ||
3 | import org.eclipse.collections.api.LazyIntIterable; | 8 | import org.eclipse.collections.api.LazyIntIterable; |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java index 4af836ff..ce49aa62 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.internal; | 6 | package tools.refinery.language.semantics.model.internal; |
2 | 7 | ||
3 | import org.eclipse.collections.api.LazyIntIterable; | 8 | import org.eclipse.collections.api.LazyIntIterable; |
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java index 4630bf53..b3fcbabb 100644 --- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java +++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.semantics.model.tests; | 6 | package tools.refinery.language.semantics.model.tests; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/language-web/build.gradle b/subprojects/language-web/build.gradle deleted file mode 100644 index 8d277a5b..00000000 --- a/subprojects/language-web/build.gradle +++ /dev/null | |||
@@ -1,87 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-application' | ||
3 | id 'refinery-xtext-conventions' | ||
4 | } | ||
5 | |||
6 | configurations { | ||
7 | webapp { | ||
8 | canBeConsumed = false | ||
9 | canBeResolved = true | ||
10 | } | ||
11 | |||
12 | all { | ||
13 | // Use log4j-over-slf4j instead of log4j 1.x | ||
14 | exclude group: 'log4j', module: 'log4j' | ||
15 | } | ||
16 | } | ||
17 | |||
18 | dependencies { | ||
19 | implementation project(':refinery-language') | ||
20 | implementation project(':refinery-language-ide') | ||
21 | implementation libs.jetty.server | ||
22 | implementation libs.jetty.servlet | ||
23 | implementation libs.jetty.websocket.server | ||
24 | implementation libs.slf4j.api | ||
25 | implementation libs.slf4j.simple | ||
26 | implementation libs.slf4j.log4j | ||
27 | implementation libs.xtext.web | ||
28 | webapp project(path: ':refinery-frontend', configuration: 'productionAssets') | ||
29 | testImplementation testFixtures(project(':refinery-language')) | ||
30 | testImplementation libs.jetty.websocket.client | ||
31 | } | ||
32 | |||
33 | def generateXtextLanguage = project(':refinery-language').tasks.named('generateXtextLanguage') | ||
34 | |||
35 | for (taskName in ['compileJava', 'processResources']) { | ||
36 | tasks.named(taskName) { | ||
37 | dependsOn generateXtextLanguage | ||
38 | } | ||
39 | } | ||
40 | |||
41 | mainClassName = 'tools.refinery.language.web.ServerLauncher' | ||
42 | |||
43 | // Enable JDK 19 preview features for virtual thread support. | ||
44 | application { | ||
45 | applicationDefaultJvmArgs += '--enable-preview' | ||
46 | } | ||
47 | tasks.withType(JavaCompile) { | ||
48 | options.release = 19 | ||
49 | options.compilerArgs += '--enable-preview' | ||
50 | } | ||
51 | tasks.withType(Test) { | ||
52 | jvmArgs += '--enable-preview' | ||
53 | } | ||
54 | |||
55 | tasks.named('jar') { | ||
56 | dependsOn project.configurations.webapp | ||
57 | from(project.configurations.webapp) { | ||
58 | into 'webapp' | ||
59 | } | ||
60 | } | ||
61 | |||
62 | tasks.named('shadowJar') { | ||
63 | dependsOn project.configurations.webapp | ||
64 | from(project.sourceSets.main.output) | ||
65 | configurations = [project.configurations.runtimeClasspath] | ||
66 | exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA','schema/*', | ||
67 | '.options', '.api_description', '*.profile', 'about.*', 'about_*.html', 'about_files/*', | ||
68 | 'plugin.xml', 'systembundle.properties', 'profile.list', 'META-INF/resources/xtext/**') | ||
69 | append('plugin.properties') | ||
70 | from(project.configurations.webapp) { | ||
71 | into 'webapp' | ||
72 | } | ||
73 | } | ||
74 | |||
75 | tasks.register('serveBackend', JavaExec) { | ||
76 | dependsOn project.configurations.webapp | ||
77 | dependsOn sourceSets.main.runtimeClasspath | ||
78 | classpath = sourceSets.main.runtimeClasspath | ||
79 | mainClass = mainClassName | ||
80 | // Enable JDK 19 preview features for virtual thread support. | ||
81 | jvmArgs += '--enable-preview' | ||
82 | standardInput = System.in | ||
83 | def baseResource = project.configurations.webapp.incoming.artifacts.artifactFiles.first() | ||
84 | environment BASE_RESOURCE: baseResource | ||
85 | group = 'run' | ||
86 | description = 'Start a Jetty web server serving the Xtex API and assets.' | ||
87 | } | ||
diff --git a/subprojects/language-web/build.gradle.kts b/subprojects/language-web/build.gradle.kts new file mode 100644 index 00000000..562a1bd9 --- /dev/null +++ b/subprojects/language-web/build.gradle.kts | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-application") | ||
9 | id("tools.refinery.gradle.xtext-generated") | ||
10 | } | ||
11 | |||
12 | val webapp: Configuration by configurations.creating { | ||
13 | isCanBeConsumed = false | ||
14 | isCanBeResolved = true | ||
15 | } | ||
16 | |||
17 | dependencies { | ||
18 | implementation(project(":refinery-language")) | ||
19 | implementation(project(":refinery-language-ide")) | ||
20 | implementation(libs.jetty.server) | ||
21 | implementation(libs.jetty.servlet) | ||
22 | implementation(libs.jetty.websocket.api) | ||
23 | implementation(libs.jetty.websocket.server) | ||
24 | implementation(libs.slf4j.api) | ||
25 | implementation(libs.xtext.web) | ||
26 | xtextGenerated(project(":refinery-language", "generatedWebSources")) | ||
27 | webapp(project(":refinery-frontend", "productionAssets")) | ||
28 | testImplementation(testFixtures(project(":refinery-language"))) | ||
29 | testImplementation(libs.jetty.websocket.client) | ||
30 | } | ||
31 | |||
32 | application { | ||
33 | mainClass.set("tools.refinery.language.web.ServerLauncher") | ||
34 | } | ||
35 | |||
36 | tasks { | ||
37 | jar { | ||
38 | dependsOn(webapp) | ||
39 | from(webapp) { | ||
40 | into("webapp") | ||
41 | } | ||
42 | } | ||
43 | |||
44 | shadowJar { | ||
45 | dependsOn(webapp) | ||
46 | from(project.sourceSets.main.map { it.output }) | ||
47 | exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "schema/*", | ||
48 | ".options", ".api_description", "*.profile", "about.*", "about_*.html", "about_files/*", | ||
49 | "plugin.xml", "systembundle.properties", "profile.list", "META-INF/resources/xtext/**") | ||
50 | append("plugin.properties") | ||
51 | from(webapp) { | ||
52 | into("webapp") | ||
53 | } | ||
54 | } | ||
55 | |||
56 | register<JavaExec>("serveBackend") { | ||
57 | dependsOn(webapp) | ||
58 | val mainRuntimeClasspath = sourceSets.main.map { it.runtimeClasspath } | ||
59 | dependsOn(mainRuntimeClasspath) | ||
60 | classpath(mainRuntimeClasspath) | ||
61 | mainClass.set(application.mainClass) | ||
62 | standardInput = System.`in` | ||
63 | val baseResource = webapp.incoming.artifacts.artifactFiles.first() | ||
64 | environment("BASE_RESOURCE", baseResource) | ||
65 | group = "run" | ||
66 | description = "Start a Jetty web server serving the Xtex API and assets." | ||
67 | } | ||
68 | } | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java index fd2af1b2..53f78c3c 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web; | 6 | package tools.refinery.language.web; |
2 | 7 | ||
3 | import jakarta.servlet.*; | 8 | import jakarta.servlet.*; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java index 706413a9..b0197c01 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java | |||
@@ -1,15 +1,19 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.web; | 10 | package tools.refinery.language.web; |
5 | 11 | ||
6 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
7 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; | 12 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; |
8 | import org.eclipse.xtext.web.server.model.IWebDocumentProvider; | 13 | import org.eclipse.xtext.web.server.model.IWebDocumentProvider; |
9 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; | 14 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; |
10 | import org.eclipse.xtext.web.server.occurrences.OccurrencesService; | 15 | import org.eclipse.xtext.web.server.occurrences.OccurrencesService; |
11 | import tools.refinery.language.web.occurrences.ProblemOccurrencesService; | 16 | import tools.refinery.language.web.occurrences.ProblemOccurrencesService; |
12 | import tools.refinery.language.web.xtext.VirtualThreadExecutorServiceProvider; | ||
13 | import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; | 17 | import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; |
14 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; | 18 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; |
15 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; | 19 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; |
@@ -33,8 +37,4 @@ public class ProblemWebModule extends AbstractProblemWebModule { | |||
33 | public Class<? extends OccurrencesService> bindOccurrencesService() { | 37 | public Class<? extends OccurrencesService> bindOccurrencesService() { |
34 | return ProblemOccurrencesService.class; | 38 | return ProblemOccurrencesService.class; |
35 | } | 39 | } |
36 | |||
37 | public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() { | ||
38 | return VirtualThreadExecutorServiceProvider.class; | ||
39 | } | ||
40 | } | 40 | } |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java index 4738bc80..53a394d8 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.web; | 10 | package tools.refinery.language.web; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java index df67b521..7b48cde8 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web; | 6 | package tools.refinery.language.web; |
2 | 7 | ||
3 | import org.eclipse.xtext.util.DisposableRegistry; | 8 | import org.eclipse.xtext.util.DisposableRegistry; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java index c41db799..7b094fde 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web; | 6 | package tools.refinery.language.web; |
2 | 7 | ||
3 | import jakarta.servlet.*; | 8 | import jakarta.servlet.*; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java index f49f46ee..ad19e77d 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.web; | 10 | package tools.refinery.language.web; |
@@ -13,6 +19,7 @@ import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletConta | |||
13 | import org.eclipse.jetty.server.Server; | 19 | import org.eclipse.jetty.server.Server; |
14 | import org.eclipse.jetty.util.resource.Resource; | 20 | import org.eclipse.jetty.util.resource.Resource; |
15 | import org.eclipse.jetty.util.resource.ResourceFactory; | 21 | import org.eclipse.jetty.util.resource.ResourceFactory; |
22 | import org.eclipse.jetty.util.thread.QueuedThreadPool; | ||
16 | import org.slf4j.Logger; | 23 | import org.slf4j.Logger; |
17 | import org.slf4j.LoggerFactory; | 24 | import org.slf4j.LoggerFactory; |
18 | import tools.refinery.language.web.config.BackendConfigServlet; | 25 | import tools.refinery.language.web.config.BackendConfigServlet; |
@@ -43,7 +50,8 @@ public class ServerLauncher { | |||
43 | private final Server server; | 50 | private final Server server; |
44 | 51 | ||
45 | public ServerLauncher(InetSocketAddress bindAddress, String[] allowedOrigins, String webSocketUrl) { | 52 | public ServerLauncher(InetSocketAddress bindAddress, String[] allowedOrigins, String webSocketUrl) { |
46 | server = VirtualThreadUtils.newServerWithVirtualThreadsThreadPool("jetty", bindAddress); | 53 | server = new Server(bindAddress); |
54 | ((QueuedThreadPool) server.getThreadPool()).setName("jetty"); | ||
47 | var handler = new ServletContextHandler(); | 55 | var handler = new ServletContextHandler(); |
48 | addSessionHandler(handler); | 56 | addSessionHandler(handler); |
49 | addProblemServlet(handler, allowedOrigins); | 57 | addProblemServlet(handler, allowedOrigins); |
@@ -105,7 +113,7 @@ public class ServerLauncher { | |||
105 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); | 113 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); |
106 | if (indexUrlInJar != null) { | 114 | if (indexUrlInJar != null) { |
107 | // If the app is packaged in the jar, serve it. | 115 | // If the app is packaged in the jar, serve it. |
108 | URI webRootUri = null; | 116 | URI webRootUri; |
109 | try { | 117 | try { |
110 | webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); | 118 | webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); |
111 | } catch (URISyntaxException e) { | 119 | } catch (URISyntaxException e) { |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java deleted file mode 100644 index a055e755..00000000 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java +++ /dev/null | |||
@@ -1,52 +0,0 @@ | |||
1 | package tools.refinery.language.web; | ||
2 | |||
3 | import org.eclipse.jetty.server.Server; | ||
4 | import org.eclipse.jetty.server.ServerConnector; | ||
5 | import org.eclipse.jetty.util.thread.QueuedThreadPool; | ||
6 | import org.eclipse.jetty.util.thread.ThreadPool; | ||
7 | |||
8 | import java.net.InetSocketAddress; | ||
9 | import java.time.Duration; | ||
10 | import java.util.concurrent.ExecutorService; | ||
11 | import java.util.concurrent.Executors; | ||
12 | |||
13 | public final class VirtualThreadUtils { | ||
14 | private VirtualThreadUtils() { | ||
15 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
16 | } | ||
17 | |||
18 | public static ExecutorService newNamedVirtualThreadsExecutor(String name) { | ||
19 | // Based on | ||
20 | // https://github.com/eclipse/jetty.project/blob/83154b4ffe4767ef44981598d6c26e6a5d32e57c/jetty-server/src/main/config/etc/jetty-threadpool-virtual-preview.xml | ||
21 | return Executors.newThreadPerTaskExecutor(Thread.ofVirtual() | ||
22 | .allowSetThreadLocals(true) | ||
23 | .inheritInheritableThreadLocals(false) | ||
24 | .name(name + "-virtual-", 0) | ||
25 | .factory()); | ||
26 | } | ||
27 | |||
28 | public static ThreadPool newThreadPoolWithVirtualThreadsExecutor(String name) { | ||
29 | // Based on | ||
30 | // https://github.com/eclipse/jetty.project/blob/83154b4ffe4767ef44981598d6c26e6a5d32e57c/jetty-server/src/main/config/etc/jetty-threadpool-virtual-preview.xml | ||
31 | int timeout = (int) Duration.ofMinutes(1).toMillis(); | ||
32 | var threadPool = new QueuedThreadPool(200, 10, timeout, -1, null, null); | ||
33 | threadPool.setName(name); | ||
34 | threadPool.setDetailedDump(false); | ||
35 | threadPool.setVirtualThreadsExecutor(newNamedVirtualThreadsExecutor(name)); | ||
36 | return threadPool; | ||
37 | } | ||
38 | |||
39 | public static Server newServerWithVirtualThreadsThreadPool(String name, InetSocketAddress listenAddress) { | ||
40 | var server = new Server(newThreadPoolWithVirtualThreadsExecutor(name)); | ||
41 | var connector = new ServerConnector(server); | ||
42 | try { | ||
43 | connector.setHost(listenAddress.getHostName()); | ||
44 | connector.setPort(listenAddress.getPort()); | ||
45 | server.addConnector(connector); | ||
46 | } catch (Exception e) { | ||
47 | connector.close(); | ||
48 | throw e; | ||
49 | } | ||
50 | return server; | ||
51 | } | ||
52 | } | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java index 2e864998..807b789c 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.config; | 6 | package tools.refinery.language.web.config; |
2 | 7 | ||
3 | import com.google.gson.annotations.SerializedName; | 8 | import com.google.gson.annotations.SerializedName; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java index f314a9fa..a2f04e34 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.config; | 6 | package tools.refinery.language.web.config; |
2 | 7 | ||
3 | import com.google.gson.Gson; | 8 | import com.google.gson.Gson; |
@@ -29,7 +34,7 @@ public class BackendConfigServlet extends HttpServlet { | |||
29 | } | 34 | } |
30 | 35 | ||
31 | @Override | 36 | @Override |
32 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | 37 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { |
33 | resp.setStatus(HttpStatus.OK_200); | 38 | resp.setStatus(HttpStatus.OK_200); |
34 | resp.setContentType("application/json"); | 39 | resp.setContentType("application/json"); |
35 | var writer = resp.getWriter(); | 40 | var writer = resp.getWriter(); |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/occurrences/ProblemOccurrencesService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/occurrences/ProblemOccurrencesService.java index d32bbb54..34117384 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/occurrences/ProblemOccurrencesService.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/occurrences/ProblemOccurrencesService.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.occurrences; | 6 | package tools.refinery.language.web.occurrences; |
2 | 7 | ||
3 | import org.eclipse.emf.ecore.EObject; | 8 | import org.eclipse.emf.ecore.EObject; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java deleted file mode 100644 index abbcbd53..00000000 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | package tools.refinery.language.web.xtext; | ||
2 | |||
3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
4 | import tools.refinery.language.web.VirtualThreadUtils; | ||
5 | |||
6 | import java.util.concurrent.ExecutorService; | ||
7 | |||
8 | public class VirtualThreadExecutorServiceProvider extends ExecutorServiceProvider { | ||
9 | private static final String THREAD_POOL_NAME = "xtextWeb"; | ||
10 | |||
11 | @Override | ||
12 | protected ExecutorService createInstance(String key) { | ||
13 | var name = key == null ? THREAD_POOL_NAME : THREAD_POOL_NAME + "-" + key; | ||
14 | return VirtualThreadUtils.newNamedVirtualThreadsExecutor(name); | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/PongResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/PongResult.java index fe510f51..27b2e04e 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/PongResult.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/PongResult.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server; | 6 | package tools.refinery.language.web.xtext.server; |
2 | 7 | ||
3 | import java.util.Objects; | 8 | import java.util.Objects; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandler.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandler.java index 2a85afe3..3069c2dd 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandler.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandler.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server; | 6 | package tools.refinery.language.web.xtext.server; |
2 | 7 | ||
3 | import tools.refinery.language.web.xtext.server.message.XtextWebResponse; | 8 | import tools.refinery.language.web.xtext.server.message.XtextWebResponse; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java index b686d33a..366ef0a7 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server; | 6 | package tools.refinery.language.web.xtext.server; |
2 | 7 | ||
3 | import java.io.Serial; | 8 | import java.io.Serial; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/SubscribingServiceContext.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/SubscribingServiceContext.java index 78e00a9e..04212b84 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/SubscribingServiceContext.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/SubscribingServiceContext.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server; | 6 | package tools.refinery.language.web.xtext.server; |
2 | 7 | ||
3 | import java.util.Set; | 8 | import java.util.Set; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java index 7bb11d2e..0135d8f5 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server; | 6 | package tools.refinery.language.web.xtext.server; |
2 | 7 | ||
3 | import com.google.common.base.Strings; | 8 | import com.google.common.base.Strings; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorKind.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorKind.java index f74bae74..6f4f265c 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorKind.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorKind.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
2 | 7 | ||
3 | import com.google.gson.annotations.SerializedName; | 8 | import com.google.gson.annotations.SerializedName; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorResponse.java index 01d78c31..af38ad70 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorResponse.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorResponse.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
2 | 7 | ||
3 | import java.util.Objects; | 8 | import java.util.Objects; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java index 8af27247..73527ee5 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
2 | 7 | ||
3 | import java.util.Objects; | 8 | import java.util.Objects; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebPushMessage.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebPushMessage.java index c9432e1c..e9ff87c4 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebPushMessage.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebPushMessage.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
2 | 7 | ||
3 | import java.util.Objects; | 8 | import java.util.Objects; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java index 959749f8..ff788e94 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
2 | 7 | ||
3 | import java.util.Map; | 8 | import java.util.Map; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java index 3bd13047..61444c99 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
2 | 7 | ||
3 | public sealed interface XtextWebResponse permits XtextWebOkResponse,XtextWebErrorResponse,XtextWebPushMessage { | 8 | public sealed interface XtextWebResponse permits XtextWebOkResponse,XtextWebErrorResponse,XtextWebPushMessage { |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java index 79a284db..110c8f52 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
2 | 7 | ||
3 | import org.eclipse.xtext.web.server.IServiceResult; | 8 | import org.eclipse.xtext.web.server.IServiceResult; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java index c7b8108d..4c9135c8 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
2 | 7 | ||
3 | import org.eclipse.xtext.web.server.IServiceContext; | 8 | import org.eclipse.xtext.web.server.IServiceContext; |
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 906b9e30..56fd12c9 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 | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
2 | 7 | ||
3 | import java.util.ArrayList; | 8 | import java.util.ArrayList; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java index b3666a86..d9e548cd 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
2 | 7 | ||
3 | import org.eclipse.xtext.service.OperationCanceledManager; | 8 | import org.eclipse.xtext.service.OperationCanceledManager; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java index b6f04748..b6f4fb43 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
2 | 7 | ||
3 | import org.eclipse.xtext.web.server.IServiceContext; | 8 | import org.eclipse.xtext.web.server.IServiceContext; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleServiceContext.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleServiceContext.java index 43e37160..fee1141d 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleServiceContext.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleServiceContext.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
2 | 7 | ||
3 | import java.util.Map; | 8 | import java.util.Map; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleSession.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleSession.java index 09c055a2..bc60c282 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleSession.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleSession.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
2 | 7 | ||
3 | import java.util.HashMap; | 8 | import java.util.HashMap; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextStatusCode.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextStatusCode.java index 0cd229e8..caa98e84 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextStatusCode.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextStatusCode.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
2 | 7 | ||
3 | public final class XtextStatusCode { | 8 | public final class XtextStatusCode { |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java index 1d9e0463..043d318c 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java | |||
@@ -1,12 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
2 | 7 | ||
3 | import com.google.gson.Gson; | 8 | import com.google.gson.Gson; |
4 | import com.google.gson.JsonIOException; | 9 | import com.google.gson.JsonIOException; |
5 | import com.google.gson.JsonParseException; | 10 | import com.google.gson.JsonParseException; |
6 | import org.eclipse.jetty.ee10.websocket.api.Session; | 11 | import org.eclipse.jetty.websocket.api.Callback; |
7 | import org.eclipse.jetty.ee10.websocket.api.StatusCode; | 12 | import org.eclipse.jetty.websocket.api.Session; |
8 | import org.eclipse.jetty.ee10.websocket.api.WriteCallback; | 13 | import org.eclipse.jetty.websocket.api.StatusCode; |
9 | import org.eclipse.jetty.ee10.websocket.api.annotations.*; | 14 | import org.eclipse.jetty.websocket.api.annotations.*; |
10 | import org.eclipse.xtext.resource.IResourceServiceProvider; | 15 | import org.eclipse.xtext.resource.IResourceServiceProvider; |
11 | import org.eclipse.xtext.web.server.ISession; | 16 | import org.eclipse.xtext.web.server.ISession; |
12 | import org.slf4j.Logger; | 17 | import org.slf4j.Logger; |
@@ -20,7 +25,7 @@ import tools.refinery.language.web.xtext.server.message.XtextWebResponse; | |||
20 | import java.io.Reader; | 25 | import java.io.Reader; |
21 | 26 | ||
22 | @WebSocket | 27 | @WebSocket |
23 | public class XtextWebSocket implements WriteCallback, ResponseHandler { | 28 | public class XtextWebSocket implements ResponseHandler { |
24 | private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); | 29 | private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); |
25 | 30 | ||
26 | private final Gson gson = new Gson(); | 31 | private final Gson gson = new Gson(); |
@@ -38,13 +43,13 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
38 | this(new TransactionExecutor(session, resourceServiceProviderRegistry)); | 43 | this(new TransactionExecutor(session, resourceServiceProviderRegistry)); |
39 | } | 44 | } |
40 | 45 | ||
41 | @OnWebSocketConnect | 46 | @OnWebSocketOpen |
42 | public void onConnect(Session webSocketSession) { | 47 | public void onOpen(Session webSocketSession) { |
43 | if (this.webSocketSession != null) { | 48 | if (this.webSocketSession != null) { |
44 | LOG.error("Websocket session onConnect when already connected"); | 49 | LOG.error("Websocket session onConnect when already connected"); |
45 | return; | 50 | return; |
46 | } | 51 | } |
47 | LOG.debug("New websocket connection from {}", webSocketSession.getRemoteAddress()); | 52 | LOG.debug("New websocket connection from {}", webSocketSession.getRemoteSocketAddress()); |
48 | this.webSocketSession = webSocketSession; | 53 | this.webSocketSession = webSocketSession; |
49 | } | 54 | } |
50 | 55 | ||
@@ -55,10 +60,10 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
55 | return; | 60 | return; |
56 | } | 61 | } |
57 | if (statusCode == StatusCode.NORMAL || statusCode == StatusCode.SHUTDOWN) { | 62 | if (statusCode == StatusCode.NORMAL || statusCode == StatusCode.SHUTDOWN) { |
58 | LOG.debug("{} closed connection normally: {}", webSocketSession.getRemoteAddress(), reason); | 63 | LOG.debug("{} closed connection normally: {}", webSocketSession.getRemoteSocketAddress(), reason); |
59 | } else { | 64 | } else { |
60 | LOG.warn("{} closed connection with status code {}: {}", webSocketSession.getRemoteAddress(), statusCode, | 65 | LOG.warn("{} closed connection with status code {}: {}", webSocketSession.getRemoteSocketAddress(), |
61 | reason); | 66 | statusCode, reason); |
62 | } | 67 | } |
63 | webSocketSession = null; | 68 | webSocketSession = null; |
64 | } | 69 | } |
@@ -68,7 +73,7 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
68 | if (webSocketSession == null) { | 73 | if (webSocketSession == null) { |
69 | return; | 74 | return; |
70 | } | 75 | } |
71 | LOG.error("Internal websocket error in connection from" + webSocketSession.getRemoteAddress(), error); | 76 | LOG.error("Internal websocket error in connection from" + webSocketSession.getRemoteSocketAddress(), error); |
72 | } | 77 | } |
73 | 78 | ||
74 | @OnWebSocketMessage | 79 | @OnWebSocketMessage |
@@ -81,14 +86,14 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
81 | try { | 86 | try { |
82 | request = gson.fromJson(reader, XtextWebRequest.class); | 87 | request = gson.fromJson(reader, XtextWebRequest.class); |
83 | } catch (JsonIOException e) { | 88 | } catch (JsonIOException e) { |
84 | LOG.error("Cannot read from websocket from" + webSocketSession.getRemoteAddress(), e); | 89 | LOG.error("Cannot read from websocket from" + webSocketSession.getRemoteSocketAddress(), e); |
85 | if (webSocketSession.isOpen()) { | 90 | if (webSocketSession.isOpen()) { |
86 | webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload"); | 91 | webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload", Callback.NOOP); |
87 | } | 92 | } |
88 | return; | 93 | return; |
89 | } catch (JsonParseException e) { | 94 | } catch (JsonParseException e) { |
90 | LOG.warn("Malformed websocket request from" + webSocketSession.getRemoteAddress(), e); | 95 | LOG.warn("Malformed websocket request from" + webSocketSession.getRemoteSocketAddress(), e); |
91 | webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload"); | 96 | webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload", Callback.NOOP); |
92 | return; | 97 | return; |
93 | } | 98 | } |
94 | try { | 99 | try { |
@@ -96,7 +101,7 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
96 | } catch (ResponseHandlerException e) { | 101 | } catch (ResponseHandlerException e) { |
97 | LOG.warn("Cannot write websocket response", e); | 102 | LOG.warn("Cannot write websocket response", e); |
98 | if (webSocketSession.isOpen()) { | 103 | if (webSocketSession.isOpen()) { |
99 | webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write response"); | 104 | webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write response", Callback.NOOP); |
100 | } | 105 | } |
101 | } | 106 | } |
102 | } | 107 | } |
@@ -107,15 +112,14 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
107 | throw new ResponseHandlerException("Trying to send message when websocket is disconnected"); | 112 | throw new ResponseHandlerException("Trying to send message when websocket is disconnected"); |
108 | } | 113 | } |
109 | var responseString = gson.toJson(response); | 114 | var responseString = gson.toJson(response); |
110 | webSocketSession.getRemote().sendPartialString(responseString, true, this); | 115 | webSocketSession.sendText(responseString, Callback.from(() -> {}, this::writeFailed)); |
111 | } | 116 | } |
112 | 117 | ||
113 | @Override | ||
114 | public void writeFailed(Throwable x) { | 118 | public void writeFailed(Throwable x) { |
115 | if (webSocketSession == null) { | 119 | if (webSocketSession == null) { |
116 | LOG.error("Cannot complete async write to disconnected websocket", x); | 120 | LOG.error("Cannot complete async write to disconnected websocket", x); |
117 | return; | 121 | return; |
118 | } | 122 | } |
119 | LOG.warn("Cannot complete async write to websocket " + webSocketSession.getRemoteAddress(), x); | 123 | LOG.warn("Cannot complete async write to websocket " + webSocketSession.getRemoteSocketAddress(), x); |
120 | } | 124 | } |
121 | } | 125 | } |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java index 9a32b937..5e4fb0ce 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
2 | 7 | ||
3 | import jakarta.servlet.ServletConfig; | 8 | import jakarta.servlet.ServletConfig; |
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 ecbefc4f..927eeab1 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 | |||
@@ -1,20 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web; | 6 | package tools.refinery.language.web; |
2 | 7 | ||
3 | import org.eclipse.jetty.ee10.servlet.ServletContextHandler; | 8 | import org.eclipse.jetty.ee10.servlet.ServletContextHandler; |
4 | import org.eclipse.jetty.ee10.servlet.ServletHolder; | 9 | import org.eclipse.jetty.ee10.servlet.ServletHolder; |
5 | import org.eclipse.jetty.ee10.websocket.api.Session; | ||
6 | import org.eclipse.jetty.ee10.websocket.api.StatusCode; | ||
7 | import org.eclipse.jetty.ee10.websocket.api.annotations.WebSocket; | ||
8 | import org.eclipse.jetty.ee10.websocket.api.exceptions.UpgradeException; | ||
9 | import org.eclipse.jetty.ee10.websocket.client.ClientUpgradeRequest; | ||
10 | import org.eclipse.jetty.ee10.websocket.client.WebSocketClient; | ||
11 | import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; | 10 | import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; |
12 | import org.eclipse.jetty.http.HttpHeader; | 11 | import org.eclipse.jetty.http.HttpHeader; |
13 | import org.eclipse.jetty.http.HttpStatus; | 12 | import org.eclipse.jetty.http.HttpStatus; |
14 | import org.eclipse.jetty.server.Server; | 13 | import org.eclipse.jetty.server.Server; |
14 | import org.eclipse.jetty.util.thread.QueuedThreadPool; | ||
15 | import org.eclipse.jetty.websocket.api.Callback; | ||
16 | import org.eclipse.jetty.websocket.api.Session; | ||
17 | import org.eclipse.jetty.websocket.api.StatusCode; | ||
18 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; | ||
19 | import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; | ||
20 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; | ||
21 | import org.eclipse.jetty.websocket.client.WebSocketClient; | ||
15 | import org.eclipse.xtext.testing.GlobalRegistries; | 22 | import org.eclipse.xtext.testing.GlobalRegistries; |
16 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; | 23 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; |
17 | import org.junit.jupiter.api.*; | 24 | import org.junit.jupiter.api.AfterEach; |
25 | import org.junit.jupiter.api.BeforeEach; | ||
26 | import org.junit.jupiter.api.Test; | ||
27 | import org.junit.jupiter.api.TestInfo; | ||
18 | import org.junit.jupiter.params.ParameterizedTest; | 28 | import org.junit.jupiter.params.ParameterizedTest; |
19 | import org.junit.jupiter.params.provider.ValueSource; | 29 | import org.junit.jupiter.params.provider.ValueSource; |
20 | import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; | 30 | import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; |
@@ -86,23 +96,34 @@ class ProblemWebSocketServletIntegrationTest { | |||
86 | assertThat(responses, hasSize(5)); | 96 | assertThat(responses, hasSize(5)); |
87 | assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); | 97 | assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); |
88 | assertThat(responses.get(1), startsWith( | 98 | assertThat(responses.get(1), startsWith( |
89 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\",\"push\":{\"regions\":[")); | 99 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," + |
100 | "\"push\":{\"regions\":[")); | ||
90 | assertThat(responses.get(2), equalTo( | 101 | assertThat(responses.get(2), equalTo( |
91 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\",\"push\":{\"issues\":[]}}")); | 102 | "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\"," + |
103 | "\"push\":{\"issues\":[]}}")); | ||
92 | assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}")); | 104 | assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}")); |
93 | assertThat(responses.get(4), startsWith( | 105 | assertThat(responses.get(4), startsWith( |
94 | "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\",\"push\":{\"regions\":[")); | 106 | "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," + |
107 | "\"push\":{\"regions\":[")); | ||
95 | } | 108 | } |
96 | 109 | ||
97 | @WebSocket | 110 | @WebSocket |
98 | public static class UpdateTestClient extends WebSocketIntegrationTestClient { | 111 | public static class UpdateTestClient extends WebSocketIntegrationTestClient { |
99 | @Override | 112 | @Override |
100 | protected void arrange(Session session, int responsesReceived) throws IOException { | 113 | protected void arrange(Session session, int responsesReceived) { |
101 | switch (responsesReceived) { | 114 | switch (responsesReceived) { |
102 | case 0 -> session.getRemote().sendString( | 115 | case 0 -> session.sendText( |
103 | "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}"); | 116 | "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\"," + |
104 | case 3 -> session.getRemote().sendString( | 117 | "\"fullText\":\"class Person.\n\"}}", |
105 | "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"indiv q.\nnode(q).\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}"); | 118 | Callback.NOOP |
119 | ); | ||
120 | case 3 -> //noinspection TextBlockMigration | ||
121 | session.sendText( | ||
122 | "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\"," + | ||
123 | "\"requiredStateId\":\"-80000000\",\"deltaText\":\"indiv q.\nnode(q).\n\"," + | ||
124 | "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}", | ||
125 | Callback.NOOP | ||
126 | ); | ||
106 | case 5 -> session.close(); | 127 | case 5 -> session.close(); |
107 | } | 128 | } |
108 | } | 129 | } |
@@ -152,13 +173,13 @@ class ProblemWebSocketServletIntegrationTest { | |||
152 | @WebSocket | 173 | @WebSocket |
153 | public static class InvalidJsonTestClient extends WebSocketIntegrationTestClient { | 174 | public static class InvalidJsonTestClient extends WebSocketIntegrationTestClient { |
154 | @Override | 175 | @Override |
155 | protected void arrange(Session session, int responsesReceived) throws IOException { | 176 | protected void arrange(Session session, int responsesReceived) { |
156 | session.getRemote().sendString("<invalid json>"); | 177 | session.sendText("<invalid json>", Callback.NOOP); |
157 | } | 178 | } |
158 | } | 179 | } |
159 | 180 | ||
160 | @ParameterizedTest(name = "validOriginTest(\"{0}\")") | 181 | @ParameterizedTest(name = "validOriginTest(\"{0}\")") |
161 | @ValueSource(strings = { "https://refinery.example", "https://refinery.example:443", "HTTPS://REFINERY.EXAMPLE" }) | 182 | @ValueSource(strings = {"https://refinery.example", "https://refinery.example:443", "HTTPS://REFINERY.EXAMPLE"}) |
162 | void validOriginTest(String origin) { | 183 | void validOriginTest(String origin) { |
163 | startServer("https://refinery.example,https://refinery.example:443"); | 184 | startServer("https://refinery.example,https://refinery.example:443"); |
164 | var clientSocket = new CloseImmediatelyTestClient(); | 185 | var clientSocket = new CloseImmediatelyTestClient(); |
@@ -188,7 +209,8 @@ class ProblemWebSocketServletIntegrationTest { | |||
188 | private void startServer(String allowedOrigins) { | 209 | private void startServer(String allowedOrigins) { |
189 | var testName = getClass().getSimpleName() + "-" + testInfo.getDisplayName(); | 210 | var testName = getClass().getSimpleName() + "-" + testInfo.getDisplayName(); |
190 | var listenAddress = new InetSocketAddress(HOSTNAME, serverPort); | 211 | var listenAddress = new InetSocketAddress(HOSTNAME, serverPort); |
191 | server = VirtualThreadUtils.newServerWithVirtualThreadsThreadPool(testName, listenAddress); | 212 | server = new Server(listenAddress); |
213 | ((QueuedThreadPool) server.getThreadPool()).setName(testName); | ||
192 | var handler = new ServletContextHandler(); | 214 | var handler = new ServletContextHandler(); |
193 | var holder = new ServletHolder(ProblemWebSocketServlet.class); | 215 | var holder = new ServletHolder(ProblemWebSocketServlet.class); |
194 | if (allowedOrigins != null) { | 216 | if (allowedOrigins != null) { |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java index c634e8fc..52acee6d 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java | |||
@@ -1,14 +1,19 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.tests; | 6 | package tools.refinery.language.web.tests; |
2 | 7 | ||
3 | import com.google.inject.Singleton; | 8 | import com.google.inject.Singleton; |
4 | import tools.refinery.language.web.xtext.VirtualThreadExecutorServiceProvider; | 9 | import org.eclipse.xtext.ide.ExecutorServiceProvider; |
5 | 10 | ||
6 | import java.util.ArrayList; | 11 | import java.util.ArrayList; |
7 | import java.util.List; | 12 | import java.util.List; |
8 | import java.util.concurrent.ExecutorService; | 13 | import java.util.concurrent.ExecutorService; |
9 | 14 | ||
10 | @Singleton | 15 | @Singleton |
11 | public class AwaitTerminationExecutorServiceProvider extends VirtualThreadExecutorServiceProvider { | 16 | public class AwaitTerminationExecutorServiceProvider extends ExecutorServiceProvider { |
12 | private final List<RestartableCachedThreadPool> servicesToShutDown = new ArrayList<>(); | 17 | private final List<RestartableCachedThreadPool> servicesToShutDown = new ArrayList<>(); |
13 | 18 | ||
14 | @Override | 19 | @Override |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java index 43c12faa..4a5eed95 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.tests; | 6 | package tools.refinery.language.web.tests; |
2 | 7 | ||
3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | 8 | import org.eclipse.xtext.ide.ExecutorServiceProvider; |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java index cf805eda..09079aa8 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.tests; | 6 | package tools.refinery.language.web.tests; |
2 | 7 | ||
3 | import com.google.inject.Provider; | 8 | import com.google.inject.Provider; |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java index f19c10ca..6ccf1760 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java | |||
@@ -1,12 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.tests; | 6 | package tools.refinery.language.web.tests; |
2 | 7 | ||
3 | import org.eclipse.jetty.ee10.websocket.api.Session; | 8 | import org.eclipse.jetty.websocket.api.Session; |
4 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketClose; | 9 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; |
5 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketConnect; | 10 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; |
6 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketError; | 11 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; |
7 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketMessage; | 12 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; |
8 | 13 | ||
9 | import java.io.IOException; | ||
10 | import java.time.Duration; | 14 | import java.time.Duration; |
11 | import java.util.ArrayList; | 15 | import java.util.ArrayList; |
12 | import java.util.List; | 16 | import java.util.List; |
@@ -14,7 +18,7 @@ import java.util.List; | |||
14 | import static org.junit.jupiter.api.Assertions.fail; | 18 | import static org.junit.jupiter.api.Assertions.fail; |
15 | 19 | ||
16 | public abstract class WebSocketIntegrationTestClient { | 20 | public abstract class WebSocketIntegrationTestClient { |
17 | private static final long TIMEOUT_MILLIS = Duration.ofSeconds(1).toMillis(); | 21 | private static final long TIMEOUT_MILLIS = Duration.ofSeconds(10).toMillis(); |
18 | 22 | ||
19 | private boolean finished = false; | 23 | private boolean finished = false; |
20 | 24 | ||
@@ -34,8 +38,8 @@ public abstract class WebSocketIntegrationTestClient { | |||
34 | return responses; | 38 | return responses; |
35 | } | 39 | } |
36 | 40 | ||
37 | @OnWebSocketConnect | 41 | @OnWebSocketOpen |
38 | public void onConnect(Session session) { | 42 | public void onOpen(Session session) { |
39 | arrangeAndCatchErrors(session); | 43 | arrangeAndCatchErrors(session); |
40 | } | 44 | } |
41 | 45 | ||
@@ -47,7 +51,7 @@ public abstract class WebSocketIntegrationTestClient { | |||
47 | } | 51 | } |
48 | } | 52 | } |
49 | 53 | ||
50 | protected abstract void arrange(Session session, int responsesReceived) throws IOException; | 54 | protected abstract void arrange(Session session, int responsesReceived); |
51 | 55 | ||
52 | @OnWebSocketClose | 56 | @OnWebSocketClose |
53 | public void onClose(int statusCode, String reason) { | 57 | public void onClose(int statusCode, String reason) { |
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 17f1ff5c..841bacd3 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 | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/build.gradle b/subprojects/language/build.gradle deleted file mode 100644 index 654558e3..00000000 --- a/subprojects/language/build.gradle +++ /dev/null | |||
@@ -1,73 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-library' | ||
3 | id 'refinery-java-test-fixtures' | ||
4 | id 'refinery-mwe2' | ||
5 | id 'refinery-sonarqube' | ||
6 | id 'refinery-xtext-conventions' | ||
7 | } | ||
8 | |||
9 | dependencies { | ||
10 | api platform(libs.xtext.bom) | ||
11 | api libs.ecore | ||
12 | api libs.xtext.core | ||
13 | api libs.xtext.xbase | ||
14 | api project(':refinery-language-model') | ||
15 | testFixturesApi libs.xtext.testing | ||
16 | mwe2 libs.xtext.generator | ||
17 | mwe2 libs.xtext.generator.antlr | ||
18 | } | ||
19 | |||
20 | sourceSets { | ||
21 | testFixtures { | ||
22 | java.srcDirs += ['src/testFixtures/xtext-gen'] | ||
23 | resources.srcDirs += ['src/testFixtures/xtext-gen'] | ||
24 | } | ||
25 | } | ||
26 | |||
27 | tasks.named('jar') { | ||
28 | from(sourceSets.main.allSource) { | ||
29 | include '**/*.xtext' | ||
30 | } | ||
31 | } | ||
32 | |||
33 | def generateXtextLanguage = tasks.register('generateXtextLanguage', JavaExec) { | ||
34 | mainClass = 'org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher' | ||
35 | classpath = configurations.mwe2 | ||
36 | inputs.file 'src/main/java/tools/refinery/language/GenerateProblem.mwe2' | ||
37 | inputs.file 'src/main/java/tools/refinery/language/Problem.xtext' | ||
38 | outputs.dir 'src/main/xtext-gen' | ||
39 | outputs.dir 'src/testFixtures/xtext-gen' | ||
40 | outputs.dir '../language-ide/src/main/xtext-gen' | ||
41 | outputs.dir '../language-web/src/main/xtext-gen' | ||
42 | args += 'src/main/java/tools/refinery/language/GenerateProblem.mwe2' | ||
43 | args += '-p' | ||
44 | args += "rootPath=/${projectDir}/.." | ||
45 | } | ||
46 | |||
47 | for (taskName in [ | ||
48 | 'compileJava', | ||
49 | 'processResources', | ||
50 | 'processTestFixturesResources', | ||
51 | 'generateEclipseSourceFolders' | ||
52 | ]) { | ||
53 | tasks.named(taskName) { | ||
54 | dependsOn generateXtextLanguage | ||
55 | } | ||
56 | } | ||
57 | |||
58 | tasks.named('clean') { | ||
59 | delete 'src/main/xtext-gen' | ||
60 | delete 'src/testFixtures/xtext-gen' | ||
61 | delete '../language-ide/src/main/xtext-gen' | ||
62 | delete '../language-web/src/main/xtext-gen' | ||
63 | } | ||
64 | |||
65 | sonarqube.properties { | ||
66 | properties['sonar.exclusions'] += [ | ||
67 | 'src/testFixtures/xtext-gen/**', | ||
68 | ] | ||
69 | } | ||
70 | |||
71 | eclipse.project.natures += [ | ||
72 | 'org.eclipse.xtext.ui.shared.xtextNature' | ||
73 | ] | ||
diff --git a/subprojects/language/build.gradle.kts b/subprojects/language/build.gradle.kts new file mode 100644 index 00000000..bac1e586 --- /dev/null +++ b/subprojects/language/build.gradle.kts | |||
@@ -0,0 +1,101 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | import tools.refinery.gradle.utils.SonarPropertiesUtils | ||
8 | |||
9 | plugins { | ||
10 | id("tools.refinery.gradle.java-library") | ||
11 | id("tools.refinery.gradle.java-test-fixtures") | ||
12 | id("tools.refinery.gradle.mwe2") | ||
13 | id("tools.refinery.gradle.sonarqube") | ||
14 | id("tools.refinery.gradle.xtext-generated") | ||
15 | } | ||
16 | |||
17 | val generatedIdeSources: Configuration by configurations.creating { | ||
18 | isCanBeConsumed = true | ||
19 | isCanBeResolved = false | ||
20 | } | ||
21 | |||
22 | val generatedWebSources: Configuration by configurations.creating { | ||
23 | isCanBeConsumed = true | ||
24 | isCanBeResolved = false | ||
25 | } | ||
26 | |||
27 | dependencies { | ||
28 | api(platform(libs.xtext.bom)) | ||
29 | api(libs.ecore) | ||
30 | api(libs.xtext.core) | ||
31 | api(libs.xtext.xbase) | ||
32 | api(project(":refinery-language-model")) | ||
33 | testFixturesApi(libs.xtext.testing) | ||
34 | mwe2(libs.xtext.generator) | ||
35 | mwe2(libs.xtext.generator.antlr) | ||
36 | } | ||
37 | |||
38 | sourceSets { | ||
39 | testFixtures { | ||
40 | java.srcDir("src/testFixtures/xtext-gen") | ||
41 | resources.srcDir("src/testFixtures/xtext-gen") | ||
42 | } | ||
43 | } | ||
44 | |||
45 | val generateXtextLanguage by tasks.registering(JavaExec::class) { | ||
46 | mainClass.set("org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher") | ||
47 | classpath(configurations.mwe2) | ||
48 | inputs.file("src/main/java/tools/refinery/language/GenerateProblem.mwe2") | ||
49 | inputs.file("src/main/java/tools/refinery/language/Problem.xtext") | ||
50 | inputs.file("../language-model/src/main/resources/model/problem.ecore") | ||
51 | inputs.file("../language-model/src/main/resources/model/problem.genmodel") | ||
52 | outputs.dir("src/main/xtext-gen") | ||
53 | outputs.dir("src/testFixtures/xtext-gen") | ||
54 | outputs.dir("$buildDir/generated/sources/xtext/ide") | ||
55 | outputs.dir("$buildDir/generated/sources/xtext/web") | ||
56 | args("src/main/java/tools/refinery/language/GenerateProblem.mwe2", "-p", "rootPath=/$projectDir/..") | ||
57 | } | ||
58 | |||
59 | tasks { | ||
60 | jar { | ||
61 | from(sourceSets.main.map { it.allSource }) { | ||
62 | include("**/*.xtext") | ||
63 | } | ||
64 | } | ||
65 | |||
66 | syncXtextGeneratedSources { | ||
67 | // We generate Xtext runtime sources directly to {@code src/main/xtext-gen}, so there is no need to copy them | ||
68 | // from an artifact. We expose the {@code generatedIdeSources} and {@code generatedWebSources} artifacts to | ||
69 | // sibling IDE and web projects which can use this task to consume them and copy the appropriate sources to | ||
70 | // their own {@code src/main/xtext-gen} directory. | ||
71 | enabled = false | ||
72 | } | ||
73 | |||
74 | for (taskName in listOf("compileJava", "processResources", "compileTestFixturesJava", | ||
75 | "processTestFixturesResources", "generateEclipseSourceFolders")) { | ||
76 | named(taskName) { | ||
77 | dependsOn(generateXtextLanguage) | ||
78 | } | ||
79 | } | ||
80 | |||
81 | clean { | ||
82 | delete("src/main/xtext-gen") | ||
83 | delete("src/testFixtures/xtext-gen") | ||
84 | } | ||
85 | } | ||
86 | |||
87 | artifacts { | ||
88 | add(generatedIdeSources.name, file("$buildDir/generated/sources/xtext/ide")) { | ||
89 | builtBy(generateXtextLanguage) | ||
90 | } | ||
91 | |||
92 | add(generatedWebSources.name, file("$buildDir/generated/sources/xtext/web")) { | ||
93 | builtBy(generateXtextLanguage) | ||
94 | } | ||
95 | } | ||
96 | |||
97 | sonarqube.properties { | ||
98 | SonarPropertiesUtils.addToList(properties, "sonar.exclusions", "src/textFixtures/xtext-gen/**") | ||
99 | } | ||
100 | |||
101 | eclipse.project.natures.plusAssign("org.eclipse.xtext.ui.shared.xtextNature") | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 b/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 index 21ff456e..59eba8f7 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 +++ b/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | module tools.refinery.language.GenerateProblem | 6 | module tools.refinery.language.GenerateProblem |
2 | 7 | ||
3 | import org.eclipse.xtext.xtext.generator.* | 8 | import org.eclipse.xtext.xtext.generator.* |
@@ -11,16 +16,28 @@ Workflow { | |||
11 | project = StandardProjectConfig { | 16 | project = StandardProjectConfig { |
12 | baseName = 'language' | 17 | baseName = 'language' |
13 | rootPath = rootPath | 18 | rootPath = rootPath |
19 | runtime = { | ||
20 | // Do not generate new files into src/main/java | ||
21 | src = null | ||
22 | } | ||
14 | runtimeTest = { | 23 | runtimeTest = { |
15 | enabled = true | 24 | enabled = true |
16 | srcGen = 'src/testFixtures/xtext-gen' | 25 | // Only generate the xtext-gen files and leave the rest of the project alone |
26 | root = null | ||
27 | srcGen = '${rootPath}/language/src/testFixtures/xtext-gen' | ||
17 | } | 28 | } |
18 | genericIde = { | 29 | genericIde = { |
19 | name = 'language-ide' | 30 | name = 'language-ide' |
31 | // Only generate the xtext-gen files and leave the rest of the project alone | ||
32 | root = null | ||
33 | srcGen = "${rootPath}/language/build/generated/sources/xtext/ide" | ||
20 | } | 34 | } |
21 | web = { | 35 | web = { |
22 | enabled = true | 36 | enabled = true |
23 | name = 'language-web' | 37 | name = 'language-web' |
38 | // Only generate the xtext-gen files and leave the rest of the project alone | ||
39 | root = null | ||
40 | srcGen = "${rootPath}/language/build/generated/sources/xtext/web" | ||
24 | } | 41 | } |
25 | mavenLayout = true | 42 | mavenLayout = true |
26 | } | 43 | } |
@@ -55,7 +72,7 @@ Workflow { | |||
55 | } | 72 | } |
56 | webSupport = { | 73 | webSupport = { |
57 | // We only generate the {@code AbstractProblemWebModule}, | 74 | // We only generate the {@code AbstractProblemWebModule}, |
58 | // because we write our own integration code for CodeMirror 6. | 75 | // because we write our own integration code for CodeMirror 6. |
59 | framework = 'codemirror' | 76 | framework = 'codemirror' |
60 | generateHtmlExample = false | 77 | generateHtmlExample = false |
61 | generateJettyLauncher = false | 78 | generateJettyLauncher = false |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext index 187ebf1f..9e330347 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals | 6 | grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals |
2 | 7 | ||
3 | import "http://www.eclipse.org/emf/2002/Ecore" as ecore | 8 | import "http://www.eclipse.org/emf/2002/Ecore" as ecore |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java index 5efcdc81..2636a8ee 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language; | 10 | package tools.refinery.language; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java index 41c96114..639d6778 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language; | 10 | package tools.refinery.language; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java b/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java index 508688ed..afbc367e 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java +++ b/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.conversion; | 6 | package tools.refinery.language.conversion; |
2 | 7 | ||
3 | import org.eclipse.xtext.common.services.DefaultTerminalConverters; | 8 | import org.eclipse.xtext.common.services.DefaultTerminalConverters; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java b/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java index be0d15ad..4886757d 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.conversion; | 6 | package tools.refinery.language.conversion; |
2 | 7 | ||
3 | import org.eclipse.xtext.conversion.ValueConverterException; | 8 | import org.eclipse.xtext.conversion.ValueConverterException; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java index 797535ea..55a5ac20 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.26.0.M2 | 8 | * generated by Xtext 2.26.0.M2 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.formatting2; | 10 | package tools.refinery.language.formatting2; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java index e959be74..1647d4e7 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.naming; | 6 | package tools.refinery.language.naming; |
2 | 7 | ||
3 | import java.util.regex.Pattern; | 8 | import java.util.regex.Pattern; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java index 5453906f..74b4e208 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.naming; | 6 | package tools.refinery.language.naming; |
2 | 7 | ||
3 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | 8 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java index ab133a90..306a86fc 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.parser.antlr; | 6 | package tools.refinery.language.parser.antlr; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java index 0b4e7185..5b91a6cc 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.29.0.M2 | 8 | * generated by Xtext 2.29.0.M2 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.parser.antlr; | 10 | package tools.refinery.language.parser.antlr; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java index 0cdd38d8..fe4c0bc7 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.parser.antlr; | 6 | package tools.refinery.language.parser.antlr; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java index 6176b0c4..07c5da41 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java index b0ac2ab6..e97c8287 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import org.eclipse.emf.ecore.EObject; | 8 | import org.eclipse.emf.ecore.EObject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java b/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java index 419be0d3..e5deca4d 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import com.google.common.collect.ImmutableSet; | 8 | import com.google.common.collect.ImmutableSet; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java index 8d3a552a..b145ef27 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java index df822987..1fe2df89 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import org.eclipse.emf.ecore.EObject; | 8 | import org.eclipse.emf.ecore.EObject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java index 1a0b73a8..630be379 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import org.eclipse.emf.ecore.EObject; | 8 | import org.eclipse.emf.ecore.EObject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java index ca20325e..f1be55ee 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource; |
2 | 7 | ||
3 | import java.util.HashMap; | 8 | import java.util.HashMap; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java index b749154c..4d2dd772 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.scoping; | 6 | package tools.refinery.language.scoping; |
2 | 7 | ||
3 | import java.util.LinkedHashSet; | 8 | import java.util.LinkedHashSet; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java index 61883f0e..229960a0 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.scoping; | 6 | package tools.refinery.language.scoping; |
2 | 7 | ||
3 | import java.util.List; | 8 | import java.util.List; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java index 3ab07496..cf099aba 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.scoping; | 10 | package tools.refinery.language.scoping; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java index 27ce1521..b9cafbc2 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.serializer; | 6 | package tools.refinery.language.serializer; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java index d3777cd3..c8c7fd4a 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.*; | 8 | import tools.refinery.language.model.problem.*; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java b/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java index b5682f32..e4e4d07a 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import java.util.Map; | 8 | import java.util.Map; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java index 708e10a9..a43c7dfe 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.PredicateKind; | 8 | import tools.refinery.language.model.problem.PredicateKind; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java b/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java index c8f47653..0fa7a454 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | public record NodeInfo(String name, boolean individual) { | 8 | public record NodeInfo(String name, boolean individual) { |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java index b8200919..738a0896 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java index 1e5164d3..9486dc2a 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import org.eclipse.emf.common.util.URI; | 8 | import org.eclipse.emf.common.util.URI; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java b/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java index 2253d257..1c46fe72 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.*; | 8 | import tools.refinery.language.model.problem.*; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java b/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java index 65167ed6..a4ea1113 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java index 659d882c..88d50c5b 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | |||
@@ -1,4 +1,10 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | /* | ||
2 | * generated by Xtext 2.25.0 | 8 | * generated by Xtext 2.25.0 |
3 | */ | 9 | */ |
4 | package tools.refinery.language.validation; | 10 | package tools.refinery.language.validation; |
diff --git a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem b/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem index 06b6da1d..9c1d7669 100644 --- a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem +++ b/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem | |||
@@ -1,3 +1,6 @@ | |||
1 | % SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
2 | % | ||
3 | % SPDX-License-Identifier: EPL-2.0 | ||
1 | problem builtin. | 4 | problem builtin. |
2 | 5 | ||
3 | abstract class node { | 6 | abstract class node { |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java index 58daa365..c7952369 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests; | 6 | package tools.refinery.language.tests; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java index 6e0802ca..f688d970 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.formatting2; | 6 | package tools.refinery.language.tests.formatting2; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java index abff8d9c..37d38dd9 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.parser.antlr; | 6 | package tools.refinery.language.tests.parser.antlr; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java index cb42d5d0..644744a0 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.parser.antlr; | 6 | package tools.refinery.language.tests.parser.antlr; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java index 65ceb45f..1180d131 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.parser.antlr; | 6 | package tools.refinery.language.tests.parser.antlr; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java index 72e5e18a..68514bfa 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.rules; | 6 | package tools.refinery.language.tests.rules; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java index fa462691..734bfcd1 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.scoping; | 6 | package tools.refinery.language.tests.scoping; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java index 4a18704a..3f3a081f 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.serializer; | 6 | package tools.refinery.language.tests.serializer; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java index af6de37f..d200eeff 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.tests.utils; | 6 | package tools.refinery.language.tests.utils; |
2 | 7 | ||
3 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemNavigationUtil.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemNavigationUtil.java index 5761935b..d92011a9 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemNavigationUtil.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemNavigationUtil.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import java.util.List; | 8 | import java.util.List; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemParseHelper.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemParseHelper.java index 5e044a94..6f6a87f7 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemParseHelper.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemParseHelper.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import org.eclipse.xtext.testing.util.ParseHelper; | 8 | import org.eclipse.xtext.testing.util.ParseHelper; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java index d176727b..3a49a0b9 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Action; | 8 | import tools.refinery.language.model.problem.Action; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java index 9e4c59f5..ed749fed 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.*; | 8 | import tools.refinery.language.model.problem.*; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertion.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertion.java index 2c38639d..b2ef6e48 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertion.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertion.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Assertion; | 8 | import tools.refinery.language.model.problem.Assertion; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java index 840c1f74..b36f2506 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.AssertionArgument; | 8 | import tools.refinery.language.model.problem.AssertionArgument; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAtom.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAtom.java index 498991f8..c02f447b 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAtom.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAtom.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Atom; | 8 | import tools.refinery.language.model.problem.Atom; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedClassDeclaration.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedClassDeclaration.java index 41b2ea62..a228137c 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedClassDeclaration.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedClassDeclaration.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.ClassDeclaration; | 8 | import tools.refinery.language.model.problem.ClassDeclaration; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConjunction.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConjunction.java index 88ff71ab..b126b1ce 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConjunction.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConjunction.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Conjunction; | 8 | import tools.refinery.language.model.problem.Conjunction; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConsequent.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConsequent.java index 46faa7da..8d6a92f8 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConsequent.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConsequent.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Consequent; | 8 | import tools.refinery.language.model.problem.Consequent; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedEnumDeclaration.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedEnumDeclaration.java index 74dcf01b..229a8c0a 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedEnumDeclaration.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedEnumDeclaration.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.EnumDeclaration; | 8 | import tools.refinery.language.model.problem.EnumDeclaration; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java index 4aa71b99..160e5dd8 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Atom; | 8 | import tools.refinery.language.model.problem.Atom; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java index c2f18a60..d44b79ed 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.Parameter; | 8 | import tools.refinery.language.model.problem.Parameter; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java index 7b95ecc1..2cf5fd89 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.PredicateDefinition; | 8 | import tools.refinery.language.model.problem.PredicateDefinition; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedProblem.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedProblem.java index 78ca95c7..e5aa0043 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedProblem.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedProblem.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import org.eclipse.emf.ecore.resource.Resource.Diagnostic; | 8 | import org.eclipse.emf.ecore.resource.Resource.Diagnostic; |
diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java index a4cf2eaf..326d8ec3 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.language.model.tests.utils; | 6 | package tools.refinery.language.model.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.language.model.problem.RuleDefinition; | 8 | import tools.refinery.language.model.problem.RuleDefinition; |
diff --git a/subprojects/store-query-viatra/NOTICE.md b/subprojects/store-query-viatra/NOTICE.md new file mode 100644 index 00000000..7c21726a --- /dev/null +++ b/subprojects/store-query-viatra/NOTICE.md | |||
@@ -0,0 +1,87 @@ | |||
1 | <!-- | ||
2 | Copyright (c) 2018-2019, Zoltan Ujhelyi, IncQuery Labs Ltd. | ||
3 | Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | |||
5 | SPDX-License-Identifier: EPL-2.0 | ||
6 | --> | ||
7 | |||
8 | This module contains source code from the [Eclipse VIATRA project](https://projects.eclipse.org/projects/modeling.viatra), which is available under the terms of the [Eclipse Public License v 2.0](http://www.eclipse.org/legal/epl-v20.html). | ||
9 | |||
10 | We reproduce the [accompanying notices](https://github.com/viatra/org.eclipse.viatra/blob/d422bcc626a99c4640c0d13931f393ccea0d2326/NOTICE.md) in full below: | ||
11 | |||
12 | # Notices for Eclipse VIATRA | ||
13 | |||
14 | This content is produced and maintained by the Eclipse VIATRA project. | ||
15 | |||
16 | * Project home: https://projects.eclipse.org/projects/modeling.viatra | ||
17 | |||
18 | ## Trademarks | ||
19 | |||
20 | Eclipse VIATRA, and VIATRA are trademarks of the Eclipse Foundation. | ||
21 | |||
22 | ## Copyright | ||
23 | |||
24 | All content is the property of the respective authors or their employers. For | ||
25 | more information regarding authorship of content, please consult the listed | ||
26 | source code repository logs. | ||
27 | |||
28 | ## Declared Project Licenses | ||
29 | |||
30 | This program and the accompanying materials are made available under the terms | ||
31 | of the Eclipse Public License v. 2.0 which is available at | ||
32 | http://www.eclipse.org/legal/epl-v20.html. | ||
33 | |||
34 | SPDX-License-Identifier: EPL-2.0 | ||
35 | |||
36 | ## Source Code | ||
37 | |||
38 | The project maintains the following source code repositories: | ||
39 | |||
40 | * http://git.eclipse.org/c/viatra/org.eclipse.viatra.git | ||
41 | * http://git.eclipse.org/c/viatra/org.eclipse.viatra.modelobfuscator.git | ||
42 | * http://git.eclipse.org/c/viatra/org.eclipse.viatra.examples.git | ||
43 | * http://git.eclipse.org/c/viatra/org.eclipse.viatra2.vpm.git | ||
44 | |||
45 | ## Third-party Content | ||
46 | |||
47 | This project leverages the following third party content. | ||
48 | |||
49 | ANTLR Runtime only: (3.2) | ||
50 | |||
51 | * License: New BSD license | ||
52 | |||
53 | Apache Commons Language Library (2.1) | ||
54 | |||
55 | * License: Apache License, 2.0 | ||
56 | |||
57 | Google Guice / Inject Core API (3.0.0) | ||
58 | |||
59 | * License: Apache License, 2.0 | ||
60 | |||
61 | Google Guice / Inject Core API (3.0.0) | ||
62 | |||
63 | * License: Apache License 2.0 | ||
64 | |||
65 | Guava (10.0.1) | ||
66 | |||
67 | * License: Apache License, 2.0 | ||
68 | |||
69 | Guice (2.0) | ||
70 | |||
71 | * License: Apache License, 2.0 | ||
72 | |||
73 | guice-multibindings (3.0.0) | ||
74 | |||
75 | * License: Apache License, 2.0 | ||
76 | |||
77 | log4j (1.2.15) | ||
78 | |||
79 | * License: Apache License 2.0 | ||
80 | |||
81 | LPG Java Runtime (lpgjavaruntime.jar) (1.1) | ||
82 | |||
83 | * License: Eclipse Public License | ||
84 | |||
85 | mockito (1.9.5) | ||
86 | |||
87 | * License: Apache License, 2.0, New BSD license, MIT license | ||
diff --git a/subprojects/store-query-viatra/build.gradle b/subprojects/store-query-viatra/build.gradle deleted file mode 100644 index c12b48fe..00000000 --- a/subprojects/store-query-viatra/build.gradle +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-library' | ||
3 | } | ||
4 | |||
5 | configurations.testRuntimeClasspath { | ||
6 | // VIATRA requires log4j 1.x, but we use log4j-over-slf4j instead | ||
7 | exclude group: 'log4j', module: 'log4j' | ||
8 | } | ||
9 | |||
10 | dependencies { | ||
11 | implementation libs.ecore | ||
12 | api libs.viatra | ||
13 | api project(':refinery-store') | ||
14 | testImplementation libs.slf4j.simple | ||
15 | testImplementation libs.slf4j.log4j | ||
16 | } | ||
diff --git a/subprojects/store-query-viatra/build.gradle.kts b/subprojects/store-query-viatra/build.gradle.kts new file mode 100644 index 00000000..e3a22145 --- /dev/null +++ b/subprojects/store-query-viatra/build.gradle.kts | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-library") | ||
9 | } | ||
10 | |||
11 | dependencies { | ||
12 | implementation(libs.ecore) | ||
13 | api(libs.viatra) | ||
14 | api(project(":refinery-store-query")) | ||
15 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java deleted file mode 100644 index 677e3c7d..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterBuilderFactory; | ||
4 | import tools.refinery.store.model.ModelStoreBuilder; | ||
5 | import tools.refinery.store.query.ModelQuery; | ||
6 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl; | ||
7 | |||
8 | public final class ViatraModelQuery extends ModelAdapterBuilderFactory<ViatraModelQueryAdapter, | ||
9 | ViatraModelQueryStoreAdapter, ViatraModelQueryBuilder> { | ||
10 | public static final ViatraModelQuery ADAPTER = new ViatraModelQuery(); | ||
11 | |||
12 | private ViatraModelQuery() { | ||
13 | super(ViatraModelQueryAdapter.class, ViatraModelQueryStoreAdapter.class, ViatraModelQueryBuilder.class); | ||
14 | extendsAdapter(ModelQuery.ADAPTER); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public ViatraModelQueryBuilder createBuilder(ModelStoreBuilder storeBuilder) { | ||
19 | return new ViatraModelQueryBuilderImpl(storeBuilder); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java index 7e21476b..12c93f62 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java | |||
@@ -1,8 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra; | 6 | package tools.refinery.store.query.viatra; |
2 | 7 | ||
3 | import tools.refinery.store.query.ModelQueryAdapter; | 8 | import tools.refinery.store.query.ModelQueryAdapter; |
9 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl; | ||
4 | 10 | ||
5 | public interface ViatraModelQueryAdapter extends ModelQueryAdapter { | 11 | public interface ViatraModelQueryAdapter extends ModelQueryAdapter { |
6 | @Override | 12 | @Override |
7 | ViatraModelQueryStoreAdapter getStoreAdapter(); | 13 | ViatraModelQueryStoreAdapter getStoreAdapter(); |
14 | |||
15 | static ViatraModelQueryBuilder builder() { | ||
16 | return new ViatraModelQueryBuilderImpl(); | ||
17 | } | ||
8 | } | 18 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java index efc6146c..931a07aa 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java | |||
@@ -1,10 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra; | 6 | package tools.refinery.store.query.viatra; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | 8 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; |
4 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | 9 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; |
5 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | 10 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; |
6 | import tools.refinery.store.model.ModelStore; | 11 | import tools.refinery.store.model.ModelStore; |
7 | import tools.refinery.store.query.DNF; | 12 | import tools.refinery.store.query.dnf.AnyQuery; |
13 | import tools.refinery.store.query.dnf.Dnf; | ||
8 | import tools.refinery.store.query.ModelQueryBuilder; | 14 | import tools.refinery.store.query.ModelQueryBuilder; |
9 | 15 | ||
10 | import java.util.Collection; | 16 | import java.util.Collection; |
@@ -23,26 +29,26 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder { | |||
23 | ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); | 29 | ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); |
24 | 30 | ||
25 | @Override | 31 | @Override |
26 | default ViatraModelQueryBuilder queries(DNF... queries) { | 32 | default ViatraModelQueryBuilder queries(AnyQuery... queries) { |
27 | ModelQueryBuilder.super.queries(queries); | 33 | ModelQueryBuilder.super.queries(queries); |
28 | return this; | 34 | return this; |
29 | } | 35 | } |
30 | 36 | ||
31 | @Override | 37 | @Override |
32 | default ViatraModelQueryBuilder queries(Collection<DNF> queries) { | 38 | default ViatraModelQueryBuilder queries(Collection<? extends AnyQuery> queries) { |
33 | ModelQueryBuilder.super.queries(queries); | 39 | ModelQueryBuilder.super.queries(queries); |
34 | return this; | 40 | return this; |
35 | } | 41 | } |
36 | 42 | ||
37 | @Override | 43 | @Override |
38 | ViatraModelQueryBuilder query(DNF query); | 44 | ViatraModelQueryBuilder query(AnyQuery query); |
39 | 45 | ||
40 | ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint); | 46 | ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint); |
41 | 47 | ||
42 | ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint); | 48 | ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint); |
43 | 49 | ||
44 | ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint); | 50 | ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint); |
45 | 51 | ||
46 | @Override | 52 | @Override |
47 | ViatraModelQueryStoreAdapter createStoreAdapter(ModelStore store); | 53 | ViatraModelQueryStoreAdapter build(ModelStore store); |
48 | } | 54 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java index 1ee02f12..da6d7bd5 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra; | 6 | package tools.refinery.store.query.viatra; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | 8 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java deleted file mode 100644 index 46c28434..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
4 | import tools.refinery.store.tuple.Tuple1; | ||
5 | import tools.refinery.store.tuple.TupleLike; | ||
6 | |||
7 | public record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike { | ||
8 | @Override | ||
9 | public int getSize() { | ||
10 | return wrappedTuple.getSize(); | ||
11 | } | ||
12 | |||
13 | @Override | ||
14 | public int get(int element) { | ||
15 | var wrappedValue = (Tuple1) wrappedTuple.get(element); | ||
16 | return wrappedValue.value0(); | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java index 8328e759..d1a65a89 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal; | 6 | package tools.refinery.store.query.viatra.internal; |
2 | 7 | ||
3 | import org.apache.log4j.Logger; | 8 | import org.apache.log4j.Logger; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java index 039f46fa..5f3e86b4 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal; | 6 | package tools.refinery.store.query.viatra.internal; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; | 8 | import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; |
@@ -7,62 +12,92 @@ import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl; | |||
7 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; | 12 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; |
8 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | 13 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; |
9 | import tools.refinery.store.model.Model; | 14 | import tools.refinery.store.model.Model; |
10 | import tools.refinery.store.query.DNF; | 15 | import tools.refinery.store.model.ModelListener; |
11 | import tools.refinery.store.query.ResultSet; | 16 | import tools.refinery.store.query.resultset.AnyResultSet; |
17 | import tools.refinery.store.query.resultset.EmptyResultSet; | ||
18 | import tools.refinery.store.query.resultset.ResultSet; | ||
19 | import tools.refinery.store.query.dnf.AnyQuery; | ||
20 | import tools.refinery.store.query.dnf.FunctionalQuery; | ||
21 | import tools.refinery.store.query.dnf.Query; | ||
22 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
12 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | 23 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; |
24 | import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher; | ||
25 | import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; | ||
26 | import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher; | ||
13 | 27 | ||
14 | import java.lang.invoke.MethodHandle; | 28 | import java.lang.invoke.MethodHandle; |
15 | import java.lang.invoke.MethodHandles; | 29 | import java.lang.invoke.MethodHandles; |
16 | import java.util.Collection; | 30 | import java.util.Collection; |
17 | import java.util.Collections; | 31 | import java.util.Collections; |
18 | import java.util.HashMap; | 32 | import java.util.LinkedHashMap; |
19 | import java.util.Map; | 33 | import java.util.Map; |
20 | 34 | ||
21 | public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | 35 | public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener { |
22 | private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; | 36 | private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; |
37 | private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE; | ||
23 | private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; | 38 | private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; |
39 | private static final MethodHandle GET_QUERY_BACKENDS_HANDLE; | ||
24 | 40 | ||
25 | private final Model model; | 41 | private final Model model; |
26 | private final ViatraModelQueryStoreAdapterImpl storeAdapter; | 42 | private final ViatraModelQueryStoreAdapterImpl storeAdapter; |
27 | private final ViatraQueryEngineImpl queryEngine; | 43 | private final ViatraQueryEngineImpl queryEngine; |
28 | private final MethodHandle setUpdatePropagationDelayedHandle; | 44 | private final Map<AnyQuery, AnyResultSet> resultSets; |
29 | private final MethodHandle getQueryBackendsHandle; | ||
30 | private final Map<DNF, ResultSet> resultSets; | ||
31 | private boolean pendingChanges; | 45 | private boolean pendingChanges; |
32 | 46 | ||
33 | ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { | 47 | static { |
34 | this.model = model; | ||
35 | this.storeAdapter = storeAdapter; | ||
36 | var scope = new RelationalScope(this); | ||
37 | queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope); | ||
38 | |||
39 | try { | 48 | try { |
40 | var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); | 49 | var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); |
41 | setUpdatePropagationDelayedHandle = lookup.findSetter(ViatraQueryEngineImpl.class, | 50 | SET_UPDATE_PROPAGATION_DELAYED_HANDLE = lookup.findSetter(ViatraQueryEngineImpl.class, |
42 | DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); | 51 | DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); |
43 | getQueryBackendsHandle = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, | 52 | GET_QUERY_BACKENDS_HANDLE = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, |
44 | Map.class); | 53 | Map.class); |
45 | } catch (IllegalAccessException | NoSuchFieldException e) { | 54 | } catch (IllegalAccessException | NoSuchFieldException e) { |
46 | throw new IllegalStateException("Cannot access private members of %s" | 55 | throw new IllegalStateException("Cannot access private members of %s" |
47 | .formatted(ViatraQueryEngineImpl.class.getName()), e); | 56 | .formatted(ViatraQueryEngineImpl.class.getName()), e); |
48 | } | 57 | } |
58 | } | ||
59 | |||
60 | ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { | ||
61 | this.model = model; | ||
62 | this.storeAdapter = storeAdapter; | ||
63 | var scope = new RelationalScope(this); | ||
64 | queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope, | ||
65 | storeAdapter.getEngineOptions()); | ||
49 | 66 | ||
50 | var querySpecifications = storeAdapter.getQuerySpecifications(); | 67 | var querySpecifications = storeAdapter.getQuerySpecifications(); |
51 | GenericQueryGroup.of( | 68 | GenericQueryGroup.of( |
52 | Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream() | 69 | Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream() |
53 | ).prepare(queryEngine); | 70 | ).prepare(queryEngine); |
54 | resultSets = new HashMap<>(querySpecifications.size()); | 71 | var vacuousQueries = storeAdapter.getVacuousQueries(); |
72 | resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size()); | ||
55 | for (var entry : querySpecifications.entrySet()) { | 73 | for (var entry : querySpecifications.entrySet()) { |
56 | var matcher = queryEngine.getMatcher(entry.getValue()); | 74 | var rawPatternMatcher = queryEngine.getMatcher(entry.getValue()); |
57 | resultSets.put(entry.getKey(), matcher); | 75 | var query = entry.getKey(); |
76 | resultSets.put(query, createResultSet((Query<?>) query, rawPatternMatcher)); | ||
77 | } | ||
78 | for (var vacuousQuery : vacuousQueries) { | ||
79 | resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query<?>) vacuousQuery)); | ||
58 | } | 80 | } |
59 | 81 | ||
60 | setUpdatePropagationDelayed(true); | 82 | setUpdatePropagationDelayed(true); |
83 | model.addListener(this); | ||
84 | } | ||
85 | |||
86 | private <T> ResultSet<T> createResultSet(Query<T> query, RawPatternMatcher matcher) { | ||
87 | if (query instanceof RelationalQuery relationalQuery) { | ||
88 | @SuppressWarnings("unchecked") | ||
89 | var resultSet = (ResultSet<T>) new RelationalViatraMatcher(this, relationalQuery, matcher); | ||
90 | return resultSet; | ||
91 | } else if (query instanceof FunctionalQuery<T> functionalQuery) { | ||
92 | return new FunctionalViatraMatcher<>(this, functionalQuery, matcher); | ||
93 | } else { | ||
94 | throw new IllegalArgumentException("Unknown query: " + query); | ||
95 | } | ||
61 | } | 96 | } |
62 | 97 | ||
63 | private void setUpdatePropagationDelayed(boolean value) { | 98 | private void setUpdatePropagationDelayed(boolean value) { |
64 | try { | 99 | try { |
65 | setUpdatePropagationDelayedHandle.invokeExact(queryEngine, value); | 100 | SET_UPDATE_PROPAGATION_DELAYED_HANDLE.invokeExact(queryEngine, value); |
66 | } catch (Error e) { | 101 | } catch (Error e) { |
67 | // Fatal JVM errors should not be wrapped. | 102 | // Fatal JVM errors should not be wrapped. |
68 | throw e; | 103 | throw e; |
@@ -74,7 +109,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | |||
74 | private Collection<IQueryBackend> getQueryBackends() { | 109 | private Collection<IQueryBackend> getQueryBackends() { |
75 | try { | 110 | try { |
76 | @SuppressWarnings("unchecked") | 111 | @SuppressWarnings("unchecked") |
77 | var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) getQueryBackendsHandle.invokeExact(queryEngine); | 112 | var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) GET_QUERY_BACKENDS_HANDLE.invokeExact(queryEngine); |
78 | return backendMap.values(); | 113 | return backendMap.values(); |
79 | } catch (Error e) { | 114 | } catch (Error e) { |
80 | // Fatal JVM errors should not be wrapped. | 115 | // Fatal JVM errors should not be wrapped. |
@@ -95,12 +130,14 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | |||
95 | } | 130 | } |
96 | 131 | ||
97 | @Override | 132 | @Override |
98 | public ResultSet getResultSet(DNF query) { | 133 | public <T> ResultSet<T> getResultSet(Query<T> query) { |
99 | var resultSet = resultSets.get(query); | 134 | var resultSet = resultSets.get(query); |
100 | if (resultSet == null) { | 135 | if (resultSet == null) { |
101 | throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); | 136 | throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); |
102 | } | 137 | } |
103 | return resultSet; | 138 | @SuppressWarnings("unchecked") |
139 | var typedResultSet = (ResultSet<T>) resultSet; | ||
140 | return typedResultSet; | ||
104 | } | 141 | } |
105 | 142 | ||
106 | @Override | 143 | @Override |
@@ -132,4 +169,9 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { | |||
132 | } | 169 | } |
133 | pendingChanges = false; | 170 | pendingChanges = false; |
134 | } | 171 | } |
172 | |||
173 | @Override | ||
174 | public void afterRestore() { | ||
175 | flushChanges(); | ||
176 | } | ||
135 | } | 177 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java index 9f1e55b1..ce2467b4 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java | |||
@@ -1,115 +1,162 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal; | 6 | package tools.refinery.store.query.viatra.internal; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; | 8 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; |
4 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | 9 | import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; |
5 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; | 10 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions; |
6 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | 11 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; |
7 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | 12 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; |
8 | import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; | 13 | import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; |
9 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | 14 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; |
10 | import tools.refinery.store.model.ModelStore; | 15 | import tools.refinery.store.model.ModelStore; |
11 | import tools.refinery.store.model.ModelStoreBuilder; | 16 | import tools.refinery.store.model.ModelStoreBuilder; |
12 | import tools.refinery.store.query.DNF; | 17 | import tools.refinery.store.query.dnf.AnyQuery; |
18 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; | 19 | import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; |
14 | import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; | 20 | import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction; |
15 | import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; | 21 | import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory; |
22 | import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; | ||
23 | import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery; | ||
16 | 24 | ||
17 | import java.util.Collections; | 25 | import java.util.*; |
18 | import java.util.LinkedHashMap; | ||
19 | import java.util.Map; | ||
20 | import java.util.function.Function; | 26 | import java.util.function.Function; |
21 | 27 | ||
22 | public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { | 28 | public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder<ViatraModelQueryStoreAdapterImpl> |
29 | implements ViatraModelQueryBuilder { | ||
23 | private ViatraQueryEngineOptions.Builder engineOptionsBuilder; | 30 | private ViatraQueryEngineOptions.Builder engineOptionsBuilder; |
24 | private final DNF2PQuery dnf2PQuery = new DNF2PQuery(); | 31 | private QueryEvaluationHint defaultHint = new QueryEvaluationHint(Map.of( |
25 | private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>(); | 32 | // Use a cost function that ignores the initial (empty) model but allows higher arity input keys. |
33 | LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction() | ||
34 | ), (IQueryBackendFactory) null); | ||
35 | private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery(); | ||
36 | private final Set<AnyQuery> vacuousQueries = new LinkedHashSet<>(); | ||
37 | private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>(); | ||
26 | 38 | ||
27 | public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) { | 39 | public ViatraModelQueryBuilderImpl() { |
28 | super(storeBuilder); | ||
29 | engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() | 40 | engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() |
30 | .withDefaultBackend(ReteBackendFactory.INSTANCE) | 41 | .withDefaultBackend(ReteBackendFactory.INSTANCE) |
31 | .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) | 42 | .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) |
32 | .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE); | 43 | .withDefaultSearchBackend(RelationalLocalSearchBackendFactory.INSTANCE); |
33 | } | 44 | } |
34 | 45 | ||
35 | @Override | 46 | @Override |
36 | public ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions) { | 47 | public ViatraModelQueryBuilder engineOptions(ViatraQueryEngineOptions engineOptions) { |
48 | checkNotConfigured(); | ||
37 | engineOptionsBuilder = new ViatraQueryEngineOptions.Builder(engineOptions); | 49 | engineOptionsBuilder = new ViatraQueryEngineOptions.Builder(engineOptions); |
38 | return this; | 50 | return this; |
39 | } | 51 | } |
40 | 52 | ||
41 | @Override | 53 | @Override |
42 | public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { | 54 | public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { |
43 | engineOptionsBuilder.withDefaultHint(queryEvaluationHint); | 55 | checkNotConfigured(); |
56 | defaultHint = defaultHint.overrideBy(queryEvaluationHint); | ||
44 | return this; | 57 | return this; |
45 | } | 58 | } |
46 | 59 | ||
47 | @Override | 60 | @Override |
48 | public ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory) { | 61 | public ViatraModelQueryBuilder backend(IQueryBackendFactory queryBackendFactory) { |
62 | checkNotConfigured(); | ||
49 | engineOptionsBuilder.withDefaultBackend(queryBackendFactory); | 63 | engineOptionsBuilder.withDefaultBackend(queryBackendFactory); |
50 | return this; | 64 | return this; |
51 | } | 65 | } |
52 | 66 | ||
53 | @Override | 67 | @Override |
54 | public ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) { | 68 | public ViatraModelQueryBuilder cachingBackend(IQueryBackendFactory queryBackendFactory) { |
69 | checkNotConfigured(); | ||
55 | engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory); | 70 | engineOptionsBuilder.withDefaultCachingBackend(queryBackendFactory); |
56 | return this; | 71 | return this; |
57 | } | 72 | } |
58 | 73 | ||
59 | @Override | 74 | @Override |
60 | public ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory) { | 75 | public ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory) { |
76 | checkNotConfigured(); | ||
61 | engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory); | 77 | engineOptionsBuilder.withDefaultSearchBackend(queryBackendFactory); |
62 | return this; | 78 | return this; |
63 | } | 79 | } |
64 | 80 | ||
65 | @Override | 81 | @Override |
66 | public ViatraModelQueryBuilder query(DNF query) { | 82 | public ViatraModelQueryBuilder query(AnyQuery query) { |
67 | if (querySpecifications.containsKey(query)) { | 83 | checkNotConfigured(); |
68 | throw new IllegalArgumentException("%s was already added to the query engine".formatted(query.name())); | 84 | if (querySpecifications.containsKey(query) || vacuousQueries.contains(query)) { |
85 | // Ignore duplicate queries. | ||
86 | return this; | ||
87 | } | ||
88 | var dnf = query.getDnf(); | ||
89 | var reduction = dnf.getReduction(); | ||
90 | switch (reduction) { | ||
91 | case NOT_REDUCIBLE -> { | ||
92 | var pQuery = dnf2PQuery.translate(dnf); | ||
93 | querySpecifications.put(query, pQuery.build()); | ||
94 | } | ||
95 | case ALWAYS_FALSE -> vacuousQueries.add(query); | ||
96 | case ALWAYS_TRUE -> throw new IllegalArgumentException( | ||
97 | "Query %s is relationally unsafe (it matches every tuple)".formatted(query.name())); | ||
98 | default -> throw new IllegalArgumentException("Unknown reduction: " + reduction); | ||
69 | } | 99 | } |
70 | var pQuery = dnf2PQuery.translate(query); | ||
71 | querySpecifications.put(query, pQuery.build()); | ||
72 | return this; | 100 | return this; |
73 | } | 101 | } |
74 | 102 | ||
75 | @Override | 103 | @Override |
76 | public ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint) { | 104 | public ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint) { |
105 | hint(query.getDnf(), queryEvaluationHint); | ||
77 | query(query); | 106 | query(query); |
78 | hint(query, queryEvaluationHint); | ||
79 | return this; | 107 | return this; |
80 | } | 108 | } |
81 | 109 | ||
82 | @Override | 110 | @Override |
83 | public ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint) { | 111 | public ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint) { |
112 | checkNotConfigured(); | ||
84 | dnf2PQuery.setComputeHint(computeHint); | 113 | dnf2PQuery.setComputeHint(computeHint); |
85 | return this; | 114 | return this; |
86 | } | 115 | } |
87 | 116 | ||
88 | @Override | 117 | @Override |
89 | public ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint) { | 118 | public ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint) { |
90 | var pQuery = dnf2PQuery.getAlreadyTranslated(dnf); | 119 | checkNotConfigured(); |
91 | if (pQuery == null) { | 120 | dnf2PQuery.hint(dnf, queryEvaluationHint); |
92 | throw new IllegalArgumentException( | ||
93 | "Cannot specify hint for %s, because it was not added to the query engine".formatted(dnf.name())); | ||
94 | } | ||
95 | pQuery.setEvaluationHints(pQuery.getEvaluationHints().overrideBy(queryEvaluationHint)); | ||
96 | return this; | 121 | return this; |
97 | } | 122 | } |
98 | 123 | ||
99 | @Override | 124 | @Override |
100 | public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) { | 125 | public void doConfigure(ModelStoreBuilder storeBuilder) { |
126 | dnf2PQuery.assertNoUnusedHints(); | ||
127 | } | ||
128 | |||
129 | @Override | ||
130 | public ViatraModelQueryStoreAdapterImpl doBuild(ModelStore store) { | ||
101 | validateSymbols(store); | 131 | validateSymbols(store); |
102 | return new ViatraModelQueryStoreAdapterImpl(store, engineOptionsBuilder.build(), dnf2PQuery.getRelationViews(), | 132 | return new ViatraModelQueryStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getSymbolViews(), |
103 | Collections.unmodifiableMap(querySpecifications)); | 133 | Collections.unmodifiableMap(querySpecifications), Collections.unmodifiableSet(vacuousQueries)); |
134 | } | ||
135 | |||
136 | private ViatraQueryEngineOptions buildEngineOptions() { | ||
137 | // Workaround: manually override the default backend, because {@link ViatraQueryEngineOptions.Builder} | ||
138 | // ignores all backend requirements except {@code SPECIFIC}. | ||
139 | switch (defaultHint.getQueryBackendRequirementType()) { | ||
140 | case SPECIFIC -> engineOptionsBuilder.withDefaultBackend(defaultHint.getQueryBackendFactory()); | ||
141 | case DEFAULT_CACHING -> engineOptionsBuilder.withDefaultBackend( | ||
142 | engineOptionsBuilder.build().getDefaultCachingBackendFactory()); | ||
143 | case DEFAULT_SEARCH -> engineOptionsBuilder.withDefaultBackend( | ||
144 | engineOptionsBuilder.build().getDefaultSearchBackendFactory()); | ||
145 | case UNSPECIFIED -> { | ||
146 | // Nothing to do, leave the default backend unchanged. | ||
147 | } | ||
148 | } | ||
149 | engineOptionsBuilder.withDefaultHint(defaultHint); | ||
150 | return engineOptionsBuilder.build(); | ||
104 | } | 151 | } |
105 | 152 | ||
106 | private void validateSymbols(ModelStore store) { | 153 | private void validateSymbols(ModelStore store) { |
107 | var symbols = store.getSymbols(); | 154 | var symbols = store.getSymbols(); |
108 | for (var relationView : dnf2PQuery.getRelationViews().keySet()) { | 155 | for (var symbolView : dnf2PQuery.getSymbolViews().keySet()) { |
109 | var symbol = relationView.getSymbol(); | 156 | var symbol = symbolView.getSymbol(); |
110 | if (!symbols.contains(symbol)) { | 157 | if (!symbols.contains(symbol)) { |
111 | throw new IllegalArgumentException("Cannot query relation view %s: symbol %s is not in the model" | 158 | throw new IllegalArgumentException("Cannot query view %s: symbol %s is not in the model" |
112 | .formatted(relationView, symbol)); | 159 | .formatted(symbolView, symbol)); |
113 | } | 160 | } |
114 | } | 161 | } |
115 | } | 162 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java index 394e407e..11a3c7fd 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal; | 6 | package tools.refinery.store.query.viatra.internal; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; | 8 | import org.eclipse.viatra.query.runtime.api.IQuerySpecification; |
@@ -5,27 +10,34 @@ import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; | |||
5 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | 10 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; |
6 | import tools.refinery.store.model.Model; | 11 | import tools.refinery.store.model.Model; |
7 | import tools.refinery.store.model.ModelStore; | 12 | import tools.refinery.store.model.ModelStore; |
8 | import tools.refinery.store.query.DNF; | 13 | import tools.refinery.store.query.dnf.AnyQuery; |
9 | import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; | 14 | import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; |
10 | import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; | 15 | import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; |
11 | import tools.refinery.store.query.view.AnyRelationView; | 16 | import tools.refinery.store.query.view.AnySymbolView; |
12 | 17 | ||
13 | import java.util.Collection; | 18 | import java.util.*; |
14 | import java.util.Map; | ||
15 | 19 | ||
16 | public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { | 20 | public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { |
17 | private final ModelStore store; | 21 | private final ModelStore store; |
18 | private final ViatraQueryEngineOptions engineOptions; | 22 | private final ViatraQueryEngineOptions engineOptions; |
19 | private final Map<AnyRelationView, IInputKey> inputKeys; | 23 | private final Map<AnySymbolView, IInputKey> inputKeys; |
20 | private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications; | 24 | private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications; |
25 | private final Set<AnyQuery> vacuousQueries; | ||
26 | private final Set<AnyQuery> allQueries; | ||
21 | 27 | ||
22 | ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, | 28 | ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, |
23 | Map<AnyRelationView, IInputKey> inputKeys, | 29 | Map<AnySymbolView, IInputKey> inputKeys, |
24 | Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications) { | 30 | Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications, |
31 | Set<AnyQuery> vacuousQueries) { | ||
25 | this.store = store; | 32 | this.store = store; |
26 | this.engineOptions = engineOptions; | 33 | this.engineOptions = engineOptions; |
27 | this.inputKeys = inputKeys; | 34 | this.inputKeys = inputKeys; |
28 | this.querySpecifications = querySpecifications; | 35 | this.querySpecifications = querySpecifications; |
36 | this.vacuousQueries = vacuousQueries; | ||
37 | var mutableAllQueries = new LinkedHashSet<AnyQuery>(querySpecifications.size() + vacuousQueries.size()); | ||
38 | mutableAllQueries.addAll(querySpecifications.keySet()); | ||
39 | mutableAllQueries.addAll(vacuousQueries); | ||
40 | this.allQueries = Collections.unmodifiableSet(mutableAllQueries); | ||
29 | } | 41 | } |
30 | 42 | ||
31 | @Override | 43 | @Override |
@@ -33,23 +45,27 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd | |||
33 | return store; | 45 | return store; |
34 | } | 46 | } |
35 | 47 | ||
36 | public Collection<AnyRelationView> getRelationViews() { | 48 | public Collection<AnySymbolView> getSymbolViews() { |
37 | return inputKeys.keySet(); | 49 | return inputKeys.keySet(); |
38 | } | 50 | } |
39 | 51 | ||
40 | public Map<AnyRelationView, IInputKey> getInputKeys() { | 52 | public Map<AnySymbolView, IInputKey> getInputKeys() { |
41 | return inputKeys; | 53 | return inputKeys; |
42 | } | 54 | } |
43 | 55 | ||
44 | @Override | 56 | @Override |
45 | public Collection<DNF> getQueries() { | 57 | public Collection<AnyQuery> getQueries() { |
46 | return querySpecifications.keySet(); | 58 | return allQueries; |
47 | } | 59 | } |
48 | 60 | ||
49 | Map<DNF, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() { | 61 | Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() { |
50 | return querySpecifications; | 62 | return querySpecifications; |
51 | } | 63 | } |
52 | 64 | ||
65 | Set<AnyQuery> getVacuousQueries() { | ||
66 | return vacuousQueries; | ||
67 | } | ||
68 | |||
53 | @Override | 69 | @Override |
54 | public ViatraQueryEngineOptions getEngineOptions() { | 70 | public ViatraQueryEngineOptions getEngineOptions() { |
55 | return engineOptions; | 71 | return engineOptions; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java deleted file mode 100644 index e0bca9e0..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java +++ /dev/null | |||
@@ -1,97 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.cardinality; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
5 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | ||
6 | import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality; | ||
7 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
8 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
9 | |||
10 | import java.util.stream.Stream; | ||
11 | |||
12 | public class UpperCardinalitySumAggregationOperator implements IMultisetAggregationOperator<UpperCardinality, | ||
13 | UpperCardinalitySumAggregationOperator.Accumulator, UpperCardinality> { | ||
14 | public static final UpperCardinalitySumAggregationOperator INSTANCE = new UpperCardinalitySumAggregationOperator(); | ||
15 | |||
16 | public static final BoundAggregator BOUND_AGGREGATOR = new BoundAggregator(INSTANCE, UpperCardinality.class, | ||
17 | UpperCardinality.class); | ||
18 | |||
19 | private UpperCardinalitySumAggregationOperator() { | ||
20 | // Singleton constructor. | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public String getName() { | ||
25 | return "sum<UpperCardinality>"; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public String getShortDescription() { | ||
30 | return "%s computes the sum of finite or unbounded upper cardinalities".formatted(getName()); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public Accumulator createNeutral() { | ||
35 | return new Accumulator(); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public boolean isNeutral(Accumulator result) { | ||
40 | return result.sumFiniteUpperBounds == 0 && result.countUnbounded == 0; | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public Accumulator update(Accumulator oldResult, UpperCardinality updateValue, boolean isInsertion) { | ||
45 | if (updateValue instanceof FiniteUpperCardinality finiteUpperCardinality) { | ||
46 | int finiteUpperBound = finiteUpperCardinality.finiteUpperBound(); | ||
47 | if (isInsertion) { | ||
48 | oldResult.sumFiniteUpperBounds += finiteUpperBound; | ||
49 | } else { | ||
50 | oldResult.sumFiniteUpperBounds -= finiteUpperBound; | ||
51 | } | ||
52 | } else if (updateValue instanceof UnboundedUpperCardinality) { | ||
53 | if (isInsertion) { | ||
54 | oldResult.countUnbounded += 1; | ||
55 | } else { | ||
56 | oldResult.countUnbounded -= 1; | ||
57 | } | ||
58 | } else { | ||
59 | throw new IllegalArgumentException("Unknown UpperCardinality: " + updateValue); | ||
60 | } | ||
61 | return oldResult; | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public UpperCardinality getAggregate(Accumulator result) { | ||
66 | return result.countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : | ||
67 | UpperCardinalities.valueOf(result.sumFiniteUpperBounds); | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public UpperCardinality aggregateStream(Stream<UpperCardinality> stream) { | ||
72 | var result = stream.collect(this::createNeutral, (accumulator, value) -> update(accumulator, value, true), | ||
73 | (left, right) -> new Accumulator(left.sumFiniteUpperBounds + right.sumFiniteUpperBounds, | ||
74 | left.countUnbounded + right.countUnbounded)); | ||
75 | return getAggregate(result); | ||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public Accumulator clone(Accumulator original) { | ||
80 | return new Accumulator(original.sumFiniteUpperBounds, original.countUnbounded); | ||
81 | } | ||
82 | |||
83 | public static class Accumulator { | ||
84 | private int sumFiniteUpperBounds; | ||
85 | |||
86 | private int countUnbounded; | ||
87 | |||
88 | private Accumulator(int sumFiniteUpperBounds, int countUnbounded) { | ||
89 | this.sumFiniteUpperBounds = sumFiniteUpperBounds; | ||
90 | this.countUnbounded = countUnbounded; | ||
91 | } | ||
92 | |||
93 | private Accumulator() { | ||
94 | this(0, 0); | ||
95 | } | ||
96 | } | ||
97 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java index 2a24b67c..8cb199d2 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.context; | 6 | package tools.refinery.store.query.viatra.internal.context; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; | 8 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java index 28bc69d0..7220f8ca 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.context; | 6 | package tools.refinery.store.query.viatra.internal.context; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; | 8 | import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java index cba3fa08..211eacb4 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java | |||
@@ -1,10 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.context; | 6 | package tools.refinery.store.query.viatra.internal.context; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; | 8 | import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; |
4 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | 9 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; |
5 | import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; | 10 | import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; |
6 | import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; | 11 | import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey; |
7 | import tools.refinery.store.query.view.AnyRelationView; | 12 | import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper; |
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
8 | 14 | ||
9 | import java.util.*; | 15 | import java.util.*; |
10 | 16 | ||
@@ -12,9 +18,9 @@ import java.util.*; | |||
12 | * The meta context information for String scopes. | 18 | * The meta context information for String scopes. |
13 | */ | 19 | */ |
14 | public class RelationalQueryMetaContext extends AbstractQueryMetaContext { | 20 | public class RelationalQueryMetaContext extends AbstractQueryMetaContext { |
15 | private final Map<AnyRelationView, IInputKey> inputKeys; | 21 | private final Map<AnySymbolView, IInputKey> inputKeys; |
16 | 22 | ||
17 | RelationalQueryMetaContext(Map<AnyRelationView, IInputKey> inputKeys) { | 23 | RelationalQueryMetaContext(Map<AnySymbolView, IInputKey> inputKeys) { |
18 | this.inputKeys = inputKeys; | 24 | this.inputKeys = inputKeys; |
19 | } | 25 | } |
20 | 26 | ||
@@ -37,26 +43,43 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext { | |||
37 | 43 | ||
38 | @Override | 44 | @Override |
39 | public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) { | 45 | public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) { |
40 | var relationView = checkKey(implyingKey); | 46 | if (implyingKey instanceof JavaTransitiveInstancesKey) { |
41 | var relationViewImplications = relationView.getImpliedRelationViews(); | 47 | return List.of(); |
48 | } | ||
49 | var symbolView = checkKey(implyingKey); | ||
50 | var relationViewImplications = symbolView.getImpliedRelationViews(); | ||
42 | var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size()); | 51 | var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size()); |
43 | for (var relationViewImplication : relationViewImplications) { | 52 | for (var relationViewImplication : relationViewImplications) { |
44 | if (!relationView.equals(relationViewImplication.implyingRelationView())) { | 53 | if (!symbolView.equals(relationViewImplication.implyingView())) { |
45 | throw new IllegalArgumentException("Relation view %s returned unrelated implication %s".formatted( | 54 | throw new IllegalArgumentException("Relation view %s returned unrelated implication %s".formatted( |
46 | relationView, relationViewImplication)); | 55 | symbolView, relationViewImplication)); |
47 | } | 56 | } |
48 | var impliedInputKey = inputKeys.get(relationViewImplication.impliedRelationView()); | 57 | var impliedInputKey = inputKeys.get(relationViewImplication.impliedView()); |
49 | // Ignore implications not relevant for any queries included in the model. | 58 | // Ignore implications not relevant for any queries included in the model. |
50 | if (impliedInputKey != null) { | 59 | if (impliedInputKey != null) { |
51 | inputKeyImplications.add(new InputKeyImplication(implyingKey, impliedInputKey, | 60 | inputKeyImplications.add(new InputKeyImplication(implyingKey, impliedInputKey, |
52 | relationViewImplication.impliedIndices())); | 61 | relationViewImplication.impliedIndices())); |
53 | } | 62 | } |
54 | } | 63 | } |
64 | var parameters = symbolView.getParameters(); | ||
65 | int arity = symbolView.arity(); | ||
66 | for (int i = 0; i < arity; i++) { | ||
67 | var parameter = parameters.get(i); | ||
68 | var parameterType = parameter.tryGetType(); | ||
69 | if (parameterType.isPresent()) { | ||
70 | var javaTransitiveInstancesKey = new JavaTransitiveInstancesKey(parameterType.get()); | ||
71 | var javaImplication = new InputKeyImplication(implyingKey, javaTransitiveInstancesKey, List.of(i)); | ||
72 | inputKeyImplications.add(javaImplication); | ||
73 | } | ||
74 | } | ||
55 | return inputKeyImplications; | 75 | return inputKeyImplications; |
56 | } | 76 | } |
57 | 77 | ||
58 | @Override | 78 | @Override |
59 | public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) { | 79 | public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) { |
80 | if (key instanceof JavaTransitiveInstancesKey) { | ||
81 | return Map.of(); | ||
82 | } | ||
60 | var relationView = checkKey(key); | 83 | var relationView = checkKey(key); |
61 | var functionalDependencies = relationView.getFunctionalDependencies(); | 84 | var functionalDependencies = relationView.getFunctionalDependencies(); |
62 | var flattened = new HashMap<Set<Integer>, Set<Integer>>(functionalDependencies.size()); | 85 | var flattened = new HashMap<Set<Integer>, Set<Integer>>(functionalDependencies.size()); |
@@ -75,20 +98,20 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext { | |||
75 | return flattened; | 98 | return flattened; |
76 | } | 99 | } |
77 | 100 | ||
78 | private static void checkValidIndices(AnyRelationView relationView, Collection<Integer> indices) { | 101 | private static void checkValidIndices(AnySymbolView relationView, Collection<Integer> indices) { |
79 | indices.stream().filter(relationView::invalidIndex).findAny().ifPresent(i -> { | 102 | indices.stream().filter(relationView::invalidIndex).findAny().ifPresent(i -> { |
80 | throw new IllegalArgumentException("Index %d is invalid for %s".formatted(i, relationView)); | 103 | throw new IllegalArgumentException("Index %d is invalid for %s".formatted(i, relationView)); |
81 | }); | 104 | }); |
82 | } | 105 | } |
83 | 106 | ||
84 | public AnyRelationView checkKey(IInputKey key) { | 107 | public AnySymbolView checkKey(IInputKey key) { |
85 | if (!(key instanceof RelationViewWrapper wrapper)) { | 108 | if (!(key instanceof SymbolViewWrapper wrapper)) { |
86 | throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key)); | 109 | throw new IllegalArgumentException("The input key %s is not a valid input key".formatted(key)); |
87 | } | 110 | } |
88 | var relationView = wrapper.getWrappedKey(); | 111 | var symbolView = wrapper.getWrappedKey(); |
89 | if (!inputKeys.containsKey(relationView)) { | 112 | if (!inputKeys.containsKey(symbolView)) { |
90 | throw new IllegalArgumentException("The input key %s is not present in the model".formatted(key)); | 113 | throw new IllegalArgumentException("The input key %s is not present in the model".formatted(key)); |
91 | } | 114 | } |
92 | return relationView; | 115 | return symbolView; |
93 | } | 116 | } |
94 | } | 117 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java index 01d20d3e..0f2daca8 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.context; | 6 | package tools.refinery.store.query.viatra.internal.context; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.context.*; | 8 | import org.eclipse.viatra.query.runtime.matchers.context.*; |
@@ -8,9 +13,9 @@ import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | |||
8 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; | 13 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; |
9 | import tools.refinery.store.model.Model; | 14 | import tools.refinery.store.model.Model; |
10 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | 15 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; |
11 | import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; | 16 | import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper; |
12 | import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; | 17 | import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; |
13 | import tools.refinery.store.query.view.AnyRelationView; | 18 | import tools.refinery.store.query.view.AnySymbolView; |
14 | 19 | ||
15 | import java.lang.reflect.InvocationTargetException; | 20 | import java.lang.reflect.InvocationTargetException; |
16 | import java.util.Iterator; | 21 | import java.util.Iterator; |
@@ -54,8 +59,9 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
54 | 59 | ||
55 | @Override | 60 | @Override |
56 | public boolean isIndexed(IInputKey key, IndexingService service) { | 61 | public boolean isIndexed(IInputKey key, IndexingService service) { |
57 | if (key instanceof AnyRelationView relationalKey) { | 62 | if (key instanceof SymbolViewWrapper wrapper) { |
58 | return this.modelUpdateListener.containsRelationView(relationalKey); | 63 | var symbolViewKey = wrapper.getWrappedKey(); |
64 | return this.modelUpdateListener.containsSymbolView(symbolViewKey); | ||
59 | } else { | 65 | } else { |
60 | return false; | 66 | return false; |
61 | } | 67 | } |
@@ -68,13 +74,13 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
68 | } | 74 | } |
69 | } | 75 | } |
70 | 76 | ||
71 | AnyRelationView checkKey(IInputKey key) { | 77 | AnySymbolView checkKey(IInputKey key) { |
72 | if (key instanceof RelationViewWrapper wrappedKey) { | 78 | if (key instanceof SymbolViewWrapper wrappedKey) { |
73 | var relationViewKey = wrappedKey.getWrappedKey(); | 79 | var symbolViewKey = wrappedKey.getWrappedKey(); |
74 | if (modelUpdateListener.containsRelationView(relationViewKey)) { | 80 | if (modelUpdateListener.containsSymbolView(symbolViewKey)) { |
75 | return relationViewKey; | 81 | return symbolViewKey; |
76 | } else { | 82 | } else { |
77 | throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(relationViewKey)); | 83 | throw new IllegalStateException("Query is asking for non-indexed key %s".formatted(symbolViewKey)); |
78 | } | 84 | } |
79 | } else { | 85 | } else { |
80 | throw new IllegalStateException("Query is asking for non-relational key"); | 86 | throw new IllegalStateException("Query is asking for non-relational key"); |
@@ -83,10 +89,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
83 | 89 | ||
84 | @Override | 90 | @Override |
85 | public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | 91 | public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { |
86 | var relationViewKey = checkKey(key); | 92 | Iterator<Object[]> iterator = enumerate(key, seedMask, seed).iterator(); |
87 | Iterable<Object[]> allObjects = relationViewKey.getAll(model); | ||
88 | Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); | ||
89 | Iterator<Object[]> iterator = filteredBySeed.iterator(); | ||
90 | int result = 0; | 93 | int result = 0; |
91 | while (iterator.hasNext()) { | 94 | while (iterator.hasNext()) { |
92 | iterator.next(); | 95 | iterator.next(); |
@@ -102,13 +105,25 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
102 | 105 | ||
103 | @Override | 106 | @Override |
104 | public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | 107 | public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { |
108 | var filteredBySeed = enumerate(key, seedMask, seed); | ||
109 | return map(filteredBySeed, Tuples::flatTupleOf); | ||
110 | } | ||
111 | |||
112 | @Override | ||
113 | public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
114 | var index = seedMask.getFirstOmittedIndex().orElseThrow( | ||
115 | () -> new IllegalArgumentException("Seed mask does not omit a value")); | ||
116 | var filteredBySeed = enumerate(key, seedMask, seed); | ||
117 | return map(filteredBySeed, array -> array[index]); | ||
118 | } | ||
119 | |||
120 | private Iterable<Object[]> enumerate(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
105 | var relationViewKey = checkKey(key); | 121 | var relationViewKey = checkKey(key); |
106 | Iterable<Object[]> allObjects = relationViewKey.getAll(model); | 122 | Iterable<Object[]> allObjects = relationViewKey.getAll(model); |
107 | Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); | 123 | return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); |
108 | return map(filteredBySeed, Tuples::flatTupleOf); | ||
109 | } | 124 | } |
110 | 125 | ||
111 | private boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { | 126 | private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { |
112 | for (int i = 0; i < seedMask.indices.length; i++) { | 127 | for (int i = 0; i < seedMask.indices.length; i++) { |
113 | final Object seedElement = seed.get(i); | 128 | final Object seedElement = seed.get(i); |
114 | final Object tupleElement = tuple[seedMask.indices[i]]; | 129 | final Object tupleElement = tuple[seedMask.indices[i]]; |
@@ -120,11 +135,6 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { | |||
120 | } | 135 | } |
121 | 136 | ||
122 | @Override | 137 | @Override |
123 | public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
124 | return enumerateTuples(key, seedMask, seed); | ||
125 | } | ||
126 | |||
127 | @Override | ||
128 | public boolean containsTuple(IInputKey key, ITuple seed) { | 138 | public boolean containsTuple(IInputKey key, ITuple seed) { |
129 | var relationViewKey = checkKey(key); | 139 | var relationViewKey = checkKey(key); |
130 | return relationViewKey.get(model, seed.getElements()); | 140 | return relationViewKey.get(model, seed.getElements()); |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java new file mode 100644 index 00000000..37177cbf --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java | |||
@@ -0,0 +1,76 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-v20.html. | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
10 | |||
11 | import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; | ||
12 | import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; | ||
13 | import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; | ||
14 | |||
15 | import java.util.Iterator; | ||
16 | |||
17 | /** | ||
18 | * An operation that can be used to enumerate all possible values for a single position based on a constraint | ||
19 | * @author Zoltan Ujhelyi, Akos Horvath | ||
20 | * @since 2.0 | ||
21 | */ | ||
22 | abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor { | ||
23 | |||
24 | private Iterator<? extends T> it; | ||
25 | |||
26 | /** | ||
27 | * Returns an iterator with the possible options from the current state | ||
28 | * @since 2.0 | ||
29 | */ | ||
30 | @SuppressWarnings("squid:S1452") | ||
31 | protected abstract Iterator<? extends T> getIterator(MatchingFrame frame, ISearchContext context); | ||
32 | /** | ||
33 | * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}. | ||
34 | * | ||
35 | * @return true if the update is successful or false otherwise; in case of false is returned, the next element should be taken from the iterator. | ||
36 | * @since 2.0 | ||
37 | */ | ||
38 | protected abstract boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context); | ||
39 | |||
40 | /** | ||
41 | * Restores the frame to the state before {@link #fillInValue(Object, MatchingFrame, ISearchContext)}. Called during | ||
42 | * {@link #onBacktrack(MatchingFrame, ISearchContext)}. | ||
43 | * | ||
44 | * @since 2.0 | ||
45 | */ | ||
46 | protected abstract void cleanup(MatchingFrame frame, ISearchContext context); | ||
47 | |||
48 | @Override | ||
49 | public void onInitialize(MatchingFrame frame, ISearchContext context) { | ||
50 | it = getIterator(frame, context); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public void onBacktrack(MatchingFrame frame, ISearchContext context) { | ||
55 | it = null; | ||
56 | |||
57 | } | ||
58 | |||
59 | /** | ||
60 | * Fixed version of {@link org.eclipse.viatra.query.runtime.localsearch.operations.ExtendOperationExecutor#execute} | ||
61 | * that handles failed unification of variables correctly. | ||
62 | * @param frame The matching frame to extend. | ||
63 | * @param context The search context. | ||
64 | * @return {@code true} if an extension was found, {@code false} otherwise. | ||
65 | */ | ||
66 | @Override | ||
67 | public boolean execute(MatchingFrame frame, ISearchContext context) { | ||
68 | while (it.hasNext()) { | ||
69 | var newValue = it.next(); | ||
70 | if (fillInValue(newValue, frame, context)) { | ||
71 | return true; | ||
72 | } | ||
73 | } | ||
74 | return false; | ||
75 | } | ||
76 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java new file mode 100644 index 00000000..9d48c785 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java | |||
@@ -0,0 +1,117 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-v20.html. | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
10 | |||
11 | import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; | ||
12 | import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; | ||
13 | import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation; | ||
14 | import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; | ||
15 | import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
19 | import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple; | ||
20 | |||
21 | import java.util.Iterator; | ||
22 | import java.util.List; | ||
23 | import java.util.function.Function; | ||
24 | |||
25 | /** | ||
26 | * @author Grill Balázs | ||
27 | * @since 1.4 | ||
28 | * | ||
29 | */ | ||
30 | public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatcherOperation { | ||
31 | |||
32 | private class Executor extends ExtendOperationExecutor<Tuple> { | ||
33 | private final VolatileModifiableMaskedTuple maskedTuple; | ||
34 | |||
35 | public Executor() { | ||
36 | maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) { | ||
41 | maskedTuple.updateTuple(frame); | ||
42 | IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment()); | ||
43 | return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator(); | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * @since 2.0 | ||
48 | */ | ||
49 | @Override | ||
50 | protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) { | ||
51 | TupleMask mask = information.getFullFrameMask(); | ||
52 | // The first loop clears out the elements from a possible previous iteration | ||
53 | for(int i : information.getFreeParameterIndices()) { | ||
54 | mask.set(frame, i, null); | ||
55 | } | ||
56 | for(int i : information.getFreeParameterIndices()) { | ||
57 | Object oldValue = mask.getValue(frame, i); | ||
58 | Object valueToFill = result.get(i); | ||
59 | if (oldValue != null && !oldValue.equals(valueToFill)){ | ||
60 | // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller. | ||
61 | // In this case if the callee assigns different values the frame shall be dropped | ||
62 | return false; | ||
63 | } | ||
64 | mask.set(frame, i, valueToFill); | ||
65 | } | ||
66 | return true; | ||
67 | } | ||
68 | |||
69 | @Override | ||
70 | protected void cleanup(MatchingFrame frame, ISearchContext context) { | ||
71 | TupleMask mask = information.getFullFrameMask(); | ||
72 | for(int i : information.getFreeParameterIndices()){ | ||
73 | mask.set(frame, i, null); | ||
74 | } | ||
75 | |||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public ISearchOperation getOperation() { | ||
80 | return ExtendPositivePatternCall.this; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | private final CallInformation information; | ||
85 | |||
86 | /** | ||
87 | * @since 1.7 | ||
88 | */ | ||
89 | public ExtendPositivePatternCall(CallInformation information) { | ||
90 | this.information = information; | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public ISearchOperationExecutor createExecutor() { | ||
95 | return new Executor(); | ||
96 | } | ||
97 | |||
98 | @Override | ||
99 | public List<Integer> getVariablePositions() { | ||
100 | return information.getVariablePositions(); | ||
101 | } | ||
102 | |||
103 | @Override | ||
104 | public String toString() { | ||
105 | return toString(Object::toString); | ||
106 | } | ||
107 | |||
108 | @Override | ||
109 | public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) { | ||
110 | return "extend find " + information.toString(variableMapping); | ||
111 | } | ||
112 | |||
113 | @Override | ||
114 | public CallInformation getCallInformation() { | ||
115 | return information; | ||
116 | } | ||
117 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java new file mode 100644 index 00000000..cc906f22 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext; | ||
9 | import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; | ||
14 | |||
15 | import java.util.Optional; | ||
16 | |||
17 | public class FlatCostFunction extends StatisticsBasedConstraintCostFunction { | ||
18 | public FlatCostFunction() { | ||
19 | // No inverse navigation penalty thanks to relational storage. | ||
20 | super(0); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public Optional<Long> projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) { | ||
25 | // We always start from an empty model, where every projection is of size 0. | ||
26 | // Therefore, projection size estimation is meaningless. | ||
27 | return Optional.empty(); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | protected double _calculateCost(TypeConstraint constraint, IConstraintEvaluationContext input) { | ||
32 | // Assume a flat cost for each relation. Maybe adjust in the future if we perform indexing? | ||
33 | return DEFAULT_COST; | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java new file mode 100644 index 00000000..96ac4a72 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java | |||
@@ -0,0 +1,137 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-v20.html. | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
10 | |||
11 | import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; | ||
12 | import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; | ||
13 | import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation; | ||
14 | import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple; | ||
19 | import org.eclipse.viatra.query.runtime.matchers.util.Preconditions; | ||
20 | |||
21 | import java.util.*; | ||
22 | import java.util.function.Function; | ||
23 | import java.util.stream.Collectors; | ||
24 | |||
25 | /** | ||
26 | * @author Zoltan Ujhelyi | ||
27 | * @since 1.7 | ||
28 | */ | ||
29 | public class GenericTypeExtend implements IIteratingSearchOperation { | ||
30 | private class Executor extends ExtendOperationExecutor<Tuple> { | ||
31 | private final VolatileMaskedTuple maskedTuple; | ||
32 | |||
33 | public Executor() { | ||
34 | this.maskedTuple = new VolatileMaskedTuple(callMask); | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) { | ||
39 | maskedTuple.updateTuple(frame); | ||
40 | return context.getRuntimeContext().enumerateTuples(type, indexerMask, maskedTuple).iterator(); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | protected boolean fillInValue(Tuple newTuple, MatchingFrame frame, ISearchContext context) { | ||
45 | for (Integer position : unboundVariableIndices) { | ||
46 | frame.setValue(position, null); | ||
47 | } | ||
48 | for (int i = 0; i < positions.length; i++) { | ||
49 | Object newValue = newTuple.get(i); | ||
50 | Object oldValue = frame.getValue(positions[i]); | ||
51 | if (oldValue != null && !Objects.equals(oldValue, newValue)) { | ||
52 | // If positions tuple maps more than one values for the same element (e.g. loop), it means that | ||
53 | // these arguments are to unified by the caller. In this case if the callee assigns different values | ||
54 | // the frame shall be considered a failed match | ||
55 | return false; | ||
56 | } | ||
57 | frame.setValue(positions[i], newValue); | ||
58 | } | ||
59 | return true; | ||
60 | } | ||
61 | |||
62 | @Override | ||
63 | protected void cleanup(MatchingFrame frame, ISearchContext context) { | ||
64 | for (Integer position : unboundVariableIndices) { | ||
65 | frame.setValue(position, null); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | @Override | ||
70 | public ISearchOperation getOperation() { | ||
71 | return GenericTypeExtend.this; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | private final IInputKey type; | ||
76 | private final int[] positions; | ||
77 | private final List<Integer> positionList; | ||
78 | private final Set<Integer> unboundVariableIndices; | ||
79 | private final TupleMask indexerMask; | ||
80 | private final TupleMask callMask; | ||
81 | |||
82 | /** | ||
83 | * | ||
84 | * @param type | ||
85 | * the type to execute the extend operation on | ||
86 | * @param positions | ||
87 | * the parameter positions that represent the variables of the input key | ||
88 | * @param unboundVariableIndices | ||
89 | * the set of positions that are bound at the start of the operation | ||
90 | */ | ||
91 | public GenericTypeExtend(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, Set<Integer> unboundVariableIndices) { | ||
92 | Preconditions.checkArgument(positions.length == type.getArity(), | ||
93 | "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(), | ||
94 | type.getArity(), positions.length); | ||
95 | List<Integer> modifiablePositionList = new ArrayList<>(); | ||
96 | for (int position : positions) { | ||
97 | modifiablePositionList.add(position); | ||
98 | } | ||
99 | this.positionList = Collections.unmodifiableList(modifiablePositionList); | ||
100 | this.positions = positions; | ||
101 | this.type = type; | ||
102 | |||
103 | this.unboundVariableIndices = unboundVariableIndices; | ||
104 | this.indexerMask = indexerMask; | ||
105 | this.callMask = callMask; | ||
106 | } | ||
107 | |||
108 | @Override | ||
109 | public IInputKey getIteratedInputKey() { | ||
110 | return type; | ||
111 | } | ||
112 | |||
113 | @Override | ||
114 | public ISearchOperationExecutor createExecutor() { | ||
115 | return new Executor(); | ||
116 | } | ||
117 | |||
118 | @Override | ||
119 | public List<Integer> getVariablePositions() { | ||
120 | return positionList; | ||
121 | } | ||
122 | |||
123 | @Override | ||
124 | public String toString() { | ||
125 | return toString(Object::toString); | ||
126 | } | ||
127 | |||
128 | @Override | ||
129 | public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) { | ||
130 | return "extend " + type.getPrettyPrintableName() + "(" | ||
131 | + positionList.stream() | ||
132 | .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input))) | ||
133 | .collect(Collectors.joining(", ")) | ||
134 | + ")"; | ||
135 | } | ||
136 | |||
137 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java new file mode 100644 index 00000000..0c77f587 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; | ||
9 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend; | ||
10 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints; | ||
11 | import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider; | ||
12 | import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; | ||
19 | |||
20 | public class RelationalLocalSearchBackendFactory implements IQueryBackendFactory { | ||
21 | public static final RelationalLocalSearchBackendFactory INSTANCE = new RelationalLocalSearchBackendFactory(); | ||
22 | |||
23 | private RelationalLocalSearchBackendFactory() { | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public IQueryBackend create(IQueryBackendContext context) { | ||
28 | return new LocalSearchBackend(context) { | ||
29 | // Create a new {@link IPlanProvider}, because the original {@link LocalSearchBackend#planProvider} is not | ||
30 | // accessible. | ||
31 | private final IPlanProvider planProvider = new SimplePlanProvider(context.getLogger()); | ||
32 | |||
33 | @Override | ||
34 | protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, | ||
35 | QueryEvaluationHint hints) { | ||
36 | return new RelationalLocalSearchResultProvider(this, context, query, planProvider, hints); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public IQueryBackendFactory getFactory() { | ||
41 | return RelationalLocalSearchBackendFactory.this; | ||
42 | } | ||
43 | }; | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public Class<? extends IQueryBackend> getBackendClass() { | ||
48 | return LocalSearchBackend.class; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public IMatcherCapability calculateRequiredCapability(PQuery pQuery, QueryEvaluationHint queryEvaluationHint) { | ||
53 | return LocalSearchHints.parse(queryEvaluationHint); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public boolean isCaching() { | ||
58 | return false; | ||
59 | } | ||
60 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java new file mode 100644 index 00000000..da37be14 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java | |||
@@ -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 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; | ||
9 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend; | ||
10 | import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints; | ||
11 | import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider; | ||
12 | import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; | ||
16 | |||
17 | class RelationalLocalSearchResultProvider extends AbstractLocalSearchResultProvider { | ||
18 | public RelationalLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, | ||
19 | IPlanProvider planProvider, QueryEvaluationHint userHints) { | ||
20 | super(backend, context, query, planProvider, userHints); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, | ||
25 | LocalSearchHints configuration) { | ||
26 | return new RelationalOperationCompiler(runtimeContext); | ||
27 | } | ||
28 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java new file mode 100644 index 00000000..f76ef486 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java | |||
@@ -0,0 +1,70 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.localsearch; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue; | ||
9 | import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation; | ||
10 | import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
18 | |||
19 | import java.util.*; | ||
20 | |||
21 | public class RelationalOperationCompiler extends GenericOperationCompiler { | ||
22 | public RelationalOperationCompiler(IQueryRuntimeContext runtimeContext) { | ||
23 | super(runtimeContext); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | protected void createExtend(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping) { | ||
28 | IInputKey inputKey = typeConstraint.getSupplierKey(); | ||
29 | Tuple tuple = typeConstraint.getVariablesTuple(); | ||
30 | |||
31 | int[] positions = new int[tuple.getSize()]; | ||
32 | List<Integer> boundVariableIndices = new ArrayList<>(); | ||
33 | List<Integer> boundVariables = new ArrayList<>(); | ||
34 | Set<Integer> unboundVariables = new HashSet<>(); | ||
35 | for (int i = 0; i < tuple.getSize(); i++) { | ||
36 | PVariable variable = (PVariable) tuple.get(i); | ||
37 | Integer position = variableMapping.get(variable); | ||
38 | positions[i] = position; | ||
39 | if (variableBindings.get(typeConstraint).contains(position)) { | ||
40 | boundVariableIndices.add(i); | ||
41 | boundVariables.add(position); | ||
42 | } else { | ||
43 | unboundVariables.add(position); | ||
44 | } | ||
45 | } | ||
46 | TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices); | ||
47 | TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables); | ||
48 | // If multiple tuple elements from the indexer should be bound to the same variable, we must use a | ||
49 | // {@link GenericTypeExtend} check whether the tuple elements have the same value. | ||
50 | if (unboundVariables.size() == 1 && indexerMask.getSize() + 1 == indexerMask.getSourceWidth()) { | ||
51 | operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask, | ||
52 | unboundVariables.iterator().next())); | ||
53 | } else { | ||
54 | // Use a fixed version of | ||
55 | // {@code org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtend} that handles | ||
56 | // failed unification of variables correctly. | ||
57 | operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables)); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | protected void createExtend(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping) { | ||
63 | CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall)); | ||
64 | // Use a fixed version of | ||
65 | // {@code org.eclipse.viatra.query.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles | ||
66 | // failed unification of variables correctly. | ||
67 | operations.add(new ExtendPositivePatternCall(information)); | ||
68 | dependencies.add(information.getCallWithAdornment()); | ||
69 | } | ||
70 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java new file mode 100644 index 00000000..99b0a3d8 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java | |||
@@ -0,0 +1,32 @@ | |||
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.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.backend.IUpdateable; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.resultset.AbstractResultSet; | ||
12 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
13 | |||
14 | public abstract class AbstractViatraMatcher<T> extends AbstractResultSet<T> implements IUpdateable { | ||
15 | protected final IQueryResultProvider backend; | ||
16 | |||
17 | protected AbstractViatraMatcher(ViatraModelQueryAdapterImpl adapter, Query<T> query, | ||
18 | RawPatternMatcher rawPatternMatcher) { | ||
19 | super(adapter, query); | ||
20 | backend = rawPatternMatcher.getBackend(); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | protected void startListeningForChanges() { | ||
25 | backend.addUpdateListener(this, this, false); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | protected void stopListeningForChanges() { | ||
30 | backend.removeUpdateListener(this); | ||
31 | } | ||
32 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java new file mode 100644 index 00000000..47efb2aa --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Iterator; | ||
13 | |||
14 | class FunctionalCursor<T> implements Cursor<Tuple, T> { | ||
15 | private final IterableIndexer indexer; | ||
16 | private final Iterator<org.eclipse.viatra.query.runtime.matchers.tuple.Tuple> iterator; | ||
17 | private boolean terminated; | ||
18 | private Tuple key; | ||
19 | private T value; | ||
20 | |||
21 | public FunctionalCursor(IterableIndexer indexer) { | ||
22 | this.indexer = indexer; | ||
23 | iterator = indexer.getSignatures().iterator(); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public Tuple getKey() { | ||
28 | return key; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public T getValue() { | ||
33 | return value; | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public boolean isTerminated() { | ||
38 | return terminated; | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public boolean move() { | ||
43 | if (!terminated && iterator.hasNext()) { | ||
44 | var match = iterator.next(); | ||
45 | key = MatcherUtils.toRefineryTuple(match); | ||
46 | value = MatcherUtils.getSingleValue(indexer.get(match)); | ||
47 | return true; | ||
48 | } | ||
49 | terminated = true; | ||
50 | return false; | ||
51 | } | ||
52 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java new file mode 100644 index 00000000..db4740cd --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java | |||
@@ -0,0 +1,88 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
10 | import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; | ||
11 | import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; | ||
12 | import tools.refinery.store.map.Cursor; | ||
13 | import tools.refinery.store.query.dnf.FunctionalQuery; | ||
14 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
15 | import tools.refinery.store.tuple.Tuple; | ||
16 | |||
17 | /** | ||
18 | * Directly access the tuples inside a VIATRA pattern matcher.<p> | ||
19 | * This class neglects calling | ||
20 | * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} | ||
21 | * and | ||
22 | * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, | ||
23 | * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial | ||
24 | * implementation for these methods. | ||
25 | * Using this class with any other runtime context may lead to undefined behavior. | ||
26 | */ | ||
27 | public class FunctionalViatraMatcher<T> extends AbstractViatraMatcher<T> { | ||
28 | private final TupleMask emptyMask; | ||
29 | private final TupleMask omitOutputMask; | ||
30 | private final IterableIndexer omitOutputIndexer; | ||
31 | |||
32 | public FunctionalViatraMatcher(ViatraModelQueryAdapterImpl adapter, FunctionalQuery<T> query, | ||
33 | RawPatternMatcher rawPatternMatcher) { | ||
34 | super(adapter, query, rawPatternMatcher); | ||
35 | int arity = query.arity(); | ||
36 | int arityWithOutput = arity + 1; | ||
37 | emptyMask = TupleMask.empty(arityWithOutput); | ||
38 | omitOutputMask = TupleMask.omit(arity, arityWithOutput); | ||
39 | if (backend instanceof RetePatternMatcher reteBackend) { | ||
40 | var maybeIterableOmitOutputIndexer = IndexerUtils.getIndexer(reteBackend, omitOutputMask); | ||
41 | if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) { | ||
42 | omitOutputIndexer = iterableOmitOutputIndexer; | ||
43 | } else { | ||
44 | omitOutputIndexer = null; | ||
45 | } | ||
46 | } else { | ||
47 | omitOutputIndexer = null; | ||
48 | } | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public T get(Tuple parameters) { | ||
53 | var tuple = MatcherUtils.toViatraTuple(parameters); | ||
54 | if (omitOutputIndexer == null) { | ||
55 | return MatcherUtils.getSingleValue(backend.getAllMatches(omitOutputMask, tuple).iterator()); | ||
56 | } else { | ||
57 | return MatcherUtils.getSingleValue(omitOutputIndexer.get(tuple)); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public Cursor<Tuple, T> getAll() { | ||
63 | if (omitOutputIndexer == null) { | ||
64 | var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf()); | ||
65 | return new UnsafeFunctionalCursor<>(allMatches.iterator()); | ||
66 | } | ||
67 | return new FunctionalCursor<>(omitOutputIndexer); | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public int size() { | ||
72 | if (omitOutputIndexer == null) { | ||
73 | return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf()); | ||
74 | } | ||
75 | return omitOutputIndexer.getBucketCount(); | ||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { | ||
80 | var key = MatcherUtils.keyToRefineryTuple(updateElement); | ||
81 | var value = MatcherUtils.<T>getValue(updateElement); | ||
82 | if (isInsertion) { | ||
83 | notifyChange(key, null, value); | ||
84 | } else { | ||
85 | notifyChange(key, value, null); | ||
86 | } | ||
87 | } | ||
88 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java new file mode 100644 index 00000000..15f00b2d --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
9 | import org.eclipse.viatra.query.runtime.rete.index.Indexer; | ||
10 | import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; | ||
11 | import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; | ||
12 | import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; | ||
13 | |||
14 | import java.lang.invoke.MethodHandle; | ||
15 | import java.lang.invoke.MethodHandles; | ||
16 | import java.lang.invoke.MethodType; | ||
17 | |||
18 | final class IndexerUtils { | ||
19 | private static final MethodHandle GET_ENGINE_HANDLE; | ||
20 | private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE; | ||
21 | private static final MethodHandle ACCESS_PROJECTION_HANDLE; | ||
22 | |||
23 | static { | ||
24 | try { | ||
25 | var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup()); | ||
26 | GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class); | ||
27 | GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace", | ||
28 | RecipeTraceInfo.class); | ||
29 | ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection", | ||
30 | MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class)); | ||
31 | } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) { | ||
32 | throw new IllegalStateException("Cannot access private members of %s" | ||
33 | .formatted(RetePatternMatcher.class.getPackageName()), e); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | private IndexerUtils() { | ||
38 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
39 | } | ||
40 | |||
41 | public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) { | ||
42 | try { | ||
43 | var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend); | ||
44 | var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend); | ||
45 | return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask); | ||
46 | } catch (Error e) { | ||
47 | // Fatal JVM errors should not be wrapped. | ||
48 | throw e; | ||
49 | } catch (Throwable e) { | ||
50 | throw new IllegalStateException("Cannot access matcher for mask " + mask, e); | ||
51 | } | ||
52 | } | ||
53 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java new file mode 100644 index 00000000..6e24812a --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java | |||
@@ -0,0 +1,115 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
10 | import org.jetbrains.annotations.Nullable; | ||
11 | import tools.refinery.store.tuple.*; | ||
12 | |||
13 | import java.util.Iterator; | ||
14 | |||
15 | final class MatcherUtils { | ||
16 | private MatcherUtils() { | ||
17 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
18 | } | ||
19 | |||
20 | public static org.eclipse.viatra.query.runtime.matchers.tuple.Tuple toViatraTuple(Tuple refineryTuple) { | ||
21 | if (refineryTuple instanceof Tuple0) { | ||
22 | return Tuples.staticArityFlatTupleOf(); | ||
23 | } else if (refineryTuple instanceof Tuple1) { | ||
24 | return Tuples.staticArityFlatTupleOf(refineryTuple); | ||
25 | } else if (refineryTuple instanceof Tuple2 tuple2) { | ||
26 | return Tuples.staticArityFlatTupleOf(Tuple.of(tuple2.value0()), Tuple.of(tuple2.value1())); | ||
27 | } else if (refineryTuple instanceof Tuple3 tuple3) { | ||
28 | return Tuples.staticArityFlatTupleOf(Tuple.of(tuple3.value0()), Tuple.of(tuple3.value1()), | ||
29 | Tuple.of(tuple3.value2())); | ||
30 | } else if (refineryTuple instanceof Tuple4 tuple4) { | ||
31 | return Tuples.staticArityFlatTupleOf(Tuple.of(tuple4.value0()), Tuple.of(tuple4.value1()), | ||
32 | Tuple.of(tuple4.value2()), Tuple.of(tuple4.value3())); | ||
33 | } else { | ||
34 | int arity = refineryTuple.getSize(); | ||
35 | var values = new Object[arity]; | ||
36 | for (int i = 0; i < arity; i++) { | ||
37 | values[i] = Tuple.of(refineryTuple.get(i)); | ||
38 | } | ||
39 | return Tuples.flatTupleOf(values); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | public static Tuple toRefineryTuple(ITuple viatraTuple) { | ||
44 | int arity = viatraTuple.getSize(); | ||
45 | if (arity == 1) { | ||
46 | return getWrapper(viatraTuple, 0); | ||
47 | } | ||
48 | return prefixToRefineryTuple(viatraTuple, viatraTuple.getSize()); | ||
49 | } | ||
50 | |||
51 | public static Tuple keyToRefineryTuple(ITuple viatraTuple) { | ||
52 | return prefixToRefineryTuple(viatraTuple, viatraTuple.getSize() - 1); | ||
53 | } | ||
54 | |||
55 | private static Tuple prefixToRefineryTuple(ITuple viatraTuple, int targetArity) { | ||
56 | if (targetArity < 0) { | ||
57 | throw new IllegalArgumentException("Requested negative prefix %d of %s" | ||
58 | .formatted(targetArity, viatraTuple)); | ||
59 | } | ||
60 | return switch (targetArity) { | ||
61 | case 0 -> Tuple.of(); | ||
62 | case 1 -> Tuple.of(unwrap(viatraTuple, 0)); | ||
63 | case 2 -> Tuple.of(unwrap(viatraTuple, 0), unwrap(viatraTuple, 1)); | ||
64 | case 3 -> Tuple.of(unwrap(viatraTuple, 0), unwrap(viatraTuple, 1), unwrap(viatraTuple, 2)); | ||
65 | case 4 -> Tuple.of(unwrap(viatraTuple, 0), unwrap(viatraTuple, 1), unwrap(viatraTuple, 2), | ||
66 | unwrap(viatraTuple, 3)); | ||
67 | default -> { | ||
68 | var entries = new int[targetArity]; | ||
69 | for (int i = 0; i < targetArity; i++) { | ||
70 | entries[i] = unwrap(viatraTuple, i); | ||
71 | } | ||
72 | yield Tuple.of(entries); | ||
73 | } | ||
74 | }; | ||
75 | } | ||
76 | |||
77 | private static Tuple1 getWrapper(ITuple viatraTuple, int index) { | ||
78 | if (!((viatraTuple.get(index)) instanceof Tuple1 wrappedObjectId)) { | ||
79 | throw new IllegalArgumentException("Element %d of tuple %s is not an object id" | ||
80 | .formatted(index, viatraTuple)); | ||
81 | } | ||
82 | return wrappedObjectId; | ||
83 | } | ||
84 | |||
85 | private static int unwrap(ITuple viatraTuple, int index) { | ||
86 | return getWrapper(viatraTuple, index).value0(); | ||
87 | } | ||
88 | |||
89 | public static <T> T getValue(ITuple match) { | ||
90 | // This is only safe if we know for sure that match came from a functional query of type {@code T}. | ||
91 | @SuppressWarnings("unchecked") | ||
92 | var result = (T) match.get(match.getSize() - 1); | ||
93 | return result; | ||
94 | } | ||
95 | |||
96 | public static <T> T getSingleValue(@Nullable Iterable<? extends ITuple> viatraTuples) { | ||
97 | if (viatraTuples == null) { | ||
98 | return null; | ||
99 | } | ||
100 | return getSingleValue(viatraTuples.iterator()); | ||
101 | } | ||
102 | |||
103 | public static <T> T getSingleValue(Iterator<? extends ITuple> iterator) { | ||
104 | if (!iterator.hasNext()) { | ||
105 | return null; | ||
106 | } | ||
107 | var match = iterator.next(); | ||
108 | var result = MatcherUtils.<T>getValue(match); | ||
109 | if (iterator.hasNext()) { | ||
110 | var input = keyToRefineryTuple(match); | ||
111 | throw new IllegalStateException("Query is not functional for input tuple: " + input); | ||
112 | } | ||
113 | return result; | ||
114 | } | ||
115 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java new file mode 100644 index 00000000..5b82c4b7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java | |||
@@ -0,0 +1,20 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | ||
9 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; | ||
11 | |||
12 | public class RawPatternMatcher extends GenericPatternMatcher { | ||
13 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { | ||
14 | super(specification); | ||
15 | } | ||
16 | |||
17 | IQueryResultProvider getBackend() { | ||
18 | return backend; | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java new file mode 100644 index 00000000..1dc8f5db --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java | |||
@@ -0,0 +1,47 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Iterator; | ||
13 | |||
14 | class RelationalCursor implements Cursor<Tuple, Boolean> { | ||
15 | private final Iterator<? extends ITuple> tuplesIterator; | ||
16 | private boolean terminated; | ||
17 | private Tuple key; | ||
18 | |||
19 | public RelationalCursor(Iterator<? extends ITuple> tuplesIterator) { | ||
20 | this.tuplesIterator = tuplesIterator; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public Tuple getKey() { | ||
25 | return key; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Boolean getValue() { | ||
30 | return true; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public boolean isTerminated() { | ||
35 | return terminated; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public boolean move() { | ||
40 | if (!terminated && tuplesIterator.hasNext()) { | ||
41 | key = MatcherUtils.toRefineryTuple(tuplesIterator.next()); | ||
42 | return true; | ||
43 | } | ||
44 | terminated = true; | ||
45 | return false; | ||
46 | } | ||
47 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java new file mode 100644 index 00000000..ac95dcc0 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java | |||
@@ -0,0 +1,80 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
10 | import org.eclipse.viatra.query.runtime.rete.index.Indexer; | ||
11 | import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; | ||
12 | import tools.refinery.store.map.Cursor; | ||
13 | import tools.refinery.store.map.Cursors; | ||
14 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
15 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | /** | ||
19 | * Directly access the tuples inside a VIATRA pattern matcher.<p> | ||
20 | * This class neglects calling | ||
21 | * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} | ||
22 | * and | ||
23 | * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, | ||
24 | * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial | ||
25 | * implementation for these methods. | ||
26 | * Using this class with any other runtime context may lead to undefined behavior. | ||
27 | */ | ||
28 | public class RelationalViatraMatcher extends AbstractViatraMatcher<Boolean> { | ||
29 | private final TupleMask emptyMask; | ||
30 | private final TupleMask identityMask; | ||
31 | private final Indexer emptyMaskIndexer; | ||
32 | |||
33 | public RelationalViatraMatcher(ViatraModelQueryAdapterImpl adapter, RelationalQuery query, | ||
34 | RawPatternMatcher rawPatternMatcher) { | ||
35 | super(adapter, query, rawPatternMatcher); | ||
36 | int arity = query.arity(); | ||
37 | emptyMask = TupleMask.empty(arity); | ||
38 | identityMask = TupleMask.identity(arity); | ||
39 | if (backend instanceof RetePatternMatcher reteBackend) { | ||
40 | emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, emptyMask); | ||
41 | } else { | ||
42 | emptyMaskIndexer = null; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public Boolean get(Tuple parameters) { | ||
48 | var tuple = MatcherUtils.toViatraTuple(parameters); | ||
49 | if (emptyMaskIndexer == null) { | ||
50 | return backend.hasMatch(identityMask, tuple); | ||
51 | } | ||
52 | var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); | ||
53 | return matches != null && matches.contains(tuple); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public Cursor<Tuple, Boolean> getAll() { | ||
58 | if (emptyMaskIndexer == null) { | ||
59 | var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf()); | ||
60 | return new RelationalCursor(allMatches.iterator()); | ||
61 | } | ||
62 | var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); | ||
63 | return matches == null ? Cursors.empty() : new RelationalCursor(matches.stream().iterator()); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public int size() { | ||
68 | if (emptyMaskIndexer == null) { | ||
69 | return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf()); | ||
70 | } | ||
71 | var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); | ||
72 | return matches == null ? 0 : matches.size(); | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { | ||
77 | var key = MatcherUtils.toRefineryTuple(updateElement); | ||
78 | notifyChange(key, !isInsertion, isInsertion); | ||
79 | } | ||
80 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java new file mode 100644 index 00000000..b0b507fe --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Iterator; | ||
13 | |||
14 | /** | ||
15 | * Cursor for a functional result set that iterates over a stream of raw matches and doesn't check whether the | ||
16 | * functional dependency of the output on the inputs is obeyed. | ||
17 | * @param <T> The output type. | ||
18 | */ | ||
19 | class UnsafeFunctionalCursor<T> implements Cursor<Tuple, T> { | ||
20 | private final Iterator<? extends ITuple> tuplesIterator; | ||
21 | private boolean terminated; | ||
22 | private Tuple key; | ||
23 | private T value; | ||
24 | |||
25 | public UnsafeFunctionalCursor(Iterator<? extends ITuple> tuplesIterator) { | ||
26 | this.tuplesIterator = tuplesIterator; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public Tuple getKey() { | ||
31 | return key; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public T getValue() { | ||
36 | return value; | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public boolean isTerminated() { | ||
41 | return terminated; | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public boolean move() { | ||
46 | if (!terminated && tuplesIterator.hasNext()) { | ||
47 | var match = tuplesIterator.next(); | ||
48 | key = MatcherUtils.keyToRefineryTuple(match); | ||
49 | value = MatcherUtils.getValue(match); | ||
50 | return true; | ||
51 | } | ||
52 | terminated = true; | ||
53 | return false; | ||
54 | } | ||
55 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java new file mode 100644 index 00000000..cf127291 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | class AssumptionEvaluator extends TermEvaluator<Boolean> { | ||
12 | public AssumptionEvaluator(Term<Boolean> term) { | ||
13 | super(term); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Object evaluateExpression(IValueProvider provider) { | ||
18 | var result = super.evaluateExpression(provider); | ||
19 | return result == null ? Boolean.FALSE : result; | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java deleted file mode 100644 index 60f1bcae..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java +++ /dev/null | |||
@@ -1,223 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.pquery; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | ||
7 | import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation; | ||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
19 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
20 | import tools.refinery.store.query.DNF; | ||
21 | import tools.refinery.store.query.DNFAnd; | ||
22 | import tools.refinery.store.query.DNFUtils; | ||
23 | import tools.refinery.store.query.Variable; | ||
24 | import tools.refinery.store.query.atom.*; | ||
25 | import tools.refinery.store.query.view.AnyRelationView; | ||
26 | |||
27 | import java.util.*; | ||
28 | import java.util.function.Function; | ||
29 | import java.util.stream.Collectors; | ||
30 | |||
31 | public class DNF2PQuery { | ||
32 | private static final Object P_CONSTRAINT_LOCK = new Object(); | ||
33 | |||
34 | private final Set<DNF> translating = new LinkedHashSet<>(); | ||
35 | |||
36 | private final Map<DNF, RawPQuery> dnf2PQueryMap = new HashMap<>(); | ||
37 | |||
38 | private final Map<AnyRelationView, RelationViewWrapper> view2WrapperMap = new LinkedHashMap<>(); | ||
39 | |||
40 | private final Map<AnyRelationView, RawPQuery> view2EmbeddedMap = new HashMap<>(); | ||
41 | |||
42 | private Function<DNF, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null, | ||
43 | QueryEvaluationHint.BackendRequirement.UNSPECIFIED); | ||
44 | |||
45 | public void setComputeHint(Function<DNF, QueryEvaluationHint> computeHint) { | ||
46 | this.computeHint = computeHint; | ||
47 | } | ||
48 | |||
49 | public RawPQuery translate(DNF dnfQuery) { | ||
50 | if (translating.contains(dnfQuery)) { | ||
51 | var path = translating.stream().map(DNF::name).collect(Collectors.joining(" -> ")); | ||
52 | throw new IllegalStateException("Circular reference %s -> %s detected".formatted(path, | ||
53 | dnfQuery.name())); | ||
54 | } | ||
55 | // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant | ||
56 | // way, which would cause a ConcurrentModificationException with computeIfAbsent. | ||
57 | var pQuery = dnf2PQueryMap.get(dnfQuery); | ||
58 | if (pQuery == null) { | ||
59 | translating.add(dnfQuery); | ||
60 | try { | ||
61 | pQuery = doTranslate(dnfQuery); | ||
62 | dnf2PQueryMap.put(dnfQuery, pQuery); | ||
63 | } finally { | ||
64 | translating.remove(dnfQuery); | ||
65 | } | ||
66 | } | ||
67 | return pQuery; | ||
68 | } | ||
69 | |||
70 | public Map<AnyRelationView, IInputKey> getRelationViews() { | ||
71 | return Collections.unmodifiableMap(view2WrapperMap); | ||
72 | } | ||
73 | |||
74 | public RawPQuery getAlreadyTranslated(DNF dnfQuery) { | ||
75 | return dnf2PQueryMap.get(dnfQuery); | ||
76 | } | ||
77 | |||
78 | private RawPQuery doTranslate(DNF dnfQuery) { | ||
79 | var pQuery = new RawPQuery(dnfQuery.getUniqueName()); | ||
80 | pQuery.setEvaluationHints(computeHint.apply(dnfQuery)); | ||
81 | |||
82 | Map<Variable, PParameter> parameters = new HashMap<>(); | ||
83 | for (Variable variable : dnfQuery.getParameters()) { | ||
84 | parameters.put(variable, new PParameter(variable.getUniqueName())); | ||
85 | } | ||
86 | |||
87 | List<PParameter> parameterList = new ArrayList<>(); | ||
88 | for (var param : dnfQuery.getParameters()) { | ||
89 | parameterList.add(parameters.get(param)); | ||
90 | } | ||
91 | pQuery.setParameters(parameterList); | ||
92 | |||
93 | for (var functionalDependency : dnfQuery.getFunctionalDependencies()) { | ||
94 | var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency"); | ||
95 | for (var forEachVariable : functionalDependency.forEach()) { | ||
96 | functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName()); | ||
97 | } | ||
98 | for (var uniqueVariable : functionalDependency.unique()) { | ||
99 | functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName()); | ||
100 | } | ||
101 | pQuery.addAnnotation(functionalDependencyAnnotation); | ||
102 | } | ||
103 | |||
104 | // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates | ||
105 | // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating | ||
106 | // any query constraints to avoid a data race. | ||
107 | synchronized (P_CONSTRAINT_LOCK) { | ||
108 | for (DNFAnd clause : dnfQuery.getClauses()) { | ||
109 | PBody body = new PBody(pQuery); | ||
110 | List<ExportedParameter> symbolicParameters = new ArrayList<>(); | ||
111 | for (var param : dnfQuery.getParameters()) { | ||
112 | PVariable pVar = body.getOrCreateVariableByName(param.getUniqueName()); | ||
113 | symbolicParameters.add(new ExportedParameter(body, pVar, parameters.get(param))); | ||
114 | } | ||
115 | body.setSymbolicParameters(symbolicParameters); | ||
116 | pQuery.addBody(body); | ||
117 | for (DNFAtom constraint : clause.constraints()) { | ||
118 | translateDNFAtom(constraint, body); | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | |||
123 | return pQuery; | ||
124 | } | ||
125 | |||
126 | private void translateDNFAtom(DNFAtom constraint, PBody body) { | ||
127 | if (constraint instanceof EquivalenceAtom equivalenceAtom) { | ||
128 | translateEquivalenceAtom(equivalenceAtom, body); | ||
129 | } else if (constraint instanceof RelationViewAtom relationViewAtom) { | ||
130 | translateRelationViewAtom(relationViewAtom, body); | ||
131 | } else if (constraint instanceof DNFCallAtom callAtom) { | ||
132 | translateCallAtom(callAtom, body); | ||
133 | } else if (constraint instanceof ConstantAtom constantAtom) { | ||
134 | translateConstantAtom(constantAtom, body); | ||
135 | } else { | ||
136 | throw new IllegalArgumentException("Unknown constraint: " + constraint.toString()); | ||
137 | } | ||
138 | } | ||
139 | |||
140 | private void translateEquivalenceAtom(EquivalenceAtom equivalence, PBody body) { | ||
141 | PVariable varSource = body.getOrCreateVariableByName(equivalence.left().getUniqueName()); | ||
142 | PVariable varTarget = body.getOrCreateVariableByName(equivalence.right().getUniqueName()); | ||
143 | if (equivalence.positive()) { | ||
144 | new Equality(body, varSource, varTarget); | ||
145 | } else { | ||
146 | new Inequality(body, varSource, varTarget); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | private void translateRelationViewAtom(RelationViewAtom relationViewAtom, PBody body) { | ||
151 | var substitution = translateSubstitution(relationViewAtom.getSubstitution(), body); | ||
152 | var polarity = relationViewAtom.getPolarity(); | ||
153 | var relationView = relationViewAtom.getTarget(); | ||
154 | if (polarity == CallPolarity.POSITIVE) { | ||
155 | new TypeConstraint(body, substitution, wrapView(relationView)); | ||
156 | } else { | ||
157 | var embeddedPQuery = translateEmbeddedRelationViewPQuery(relationView); | ||
158 | switch (polarity) { | ||
159 | case TRANSITIVE -> new BinaryTransitiveClosure(body, substitution, embeddedPQuery); | ||
160 | case NEGATIVE -> new NegativePatternCall(body, substitution, embeddedPQuery); | ||
161 | default -> throw new IllegalArgumentException("Unknown polarity: " + polarity); | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
166 | private static Tuple translateSubstitution(List<Variable> substitution, PBody body) { | ||
167 | int arity = substitution.size(); | ||
168 | Object[] variables = new Object[arity]; | ||
169 | for (int i = 0; i < arity; i++) { | ||
170 | var variable = substitution.get(i); | ||
171 | variables[i] = body.getOrCreateVariableByName(variable.getUniqueName()); | ||
172 | } | ||
173 | return Tuples.flatTupleOf(variables); | ||
174 | } | ||
175 | |||
176 | private RawPQuery translateEmbeddedRelationViewPQuery(AnyRelationView relationView) { | ||
177 | return view2EmbeddedMap.computeIfAbsent(relationView, this::doTranslateEmbeddedRelationViewPQuery); | ||
178 | } | ||
179 | |||
180 | private RawPQuery doTranslateEmbeddedRelationViewPQuery(AnyRelationView relationView) { | ||
181 | var embeddedPQuery = new RawPQuery(DNFUtils.generateUniqueName(relationView.name()), PVisibility.EMBEDDED); | ||
182 | var body = new PBody(embeddedPQuery); | ||
183 | int arity = relationView.arity(); | ||
184 | var parameters = new ArrayList<PParameter>(arity); | ||
185 | var arguments = new Object[arity]; | ||
186 | var symbolicParameters = new ArrayList<ExportedParameter>(arity); | ||
187 | for (int i = 0; i < arity; i++) { | ||
188 | var parameterName = "p" + i; | ||
189 | var parameter = new PParameter(parameterName); | ||
190 | parameters.add(parameter); | ||
191 | var variable = body.getOrCreateVariableByName(parameterName); | ||
192 | arguments[i] = variable; | ||
193 | symbolicParameters.add(new ExportedParameter(body, variable, parameter)); | ||
194 | } | ||
195 | embeddedPQuery.setParameters(parameters); | ||
196 | body.setSymbolicParameters(symbolicParameters); | ||
197 | var argumentTuple = Tuples.flatTupleOf(arguments); | ||
198 | new TypeConstraint(body, argumentTuple, wrapView(relationView)); | ||
199 | embeddedPQuery.addBody(body); | ||
200 | return embeddedPQuery; | ||
201 | } | ||
202 | |||
203 | private RelationViewWrapper wrapView(AnyRelationView relationView) { | ||
204 | return view2WrapperMap.computeIfAbsent(relationView, RelationViewWrapper::new); | ||
205 | } | ||
206 | |||
207 | private void translateCallAtom(DNFCallAtom callAtom, PBody body) { | ||
208 | var variablesTuple = translateSubstitution(callAtom.getSubstitution(), body); | ||
209 | var translatedReferred = translate(callAtom.getTarget()); | ||
210 | var polarity = callAtom.getPolarity(); | ||
211 | switch (polarity) { | ||
212 | case POSITIVE -> new PositivePatternCall(body, variablesTuple, translatedReferred); | ||
213 | case TRANSITIVE -> new BinaryTransitiveClosure(body, variablesTuple, translatedReferred); | ||
214 | case NEGATIVE -> new NegativePatternCall(body, variablesTuple, translatedReferred); | ||
215 | default -> throw new IllegalArgumentException("Unknown polarity: " + polarity); | ||
216 | } | ||
217 | } | ||
218 | |||
219 | private void translateConstantAtom(ConstantAtom constantAtom, PBody body) { | ||
220 | var variable = body.getOrCreateVariableByName(constantAtom.variable().getUniqueName()); | ||
221 | new ConstantValue(body, variable, constantAtom.nodeId()); | ||
222 | } | ||
223 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java new file mode 100644 index 00000000..5b0ea61d --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java | |||
@@ -0,0 +1,266 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue; | ||
19 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
20 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
21 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | ||
22 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameterDirection; | ||
23 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; | ||
24 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
25 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
26 | import tools.refinery.store.query.dnf.Dnf; | ||
27 | import tools.refinery.store.query.dnf.DnfClause; | ||
28 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
29 | import tools.refinery.store.query.literal.*; | ||
30 | import tools.refinery.store.query.term.ConstantTerm; | ||
31 | import tools.refinery.store.query.term.StatefulAggregator; | ||
32 | import tools.refinery.store.query.term.StatelessAggregator; | ||
33 | import tools.refinery.store.query.term.Variable; | ||
34 | import tools.refinery.store.query.view.AnySymbolView; | ||
35 | import tools.refinery.store.util.CycleDetectingMapper; | ||
36 | |||
37 | import java.util.*; | ||
38 | import java.util.function.Function; | ||
39 | import java.util.stream.Collectors; | ||
40 | |||
41 | public class Dnf2PQuery { | ||
42 | private static final Object P_CONSTRAINT_LOCK = new Object(); | ||
43 | private final CycleDetectingMapper<Dnf, RawPQuery> mapper = new CycleDetectingMapper<>(Dnf::name, | ||
44 | this::doTranslate); | ||
45 | private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this); | ||
46 | private final Map<Dnf, QueryEvaluationHint> hintOverrides = new LinkedHashMap<>(); | ||
47 | private Function<Dnf, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null, | ||
48 | (IQueryBackendFactory) null); | ||
49 | |||
50 | public void setComputeHint(Function<Dnf, QueryEvaluationHint> computeHint) { | ||
51 | this.computeHint = computeHint; | ||
52 | } | ||
53 | |||
54 | public RawPQuery translate(Dnf dnfQuery) { | ||
55 | return mapper.map(dnfQuery); | ||
56 | } | ||
57 | |||
58 | public Map<AnySymbolView, IInputKey> getSymbolViews() { | ||
59 | return wrapperFactory.getSymbolViews(); | ||
60 | } | ||
61 | |||
62 | public void hint(Dnf dnf, QueryEvaluationHint hint) { | ||
63 | hintOverrides.compute(dnf, (ignoredKey, existingHint) -> | ||
64 | existingHint == null ? hint : existingHint.overrideBy(hint)); | ||
65 | } | ||
66 | |||
67 | private QueryEvaluationHint consumeHint(Dnf dnf) { | ||
68 | var defaultHint = computeHint.apply(dnf); | ||
69 | var existingHint = hintOverrides.remove(dnf); | ||
70 | return defaultHint.overrideBy(existingHint); | ||
71 | } | ||
72 | |||
73 | public void assertNoUnusedHints() { | ||
74 | if (hintOverrides.isEmpty()) { | ||
75 | return; | ||
76 | } | ||
77 | var unusedHints = hintOverrides.keySet().stream().map(Dnf::name).collect(Collectors.joining(", ")); | ||
78 | throw new IllegalStateException( | ||
79 | "Unused query evaluation hints for %s. Hints must be set before a query is added to the engine" | ||
80 | .formatted(unusedHints)); | ||
81 | } | ||
82 | |||
83 | private RawPQuery doTranslate(Dnf dnfQuery) { | ||
84 | var pQuery = new RawPQuery(dnfQuery.getUniqueName()); | ||
85 | pQuery.setEvaluationHints(consumeHint(dnfQuery)); | ||
86 | |||
87 | Map<SymbolicParameter, PParameter> parameters = new HashMap<>(); | ||
88 | List<PParameter> parameterList = new ArrayList<>(); | ||
89 | for (var parameter : dnfQuery.getSymbolicParameters()) { | ||
90 | var direction = switch (parameter.getDirection()) { | ||
91 | case OUT -> parameter.isUnifiable() ? PParameterDirection.INOUT : PParameterDirection.OUT; | ||
92 | case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported" | ||
93 | .formatted(dnfQuery, parameter.getVariable())); | ||
94 | }; | ||
95 | var pParameter = new PParameter(parameter.getVariable().getUniqueName(), null, null, direction); | ||
96 | parameters.put(parameter, pParameter); | ||
97 | parameterList.add(pParameter); | ||
98 | } | ||
99 | |||
100 | pQuery.setParameters(parameterList); | ||
101 | |||
102 | for (var functionalDependency : dnfQuery.getFunctionalDependencies()) { | ||
103 | var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency"); | ||
104 | for (var forEachVariable : functionalDependency.forEach()) { | ||
105 | functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName()); | ||
106 | } | ||
107 | for (var uniqueVariable : functionalDependency.unique()) { | ||
108 | functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName()); | ||
109 | } | ||
110 | pQuery.addAnnotation(functionalDependencyAnnotation); | ||
111 | } | ||
112 | |||
113 | // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates | ||
114 | // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating | ||
115 | // any query literals to avoid a data race. | ||
116 | synchronized (P_CONSTRAINT_LOCK) { | ||
117 | for (DnfClause clause : dnfQuery.getClauses()) { | ||
118 | PBody body = new PBody(pQuery); | ||
119 | List<ExportedParameter> parameterExports = new ArrayList<>(); | ||
120 | for (var parameter : dnfQuery.getSymbolicParameters()) { | ||
121 | PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName()); | ||
122 | parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter))); | ||
123 | } | ||
124 | body.setSymbolicParameters(parameterExports); | ||
125 | pQuery.addBody(body); | ||
126 | for (Literal literal : clause.literals()) { | ||
127 | translateLiteral(literal, body); | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
132 | return pQuery; | ||
133 | } | ||
134 | |||
135 | private void translateLiteral(Literal literal, PBody body) { | ||
136 | if (literal instanceof EquivalenceLiteral equivalenceLiteral) { | ||
137 | translateEquivalenceLiteral(equivalenceLiteral, body); | ||
138 | } else if (literal instanceof CallLiteral callLiteral) { | ||
139 | translateCallLiteral(callLiteral, body); | ||
140 | } else if (literal instanceof ConstantLiteral constantLiteral) { | ||
141 | translateConstantLiteral(constantLiteral, body); | ||
142 | } else if (literal instanceof AssignLiteral<?> assignLiteral) { | ||
143 | translateAssignLiteral(assignLiteral, body); | ||
144 | } else if (literal instanceof AssumeLiteral assumeLiteral) { | ||
145 | translateAssumeLiteral(assumeLiteral, body); | ||
146 | } else if (literal instanceof CountLiteral countLiteral) { | ||
147 | translateCountLiteral(countLiteral, body); | ||
148 | } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) { | ||
149 | translateAggregationLiteral(aggregationLiteral, body); | ||
150 | } else { | ||
151 | throw new IllegalArgumentException("Unknown literal: " + literal.toString()); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) { | ||
156 | PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.left().getUniqueName()); | ||
157 | PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.right().getUniqueName()); | ||
158 | if (equivalenceLiteral.positive()) { | ||
159 | new Equality(body, varSource, varTarget); | ||
160 | } else { | ||
161 | new Inequality(body, varSource, varTarget); | ||
162 | } | ||
163 | } | ||
164 | |||
165 | private void translateCallLiteral(CallLiteral callLiteral, PBody body) { | ||
166 | var polarity = callLiteral.getPolarity(); | ||
167 | switch (polarity) { | ||
168 | case POSITIVE -> { | ||
169 | var substitution = translateSubstitution(callLiteral.getArguments(), body); | ||
170 | var constraint = callLiteral.getTarget(); | ||
171 | if (constraint instanceof Dnf dnf) { | ||
172 | var pattern = translate(dnf); | ||
173 | new PositivePatternCall(body, substitution, pattern); | ||
174 | } else if (constraint instanceof AnySymbolView symbolView) { | ||
175 | var inputKey = wrapperFactory.getInputKey(symbolView); | ||
176 | new TypeConstraint(body, substitution, inputKey); | ||
177 | } else { | ||
178 | throw new IllegalArgumentException("Unknown Constraint: " + constraint); | ||
179 | } | ||
180 | } | ||
181 | case TRANSITIVE -> { | ||
182 | var substitution = translateSubstitution(callLiteral.getArguments(), body); | ||
183 | var constraint = callLiteral.getTarget(); | ||
184 | PQuery pattern; | ||
185 | if (constraint instanceof Dnf dnf) { | ||
186 | pattern = translate(dnf); | ||
187 | } else if (constraint instanceof AnySymbolView symbolView) { | ||
188 | pattern = wrapperFactory.wrapSymbolViewIdentityArguments(symbolView); | ||
189 | } else { | ||
190 | throw new IllegalArgumentException("Unknown Constraint: " + constraint); | ||
191 | } | ||
192 | new BinaryTransitiveClosure(body, substitution, pattern); | ||
193 | } | ||
194 | case NEGATIVE -> { | ||
195 | var wrappedCall = wrapperFactory.maybeWrapConstraint(callLiteral); | ||
196 | var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); | ||
197 | var pattern = wrappedCall.pattern(); | ||
198 | new NegativePatternCall(body, substitution, pattern); | ||
199 | } | ||
200 | default -> throw new IllegalArgumentException("Unknown polarity: " + polarity); | ||
201 | } | ||
202 | } | ||
203 | |||
204 | private static Tuple translateSubstitution(List<Variable> substitution, PBody body) { | ||
205 | int arity = substitution.size(); | ||
206 | Object[] variables = new Object[arity]; | ||
207 | for (int i = 0; i < arity; i++) { | ||
208 | var variable = substitution.get(i); | ||
209 | variables[i] = body.getOrCreateVariableByName(variable.getUniqueName()); | ||
210 | } | ||
211 | return Tuples.flatTupleOf(variables); | ||
212 | } | ||
213 | |||
214 | private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) { | ||
215 | var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName()); | ||
216 | new ConstantValue(body, variable, constantLiteral.nodeId()); | ||
217 | } | ||
218 | |||
219 | private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) { | ||
220 | var variable = body.getOrCreateVariableByName(assignLiteral.variable().getUniqueName()); | ||
221 | var term = assignLiteral.term(); | ||
222 | if (term instanceof ConstantTerm<T> constantTerm) { | ||
223 | new ConstantValue(body, variable, constantTerm.getValue()); | ||
224 | } else { | ||
225 | var evaluator = new TermEvaluator<>(term); | ||
226 | new ExpressionEvaluation(body, evaluator, variable); | ||
227 | } | ||
228 | } | ||
229 | |||
230 | private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) { | ||
231 | var evaluator = new AssumptionEvaluator(assumeLiteral.term()); | ||
232 | new ExpressionEvaluation(body, evaluator, null); | ||
233 | } | ||
234 | |||
235 | private void translateCountLiteral(CountLiteral countLiteral, PBody body) { | ||
236 | var wrappedCall = wrapperFactory.maybeWrapConstraint(countLiteral); | ||
237 | var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); | ||
238 | var resultVariable = body.getOrCreateVariableByName(countLiteral.getResultVariable().getUniqueName()); | ||
239 | new PatternMatchCounter(body, substitution, wrappedCall.pattern(), resultVariable); | ||
240 | } | ||
241 | |||
242 | private <R, T> void translateAggregationLiteral(AggregationLiteral<R, T> aggregationLiteral, PBody body) { | ||
243 | var aggregator = aggregationLiteral.getAggregator(); | ||
244 | IMultisetAggregationOperator<T, ?, R> aggregationOperator; | ||
245 | if (aggregator instanceof StatelessAggregator<R, T> statelessAggregator) { | ||
246 | aggregationOperator = new StatelessMultisetAggregator<>(statelessAggregator); | ||
247 | } else if (aggregator instanceof StatefulAggregator<R, T> statefulAggregator) { | ||
248 | aggregationOperator = new StatefulMultisetAggregator<>(statefulAggregator); | ||
249 | } else { | ||
250 | throw new IllegalArgumentException("Unknown aggregator: " + aggregator); | ||
251 | } | ||
252 | var wrappedCall = wrapperFactory.maybeWrapConstraint(aggregationLiteral); | ||
253 | var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); | ||
254 | var inputVariable = body.getOrCreateVariableByName(aggregationLiteral.getInputVariable().getUniqueName()); | ||
255 | var aggregatedColumn = substitution.invertIndex().get(inputVariable); | ||
256 | if (aggregatedColumn == null) { | ||
257 | throw new IllegalStateException("Input variable %s not found in substitution %s".formatted(inputVariable, | ||
258 | substitution)); | ||
259 | } | ||
260 | var boundAggregator = new BoundAggregator(aggregationOperator, aggregator.getInputType(), | ||
261 | aggregator.getResultType()); | ||
262 | var resultVariable = body.getOrCreateVariableByName(aggregationLiteral.getResultVariable().getUniqueName()); | ||
263 | new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable, | ||
264 | aggregatedColumn); | ||
265 | } | ||
266 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java new file mode 100644 index 00000000..2b7280f2 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java | |||
@@ -0,0 +1,189 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; | ||
12 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; | ||
13 | import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; | ||
14 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | ||
15 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; | ||
16 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; | ||
17 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
18 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
19 | import tools.refinery.store.query.Constraint; | ||
20 | import tools.refinery.store.query.dnf.Dnf; | ||
21 | import tools.refinery.store.query.dnf.DnfUtils; | ||
22 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
23 | import tools.refinery.store.query.term.ParameterDirection; | ||
24 | import tools.refinery.store.query.term.Variable; | ||
25 | import tools.refinery.store.query.view.AnySymbolView; | ||
26 | import tools.refinery.store.query.view.SymbolView; | ||
27 | import tools.refinery.store.util.CycleDetectingMapper; | ||
28 | |||
29 | import java.util.*; | ||
30 | import java.util.function.ToIntFunction; | ||
31 | |||
32 | class QueryWrapperFactory { | ||
33 | private final Dnf2PQuery dnf2PQuery; | ||
34 | private final Map<AnySymbolView, SymbolViewWrapper> view2WrapperMap = new LinkedHashMap<>(); | ||
35 | private final CycleDetectingMapper<RemappedConstraint, RawPQuery> wrapConstraint = new CycleDetectingMapper<>( | ||
36 | RemappedConstraint::toString, this::doWrapConstraint); | ||
37 | |||
38 | QueryWrapperFactory(Dnf2PQuery dnf2PQuery) { | ||
39 | this.dnf2PQuery = dnf2PQuery; | ||
40 | } | ||
41 | |||
42 | public PQuery wrapSymbolViewIdentityArguments(AnySymbolView symbolView) { | ||
43 | var identity = new int[symbolView.arity()]; | ||
44 | for (int i = 0; i < identity.length; i++) { | ||
45 | identity[i] = i; | ||
46 | } | ||
47 | return maybeWrapConstraint(symbolView, identity); | ||
48 | } | ||
49 | |||
50 | public WrappedCall maybeWrapConstraint(AbstractCallLiteral callLiteral) { | ||
51 | var arguments = callLiteral.getArguments(); | ||
52 | int arity = arguments.size(); | ||
53 | var remappedParameters = new int[arity]; | ||
54 | var unboundVariableIndices = new HashMap<Variable, Integer>(); | ||
55 | var appendVariable = new VariableAppender(); | ||
56 | for (int i = 0; i < arity; i++) { | ||
57 | var variable = arguments.get(i); | ||
58 | // Unify all variables to avoid VIATRA bugs, even if they're bound in the containing clause. | ||
59 | remappedParameters[i] = unboundVariableIndices.computeIfAbsent(variable, appendVariable::applyAsInt); | ||
60 | } | ||
61 | var pattern = maybeWrapConstraint(callLiteral.getTarget(), remappedParameters); | ||
62 | return new WrappedCall(pattern, appendVariable.getRemappedArguments()); | ||
63 | } | ||
64 | |||
65 | private PQuery maybeWrapConstraint(Constraint constraint, int[] remappedParameters) { | ||
66 | if (remappedParameters.length != constraint.arity()) { | ||
67 | throw new IllegalArgumentException("Constraint %s expected %d parameters, but got %d parameters".formatted( | ||
68 | constraint, constraint.arity(), remappedParameters.length)); | ||
69 | } | ||
70 | if (constraint instanceof Dnf dnf && isIdentity(remappedParameters)) { | ||
71 | return dnf2PQuery.translate(dnf); | ||
72 | } | ||
73 | return wrapConstraint.map(new RemappedConstraint(constraint, remappedParameters)); | ||
74 | } | ||
75 | |||
76 | private static boolean isIdentity(int[] remappedParameters) { | ||
77 | for (int i = 0; i < remappedParameters.length; i++) { | ||
78 | if (remappedParameters[i] != i) { | ||
79 | return false; | ||
80 | } | ||
81 | } | ||
82 | return true; | ||
83 | } | ||
84 | |||
85 | private RawPQuery doWrapConstraint(RemappedConstraint remappedConstraint) { | ||
86 | var constraint = remappedConstraint.constraint(); | ||
87 | var remappedParameters = remappedConstraint.remappedParameters(); | ||
88 | |||
89 | checkNoInputParameters(constraint); | ||
90 | |||
91 | var embeddedPQuery = new RawPQuery(DnfUtils.generateUniqueName(constraint.name()), PVisibility.EMBEDDED); | ||
92 | var body = new PBody(embeddedPQuery); | ||
93 | int arity = Arrays.stream(remappedParameters).max().orElse(-1) + 1; | ||
94 | var parameters = new ArrayList<PParameter>(arity); | ||
95 | var parameterVariables = new PVariable[arity]; | ||
96 | var symbolicParameters = new ArrayList<ExportedParameter>(arity); | ||
97 | for (int i = 0; i < arity; i++) { | ||
98 | var parameterName = "p" + i; | ||
99 | var parameter = new PParameter(parameterName); | ||
100 | parameters.add(parameter); | ||
101 | var variable = body.getOrCreateVariableByName(parameterName); | ||
102 | parameterVariables[i] = variable; | ||
103 | symbolicParameters.add(new ExportedParameter(body, variable, parameter)); | ||
104 | } | ||
105 | embeddedPQuery.setParameters(parameters); | ||
106 | body.setSymbolicParameters(symbolicParameters); | ||
107 | |||
108 | var arguments = new Object[remappedParameters.length]; | ||
109 | for (int i = 0; i < remappedParameters.length; i++) { | ||
110 | arguments[i] = parameterVariables[remappedParameters[i]]; | ||
111 | } | ||
112 | var argumentTuple = Tuples.flatTupleOf(arguments); | ||
113 | |||
114 | addPositiveConstraint(constraint, body, argumentTuple); | ||
115 | embeddedPQuery.addBody(body); | ||
116 | return embeddedPQuery; | ||
117 | } | ||
118 | |||
119 | private static void checkNoInputParameters(Constraint constraint) { | ||
120 | for (var constraintParameter : constraint.getParameters()) { | ||
121 | if (constraintParameter.getDirection() == ParameterDirection.IN) { | ||
122 | throw new IllegalArgumentException("Input parameter %s of %s is not supported" | ||
123 | .formatted(constraintParameter, constraint)); | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
128 | private void addPositiveConstraint(Constraint constraint, PBody body, Tuple argumentTuple) { | ||
129 | if (constraint instanceof SymbolView<?> view) { | ||
130 | new TypeConstraint(body, argumentTuple, getInputKey(view)); | ||
131 | } else if (constraint instanceof Dnf dnf) { | ||
132 | var calledPQuery = dnf2PQuery.translate(dnf); | ||
133 | new PositivePatternCall(body, argumentTuple, calledPQuery); | ||
134 | } else { | ||
135 | throw new IllegalArgumentException("Unknown Constraint: " + constraint); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | public IInputKey getInputKey(AnySymbolView symbolView) { | ||
140 | return view2WrapperMap.computeIfAbsent(symbolView, SymbolViewWrapper::new); | ||
141 | } | ||
142 | |||
143 | public Map<AnySymbolView, IInputKey> getSymbolViews() { | ||
144 | return Collections.unmodifiableMap(view2WrapperMap); | ||
145 | } | ||
146 | |||
147 | public record WrappedCall(PQuery pattern, List<Variable> remappedArguments) { | ||
148 | } | ||
149 | |||
150 | private static class VariableAppender implements ToIntFunction<Variable> { | ||
151 | private final List<Variable> remappedArguments = new ArrayList<>(); | ||
152 | private int nextIndex = 0; | ||
153 | |||
154 | @Override | ||
155 | public int applyAsInt(Variable variable) { | ||
156 | remappedArguments.add(variable); | ||
157 | int index = nextIndex; | ||
158 | nextIndex++; | ||
159 | return index; | ||
160 | } | ||
161 | |||
162 | public List<Variable> getRemappedArguments() { | ||
163 | return remappedArguments; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | private record RemappedConstraint(Constraint constraint, int[] remappedParameters) { | ||
168 | @Override | ||
169 | public boolean equals(Object o) { | ||
170 | if (this == o) return true; | ||
171 | if (o == null || getClass() != o.getClass()) return false; | ||
172 | RemappedConstraint that = (RemappedConstraint) o; | ||
173 | return constraint.equals(that.constraint) && Arrays.equals(remappedParameters, that.remappedParameters); | ||
174 | } | ||
175 | |||
176 | @Override | ||
177 | public int hashCode() { | ||
178 | int result = Objects.hash(constraint); | ||
179 | result = 31 * result + Arrays.hashCode(remappedParameters); | ||
180 | return result; | ||
181 | } | ||
182 | |||
183 | @Override | ||
184 | public String toString() { | ||
185 | return "RemappedConstraint{constraint=%s, remappedParameters=%s}".formatted(constraint, | ||
186 | Arrays.toString(remappedParameters)); | ||
187 | } | ||
188 | } | ||
189 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java index 71b74396..255738c5 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.pquery; | 6 | package tools.refinery.store.query.viatra.internal.pquery; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | 8 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; |
@@ -9,6 +14,7 @@ import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; | |||
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; | 14 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; |
10 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; | 15 | import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; |
11 | import tools.refinery.store.query.viatra.internal.RelationalScope; | 16 | import tools.refinery.store.query.viatra.internal.RelationalScope; |
17 | import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; | ||
12 | 18 | ||
13 | import java.util.LinkedHashSet; | 19 | import java.util.LinkedHashSet; |
14 | import java.util.List; | 20 | import java.util.List; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java deleted file mode 100644 index e944e873..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java +++ /dev/null | |||
@@ -1,72 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.pquery; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; | ||
4 | import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; | ||
5 | import tools.refinery.store.query.ResultSet; | ||
6 | import tools.refinery.store.query.viatra.ViatraTupleLike; | ||
7 | import tools.refinery.store.tuple.Tuple; | ||
8 | import tools.refinery.store.tuple.TupleLike; | ||
9 | |||
10 | import java.util.Optional; | ||
11 | import java.util.stream.Stream; | ||
12 | |||
13 | public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet { | ||
14 | protected final Object[] empty; | ||
15 | |||
16 | public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { | ||
17 | super(specification); | ||
18 | empty = new Object[specification.getParameterNames().size()]; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public boolean hasResult() { | ||
23 | return backend.hasMatch(empty); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public boolean hasResult(Tuple parameters) { | ||
28 | return backend.hasMatch(toParametersArray(parameters)); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Optional<TupleLike> oneResult() { | ||
33 | return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public Optional<TupleLike> oneResult(Tuple parameters) { | ||
38 | return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public Stream<TupleLike> allResults() { | ||
43 | return backend.getAllMatches(empty).map(ViatraTupleLike::new); | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public Stream<TupleLike> allResults(Tuple parameters) { | ||
48 | return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public int countResults() { | ||
53 | return backend.countMatches(empty); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public int countResults(Tuple parameters) { | ||
58 | return backend.countMatches(toParametersArray(parameters)); | ||
59 | } | ||
60 | |||
61 | private Object[] toParametersArray(Tuple tuple) { | ||
62 | int size = tuple.getSize(); | ||
63 | var array = new Object[tuple.getSize()]; | ||
64 | for (int i = 0; i < size; i++) { | ||
65 | var value = tuple.get(i); | ||
66 | if (value >= 0) { | ||
67 | array[i] = Tuple.of(value); | ||
68 | } | ||
69 | } | ||
70 | return array; | ||
71 | } | ||
72 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java new file mode 100644 index 00000000..461416f7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
9 | import tools.refinery.store.query.term.StatefulAggregate; | ||
10 | import tools.refinery.store.query.term.StatefulAggregator; | ||
11 | |||
12 | import java.util.stream.Stream; | ||
13 | |||
14 | record StatefulMultisetAggregator<R, T>(StatefulAggregator<R, T> aggregator) | ||
15 | implements IMultisetAggregationOperator<T, StatefulAggregate<R, T>, R> { | ||
16 | @Override | ||
17 | public String getShortDescription() { | ||
18 | return getName(); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public String getName() { | ||
23 | return aggregator.toString(); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public StatefulAggregate<R, T> createNeutral() { | ||
28 | return aggregator.createEmptyAggregate(); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public boolean isNeutral(StatefulAggregate<R, T> result) { | ||
33 | return result.isEmpty(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public StatefulAggregate<R, T> update(StatefulAggregate<R, T> oldResult, T updateValue, boolean isInsertion) { | ||
38 | if (isInsertion) { | ||
39 | oldResult.add(updateValue); | ||
40 | } else { | ||
41 | oldResult.remove(updateValue); | ||
42 | } | ||
43 | return oldResult; | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public R getAggregate(StatefulAggregate<R, T> result) { | ||
48 | return result.getResult(); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public R aggregateStream(Stream<T> stream) { | ||
53 | return aggregator.aggregateStream(stream); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public StatefulAggregate<R, T> clone(StatefulAggregate<R, T> original) { | ||
58 | return original.deepCopy(); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public boolean contains(T value, StatefulAggregate<R, T> accumulator) { | ||
63 | return accumulator.contains(value); | ||
64 | } | ||
65 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java new file mode 100644 index 00000000..49175d75 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; | ||
9 | import tools.refinery.store.query.term.StatelessAggregator; | ||
10 | |||
11 | import java.util.stream.Stream; | ||
12 | |||
13 | record StatelessMultisetAggregator<R, T>(StatelessAggregator<R, T> aggregator) | ||
14 | implements IMultisetAggregationOperator<T, R, R> { | ||
15 | @Override | ||
16 | public String getShortDescription() { | ||
17 | return getName(); | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public String getName() { | ||
22 | return aggregator.toString(); | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public R createNeutral() { | ||
27 | return aggregator.getEmptyResult(); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public boolean isNeutral(R result) { | ||
32 | return createNeutral().equals(result); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public R update(R oldResult, T updateValue, boolean isInsertion) { | ||
37 | return isInsertion ? aggregator.add(oldResult, updateValue) : aggregator.remove(oldResult, updateValue); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public R getAggregate(R result) { | ||
42 | return result; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public R clone(R original) { | ||
47 | // Aggregate result is immutable. | ||
48 | return original; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public R aggregateStream(Stream<T> stream) { | ||
53 | return aggregator.aggregateStream(stream); | ||
54 | } | ||
55 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java index c442add8..a777613e 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java | |||
@@ -1,10 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.pquery; | 6 | package tools.refinery.store.query.viatra.internal.pquery; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; | 8 | import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; |
4 | import tools.refinery.store.query.view.AnyRelationView; | 9 | import tools.refinery.store.query.view.AnySymbolView; |
5 | 10 | ||
6 | public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> { | 11 | public class SymbolViewWrapper extends BaseInputKeyWrapper<AnySymbolView> { |
7 | public RelationViewWrapper(AnyRelationView wrappedKey) { | 12 | public SymbolViewWrapper(AnySymbolView wrappedKey) { |
8 | super(wrappedKey); | 13 | super(wrappedKey); |
9 | } | 14 | } |
10 | 15 | ||
@@ -27,4 +32,9 @@ public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> { | |||
27 | public boolean isEnumerable() { | 32 | public boolean isEnumerable() { |
28 | return true; | 33 | return true; |
29 | } | 34 | } |
35 | |||
36 | @Override | ||
37 | public String toString() { | ||
38 | return "RelationViewWrapper{wrappedKey=%s}".formatted(wrappedKey); | ||
39 | } | ||
30 | } | 40 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java new file mode 100644 index 00000000..1187f57a --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; | ||
10 | import tools.refinery.store.query.term.Term; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | |||
13 | import java.util.stream.Collectors; | ||
14 | |||
15 | class TermEvaluator<T> implements IExpressionEvaluator { | ||
16 | private final Term<T> term; | ||
17 | |||
18 | public TermEvaluator(Term<T> term) { | ||
19 | this.term = term; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public String getShortDescription() { | ||
24 | return term.toString(); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public Iterable<String> getInputParameterNames() { | ||
29 | return term.getInputVariables().stream().map(Variable::getUniqueName).collect(Collectors.toUnmodifiableSet()); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public Object evaluateExpression(IValueProvider provider) { | ||
34 | var valuation = new ValueProviderBasedValuation(provider); | ||
35 | return term.evaluate(valuation); | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java new file mode 100644 index 00000000..62cb8b3a --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java | |||
@@ -0,0 +1,19 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.pquery; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | public record ValueProviderBasedValuation(IValueProvider valueProvider) implements Valuation { | ||
13 | @Override | ||
14 | public <T> T getValue(DataVariable<T> variable) { | ||
15 | @SuppressWarnings("unchecked") | ||
16 | var value = (T) valueProvider.getValue(variable.getUniqueName()); | ||
17 | return value; | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java index 8a467066..986bb0b1 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java | |||
@@ -1,47 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.update; | 6 | package tools.refinery.store.query.viatra.internal.update; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | 8 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; |
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | 9 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; |
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | 10 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; |
6 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | 11 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; |
7 | import tools.refinery.store.query.view.AnyRelationView; | 12 | import tools.refinery.store.query.view.AnySymbolView; |
8 | import tools.refinery.store.query.view.RelationView; | 13 | import tools.refinery.store.query.view.SymbolView; |
9 | 14 | ||
10 | import java.util.HashMap; | 15 | import java.util.HashMap; |
11 | import java.util.Map; | 16 | import java.util.Map; |
12 | 17 | ||
13 | public class ModelUpdateListener { | 18 | public class ModelUpdateListener { |
14 | private final Map<AnyRelationView, RelationViewUpdateListener<?>> relationViewUpdateListeners; | 19 | private final Map<AnySymbolView, SymbolViewUpdateListener<?>> symbolViewUpdateListeners; |
15 | 20 | ||
16 | public ModelUpdateListener(ViatraModelQueryAdapterImpl adapter) { | 21 | public ModelUpdateListener(ViatraModelQueryAdapterImpl adapter) { |
17 | var relationViews = adapter.getStoreAdapter().getInputKeys().keySet(); | 22 | var symbolViews = adapter.getStoreAdapter().getInputKeys().keySet(); |
18 | relationViewUpdateListeners = new HashMap<>(relationViews.size()); | 23 | symbolViewUpdateListeners = new HashMap<>(symbolViews.size()); |
19 | for (var relationView : relationViews) { | 24 | for (var symbolView : symbolViews) { |
20 | registerView(adapter, (RelationView<?>) relationView); | 25 | registerView(adapter, (SymbolView<?>) symbolView); |
21 | } | 26 | } |
22 | } | 27 | } |
23 | 28 | ||
24 | private <T> void registerView(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) { | 29 | private <T> void registerView(ViatraModelQueryAdapterImpl adapter, SymbolView<T> view) { |
25 | var listener = RelationViewUpdateListener.of(adapter, relationView); | ||
26 | var model = adapter.getModel(); | 30 | var model = adapter.getModel(); |
27 | var interpretation = model.getInterpretation(relationView.getSymbol()); | 31 | var interpretation = model.getInterpretation(view.getSymbol()); |
28 | interpretation.addListener(listener, true); | 32 | var listener = SymbolViewUpdateListener.of(adapter, view, interpretation); |
29 | relationViewUpdateListeners.put(relationView, listener); | 33 | symbolViewUpdateListeners.put(view, listener); |
30 | } | 34 | } |
31 | 35 | ||
32 | public boolean containsRelationView(AnyRelationView relationView) { | 36 | public boolean containsSymbolView(AnySymbolView relationView) { |
33 | return relationViewUpdateListeners.containsKey(relationView); | 37 | return symbolViewUpdateListeners.containsKey(relationView); |
34 | } | 38 | } |
35 | 39 | ||
36 | public void addListener(IInputKey key, AnyRelationView relationView, ITuple seed, | 40 | public void addListener(IInputKey key, AnySymbolView symbolView, ITuple seed, |
37 | IQueryRuntimeContextListener listener) { | 41 | IQueryRuntimeContextListener listener) { |
38 | var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); | 42 | var symbolViewUpdateListener = symbolViewUpdateListeners.get(symbolView); |
39 | relationViewUpdateListener.addFilter(key, seed, listener); | 43 | symbolViewUpdateListener.addFilter(key, seed, listener); |
40 | } | 44 | } |
41 | 45 | ||
42 | public void removeListener(IInputKey key, AnyRelationView relationView, ITuple seed, | 46 | public void removeListener(IInputKey key, AnySymbolView symbolView, ITuple seed, |
43 | IQueryRuntimeContextListener listener) { | 47 | IQueryRuntimeContextListener listener) { |
44 | var relationViewUpdateListener = relationViewUpdateListeners.get(relationView); | 48 | var symbolViewUpdateListener = symbolViewUpdateListeners.get(symbolView); |
45 | relationViewUpdateListener.removeFilter(key, seed, listener); | 49 | symbolViewUpdateListener.removeFilter(key, seed, listener); |
46 | } | 50 | } |
47 | } | 51 | } |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java index 221f1b4a..efdbfcbe 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.update; | 6 | package tools.refinery.store.query.viatra.internal.update; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | 8 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; |
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java deleted file mode 100644 index bf6b4197..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java +++ /dev/null | |||
@@ -1,48 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
4 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
5 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
6 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
7 | import tools.refinery.store.model.InterpretationListener; | ||
8 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
9 | import tools.refinery.store.query.view.RelationView; | ||
10 | import tools.refinery.store.query.view.TuplePreservingRelationView; | ||
11 | |||
12 | import java.util.ArrayList; | ||
13 | import java.util.List; | ||
14 | |||
15 | public abstract class RelationViewUpdateListener<T> implements InterpretationListener<T> { | ||
16 | private final ViatraModelQueryAdapterImpl adapter; | ||
17 | private final List<RelationViewFilter> filters = new ArrayList<>(); | ||
18 | |||
19 | protected RelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter) { | ||
20 | this.adapter = adapter; | ||
21 | } | ||
22 | |||
23 | public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
24 | filters.add(new RelationViewFilter(inputKey, seed, listener)); | ||
25 | } | ||
26 | |||
27 | public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
28 | filters.remove(new RelationViewFilter(inputKey, seed, listener)); | ||
29 | } | ||
30 | |||
31 | protected void processUpdate(Tuple tuple, boolean isInsertion) { | ||
32 | adapter.markAsPending(); | ||
33 | int size = filters.size(); | ||
34 | // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead. | ||
35 | //noinspection ForLoopReplaceableByForEach | ||
36 | for (int i = 0; i < size; i++) { | ||
37 | filters.get(i).update(tuple, isInsertion); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | public static <T> RelationViewUpdateListener<T> of(ViatraModelQueryAdapterImpl adapter, | ||
42 | RelationView<T> relationView) { | ||
43 | if (relationView instanceof TuplePreservingRelationView<T> tuplePreservingRelationView) { | ||
44 | return new TuplePreservingRelationViewUpdateListener<>(adapter, tuplePreservingRelationView); | ||
45 | } | ||
46 | return new TupleChangingRelationViewUpdateListener<>(adapter, relationView); | ||
47 | } | ||
48 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java new file mode 100644 index 00000000..f1a2ac7c --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.update; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; | ||
9 | import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; | ||
10 | import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; | ||
11 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; | ||
12 | import tools.refinery.store.model.Interpretation; | ||
13 | import tools.refinery.store.model.InterpretationListener; | ||
14 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
15 | import tools.refinery.store.query.view.SymbolView; | ||
16 | import tools.refinery.store.query.view.TuplePreservingView; | ||
17 | |||
18 | import java.util.ArrayList; | ||
19 | import java.util.List; | ||
20 | |||
21 | public abstract class SymbolViewUpdateListener<T> implements InterpretationListener<T> { | ||
22 | private final ViatraModelQueryAdapterImpl adapter; | ||
23 | private final Interpretation<T> interpretation; | ||
24 | private final List<RelationViewFilter> filters = new ArrayList<>(); | ||
25 | |||
26 | protected SymbolViewUpdateListener(ViatraModelQueryAdapterImpl adapter, Interpretation<T> interpretation) { | ||
27 | this.adapter = adapter; | ||
28 | this.interpretation = interpretation; | ||
29 | } | ||
30 | |||
31 | public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
32 | if (filters.isEmpty()) { | ||
33 | // First filter to be added, from now on we have to subscribe to model updates. | ||
34 | interpretation.addListener(this, true); | ||
35 | } | ||
36 | filters.add(new RelationViewFilter(inputKey, seed, listener)); | ||
37 | } | ||
38 | |||
39 | public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { | ||
40 | if (filters.remove(new RelationViewFilter(inputKey, seed, listener)) && filters.isEmpty()) { | ||
41 | // Last listener to be added, we don't have be subscribed to model updates anymore. | ||
42 | interpretation.removeListener(this); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | protected void processUpdate(Tuple tuple, boolean isInsertion) { | ||
47 | adapter.markAsPending(); | ||
48 | int size = filters.size(); | ||
49 | // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead. | ||
50 | //noinspection ForLoopReplaceableByForEach | ||
51 | for (int i = 0; i < size; i++) { | ||
52 | filters.get(i).update(tuple, isInsertion); | ||
53 | } | ||
54 | } | ||
55 | |||
56 | public static <T> SymbolViewUpdateListener<T> of(ViatraModelQueryAdapterImpl adapter, | ||
57 | SymbolView<T> view, | ||
58 | Interpretation<T> interpretation) { | ||
59 | if (view instanceof TuplePreservingView<T> tuplePreservingRelationView) { | ||
60 | return new TuplePreservingViewUpdateListener<>(adapter, tuplePreservingRelationView, | ||
61 | interpretation); | ||
62 | } | ||
63 | return new TupleChangingViewUpdateListener<>(adapter, view, interpretation); | ||
64 | } | ||
65 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java deleted file mode 100644 index 14142884..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.update; | ||
2 | |||
3 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
4 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
5 | import tools.refinery.store.query.view.RelationView; | ||
6 | import tools.refinery.store.tuple.Tuple; | ||
7 | |||
8 | import java.util.Arrays; | ||
9 | |||
10 | public class TupleChangingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { | ||
11 | private final RelationView<T> relationView; | ||
12 | |||
13 | TupleChangingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) { | ||
14 | super(adapter); | ||
15 | this.relationView = relationView; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public void put(Tuple key, T fromValue, T toValue, boolean restoring) { | ||
20 | boolean fromPresent = relationView.filter(key, fromValue); | ||
21 | boolean toPresent = relationView.filter(key, toValue); | ||
22 | if (fromPresent) { | ||
23 | if (toPresent) { // value change | ||
24 | var fromArray = relationView.forwardMap(key, fromValue); | ||
25 | var toArray = relationView.forwardMap(key, toValue); | ||
26 | if (!Arrays.equals(fromArray, toArray)) { | ||
27 | processUpdate(Tuples.flatTupleOf(fromArray), false); | ||
28 | processUpdate(Tuples.flatTupleOf(toArray), true); | ||
29 | } | ||
30 | } else { // fromValue disappears | ||
31 | processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, fromValue)), false); | ||
32 | } | ||
33 | } else if (toPresent) { // toValue appears | ||
34 | processUpdate(Tuples.flatTupleOf(relationView.forwardMap(key, toValue)), true); | ||
35 | } | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java new file mode 100644 index 00000000..45d35571 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.update; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
10 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | ||
11 | import tools.refinery.store.query.view.SymbolView; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | import java.util.Arrays; | ||
15 | |||
16 | public class TupleChangingViewUpdateListener<T> extends SymbolViewUpdateListener<T> { | ||
17 | private final SymbolView<T> view; | ||
18 | |||
19 | TupleChangingViewUpdateListener(ViatraModelQueryAdapterImpl adapter, SymbolView<T> view, | ||
20 | Interpretation<T> interpretation) { | ||
21 | super(adapter, interpretation); | ||
22 | this.view = view; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public void put(Tuple key, T fromValue, T toValue, boolean restoring) { | ||
27 | boolean fromPresent = view.filter(key, fromValue); | ||
28 | boolean toPresent = view.filter(key, toValue); | ||
29 | if (fromPresent) { | ||
30 | if (toPresent) { // value change | ||
31 | var fromArray = view.forwardMap(key, fromValue); | ||
32 | var toArray = view.forwardMap(key, toValue); | ||
33 | if (!Arrays.equals(fromArray, toArray)) { | ||
34 | processUpdate(Tuples.flatTupleOf(fromArray), false); | ||
35 | processUpdate(Tuples.flatTupleOf(toArray), true); | ||
36 | } | ||
37 | } else { // fromValue disappears | ||
38 | processUpdate(Tuples.flatTupleOf(view.forwardMap(key, fromValue)), false); | ||
39 | } | ||
40 | } else if (toPresent) { // toValue appears | ||
41 | processUpdate(Tuples.flatTupleOf(view.forwardMap(key, toValue)), true); | ||
42 | } | ||
43 | } | ||
44 | } | ||
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java index 288e018a..c18dbafb 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java | |||
@@ -1,16 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra.internal.update; | 6 | package tools.refinery.store.query.viatra.internal.update; |
2 | 7 | ||
3 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; | 8 | import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; |
9 | import tools.refinery.store.model.Interpretation; | ||
4 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; | 10 | import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; |
5 | import tools.refinery.store.query.view.TuplePreservingRelationView; | 11 | import tools.refinery.store.query.view.TuplePreservingView; |
6 | import tools.refinery.store.tuple.Tuple; | 12 | import tools.refinery.store.tuple.Tuple; |
7 | 13 | ||
8 | public class TuplePreservingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { | 14 | public class TuplePreservingViewUpdateListener<T> extends SymbolViewUpdateListener<T> { |
9 | private final TuplePreservingRelationView<T> view; | 15 | private final TuplePreservingView<T> view; |
10 | 16 | ||
11 | TuplePreservingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, | 17 | TuplePreservingViewUpdateListener(ViatraModelQueryAdapterImpl adapter, TuplePreservingView<T> view, |
12 | TuplePreservingRelationView<T> view) { | 18 | Interpretation<T> interpretation) { |
13 | super(adapter); | 19 | super(adapter, interpretation); |
14 | this.view = view; | 20 | this.view = view; |
15 | } | 21 | } |
16 | 22 | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java new file mode 100644 index 00000000..6aae2ebe --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java | |||
@@ -0,0 +1,390 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.viatra.tests.QueryEngineTest; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.query.view.FunctionView; | ||
16 | import tools.refinery.store.query.view.KeyOnlyView; | ||
17 | import tools.refinery.store.representation.Symbol; | ||
18 | import tools.refinery.store.tuple.Tuple; | ||
19 | |||
20 | import java.util.List; | ||
21 | import java.util.Map; | ||
22 | import java.util.Optional; | ||
23 | |||
24 | import static tools.refinery.store.query.literal.Literals.not; | ||
25 | import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; | ||
26 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; | ||
27 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; | ||
28 | |||
29 | class DiagonalQueryTest { | ||
30 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
31 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
32 | private static final Symbol<Boolean> symbol = Symbol.of("symbol", 4); | ||
33 | private static final Symbol<Integer> intSymbol = Symbol.of("intSymbol", 4, Integer.class); | ||
34 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
35 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
36 | private static final AnySymbolView symbolView = new KeyOnlyView<>(symbol); | ||
37 | private static final FunctionView<Integer> intSymbolView = new FunctionView<>(intSymbol); | ||
38 | |||
39 | @QueryEngineTest | ||
40 | void inputKeyNegationTest(QueryEvaluationHint hint) { | ||
41 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of( | ||
42 | personView.call(p1), | ||
43 | not(symbolView.call(p1, p1, p2, p2)) | ||
44 | ))); | ||
45 | |||
46 | var store = ModelStore.builder() | ||
47 | .symbols(person, symbol) | ||
48 | .with(ViatraModelQueryAdapter.builder() | ||
49 | .defaultHint(hint) | ||
50 | .queries(query)) | ||
51 | .build(); | ||
52 | |||
53 | var model = store.createEmptyModel(); | ||
54 | var personInterpretation = model.getInterpretation(person); | ||
55 | var symbolInterpretation = model.getInterpretation(symbol); | ||
56 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
57 | var queryResultSet = queryEngine.getResultSet(query); | ||
58 | |||
59 | personInterpretation.put(Tuple.of(0), true); | ||
60 | personInterpretation.put(Tuple.of(1), true); | ||
61 | personInterpretation.put(Tuple.of(2), true); | ||
62 | |||
63 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
64 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
65 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
66 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
67 | |||
68 | queryEngine.flushChanges(); | ||
69 | assertResults(Map.of( | ||
70 | Tuple.of(0), false, | ||
71 | Tuple.of(1), true, | ||
72 | Tuple.of(2), true, | ||
73 | Tuple.of(3), false | ||
74 | ), queryResultSet); | ||
75 | } | ||
76 | |||
77 | @QueryEngineTest | ||
78 | void subQueryNegationTest(QueryEvaluationHint hint) { | ||
79 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder | ||
80 | .clause( | ||
81 | personView.call(p1), | ||
82 | symbolView.call(p1, p2, p3, p4) | ||
83 | ) | ||
84 | .clause( | ||
85 | personView.call(p2), | ||
86 | symbolView.call(p1, p2, p3, p4) | ||
87 | )); | ||
88 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause(p2 -> List.of( | ||
89 | personView.call(p1), | ||
90 | not(subQuery.call(p1, p1, p2, p2)) | ||
91 | ))); | ||
92 | |||
93 | var store = ModelStore.builder() | ||
94 | .symbols(person, symbol) | ||
95 | .with(ViatraModelQueryAdapter.builder() | ||
96 | .defaultHint(hint) | ||
97 | .queries(query)) | ||
98 | .build(); | ||
99 | |||
100 | var model = store.createEmptyModel(); | ||
101 | var personInterpretation = model.getInterpretation(person); | ||
102 | var symbolInterpretation = model.getInterpretation(symbol); | ||
103 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
104 | var queryResultSet = queryEngine.getResultSet(query); | ||
105 | |||
106 | personInterpretation.put(Tuple.of(0), true); | ||
107 | personInterpretation.put(Tuple.of(1), true); | ||
108 | personInterpretation.put(Tuple.of(2), true); | ||
109 | |||
110 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
111 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
112 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
113 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
114 | |||
115 | queryEngine.flushChanges(); | ||
116 | assertResults(Map.of( | ||
117 | Tuple.of(0), false, | ||
118 | Tuple.of(1), true, | ||
119 | Tuple.of(2), true, | ||
120 | Tuple.of(3), false | ||
121 | ), queryResultSet); | ||
122 | } | ||
123 | |||
124 | @QueryEngineTest | ||
125 | void inputKeyCountTest(QueryEvaluationHint hint) { | ||
126 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of( | ||
127 | personView.call(p1), | ||
128 | output.assign(symbolView.count(p1, p1, p2, p2)) | ||
129 | ))); | ||
130 | |||
131 | var store = ModelStore.builder() | ||
132 | .symbols(person, symbol) | ||
133 | .with(ViatraModelQueryAdapter.builder() | ||
134 | .defaultHint(hint) | ||
135 | .queries(query)) | ||
136 | .build(); | ||
137 | |||
138 | var model = store.createEmptyModel(); | ||
139 | var personInterpretation = model.getInterpretation(person); | ||
140 | var symbolInterpretation = model.getInterpretation(symbol); | ||
141 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
142 | var queryResultSet = queryEngine.getResultSet(query); | ||
143 | |||
144 | personInterpretation.put(Tuple.of(0), true); | ||
145 | personInterpretation.put(Tuple.of(1), true); | ||
146 | personInterpretation.put(Tuple.of(2), true); | ||
147 | |||
148 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
149 | symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); | ||
150 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
151 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
152 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
153 | |||
154 | queryEngine.flushChanges(); | ||
155 | assertNullableResults(Map.of( | ||
156 | Tuple.of(0), Optional.of(2), | ||
157 | Tuple.of(1), Optional.of(0), | ||
158 | Tuple.of(2), Optional.of(0), | ||
159 | Tuple.of(3), Optional.empty() | ||
160 | ), queryResultSet); | ||
161 | } | ||
162 | |||
163 | @QueryEngineTest | ||
164 | void subQueryCountTest(QueryEvaluationHint hint) { | ||
165 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3, p4) -> builder.clause( | ||
166 | personView.call(p1), | ||
167 | symbolView.call(p1, p2, p3, p4) | ||
168 | ) | ||
169 | .clause( | ||
170 | personView.call(p2), | ||
171 | symbolView.call(p1, p2, p3, p4) | ||
172 | )); | ||
173 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder.clause(p2 -> List.of( | ||
174 | personView.call(p1), | ||
175 | output.assign(subQuery.count(p1, p1, p2, p2)) | ||
176 | ))); | ||
177 | |||
178 | var store = ModelStore.builder() | ||
179 | .symbols(person, symbol) | ||
180 | .with(ViatraModelQueryAdapter.builder() | ||
181 | .defaultHint(hint) | ||
182 | .queries(query)) | ||
183 | .build(); | ||
184 | |||
185 | var model = store.createEmptyModel(); | ||
186 | var personInterpretation = model.getInterpretation(person); | ||
187 | var symbolInterpretation = model.getInterpretation(symbol); | ||
188 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
189 | var queryResultSet = queryEngine.getResultSet(query); | ||
190 | |||
191 | personInterpretation.put(Tuple.of(0), true); | ||
192 | personInterpretation.put(Tuple.of(1), true); | ||
193 | personInterpretation.put(Tuple.of(2), true); | ||
194 | |||
195 | symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); | ||
196 | symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); | ||
197 | symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); | ||
198 | symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); | ||
199 | symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); | ||
200 | |||
201 | queryEngine.flushChanges(); | ||
202 | assertNullableResults(Map.of( | ||
203 | Tuple.of(0), Optional.of(2), | ||
204 | Tuple.of(1), Optional.of(0), | ||
205 | Tuple.of(2), Optional.of(0), | ||
206 | Tuple.of(3), Optional.empty() | ||
207 | ), queryResultSet); | ||
208 | } | ||
209 | |||
210 | @QueryEngineTest | ||
211 | void inputKeyAggregationTest(QueryEvaluationHint hint) { | ||
212 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder | ||
213 | .clause((p2) -> List.of( | ||
214 | personView.call(p1), | ||
215 | output.assign(intSymbolView.aggregate(INT_SUM, p1, p1, p2, p2)) | ||
216 | ))); | ||
217 | |||
218 | var store = ModelStore.builder() | ||
219 | .symbols(person, intSymbol) | ||
220 | .with(ViatraModelQueryAdapter.builder() | ||
221 | .defaultHint(hint) | ||
222 | .queries(query)) | ||
223 | .build(); | ||
224 | |||
225 | var model = store.createEmptyModel(); | ||
226 | var personInterpretation = model.getInterpretation(person); | ||
227 | var intSymbolInterpretation = model.getInterpretation(intSymbol); | ||
228 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
229 | var queryResultSet = queryEngine.getResultSet(query); | ||
230 | |||
231 | personInterpretation.put(Tuple.of(0), true); | ||
232 | personInterpretation.put(Tuple.of(1), true); | ||
233 | personInterpretation.put(Tuple.of(2), true); | ||
234 | |||
235 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); | ||
236 | intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); | ||
237 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); | ||
238 | intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); | ||
239 | intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); | ||
240 | |||
241 | queryEngine.flushChanges(); | ||
242 | assertNullableResults(Map.of( | ||
243 | Tuple.of(0), Optional.of(3), | ||
244 | Tuple.of(1), Optional.of(0), | ||
245 | Tuple.of(2), Optional.of(0), | ||
246 | Tuple.of(3), Optional.empty() | ||
247 | ), queryResultSet); | ||
248 | } | ||
249 | |||
250 | @QueryEngineTest | ||
251 | void subQueryAggregationTest(QueryEvaluationHint hint) { | ||
252 | var subQuery = Dnf.of("SubQuery", builder -> { | ||
253 | var p1 = builder.parameter("p1"); | ||
254 | var p2 = builder.parameter("p2"); | ||
255 | var p3 = builder.parameter("p3"); | ||
256 | var p4 = builder.parameter("p4"); | ||
257 | var x = builder.parameter("x", Integer.class); | ||
258 | var y = builder.parameter("y", Integer.class); | ||
259 | builder.clause( | ||
260 | personView.call(p1), | ||
261 | intSymbolView.call(p1, p2, p3, p4, x), | ||
262 | y.assign(x) | ||
263 | ); | ||
264 | builder.clause( | ||
265 | personView.call(p2), | ||
266 | intSymbolView.call(p1, p2, p3, p4, x), | ||
267 | y.assign(x) | ||
268 | ); | ||
269 | }); | ||
270 | var query = Query.of("Diagonal", Integer.class, (builder, p1, output) -> builder | ||
271 | .clause(Integer.class, Integer.class, (p2, y, z) -> List.of( | ||
272 | personView.call(p1), | ||
273 | output.assign(subQuery.aggregateBy(y, INT_SUM, p1, p1, p2, p2, y, z)) | ||
274 | ))); | ||
275 | |||
276 | var store = ModelStore.builder() | ||
277 | .symbols(person, intSymbol) | ||
278 | .with(ViatraModelQueryAdapter.builder() | ||
279 | .defaultHint(hint) | ||
280 | .queries(query)) | ||
281 | .build(); | ||
282 | |||
283 | var model = store.createEmptyModel(); | ||
284 | var personInterpretation = model.getInterpretation(person); | ||
285 | var intSymbolInterpretation = model.getInterpretation(intSymbol); | ||
286 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
287 | var queryResultSet = queryEngine.getResultSet(query); | ||
288 | |||
289 | personInterpretation.put(Tuple.of(0), true); | ||
290 | personInterpretation.put(Tuple.of(1), true); | ||
291 | personInterpretation.put(Tuple.of(2), true); | ||
292 | |||
293 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); | ||
294 | intSymbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); | ||
295 | intSymbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); | ||
296 | intSymbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); | ||
297 | intSymbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); | ||
298 | |||
299 | queryEngine.flushChanges(); | ||
300 | assertNullableResults(Map.of( | ||
301 | Tuple.of(0), Optional.of(3), | ||
302 | Tuple.of(1), Optional.of(0), | ||
303 | Tuple.of(2), Optional.of(0), | ||
304 | Tuple.of(3), Optional.empty() | ||
305 | ), queryResultSet); | ||
306 | } | ||
307 | |||
308 | @QueryEngineTest | ||
309 | void inputKeyTransitiveTest(QueryEvaluationHint hint) { | ||
310 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause( | ||
311 | personView.call(p1), | ||
312 | friendView.callTransitive(p1, p1) | ||
313 | )); | ||
314 | |||
315 | var store = ModelStore.builder() | ||
316 | .symbols(person, friend) | ||
317 | .with(ViatraModelQueryAdapter.builder() | ||
318 | .defaultHint(hint) | ||
319 | .queries(query)) | ||
320 | .build(); | ||
321 | |||
322 | var model = store.createEmptyModel(); | ||
323 | var personInterpretation = model.getInterpretation(person); | ||
324 | var friendInterpretation = model.getInterpretation(friend); | ||
325 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
326 | var queryResultSet = queryEngine.getResultSet(query); | ||
327 | |||
328 | personInterpretation.put(Tuple.of(0), true); | ||
329 | personInterpretation.put(Tuple.of(1), true); | ||
330 | personInterpretation.put(Tuple.of(2), true); | ||
331 | |||
332 | friendInterpretation.put(Tuple.of(0, 0), true); | ||
333 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
334 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
335 | |||
336 | queryEngine.flushChanges(); | ||
337 | assertResults(Map.of( | ||
338 | Tuple.of(0), true, | ||
339 | Tuple.of(1), false, | ||
340 | Tuple.of(2), false, | ||
341 | Tuple.of(3), false | ||
342 | ), queryResultSet); | ||
343 | } | ||
344 | |||
345 | @QueryEngineTest | ||
346 | void subQueryTransitiveTest(QueryEvaluationHint hint) { | ||
347 | var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder | ||
348 | .clause( | ||
349 | personView.call(p1), | ||
350 | friendView.call(p1, p2) | ||
351 | ) | ||
352 | .clause( | ||
353 | personView.call(p2), | ||
354 | friendView.call(p1, p2) | ||
355 | )); | ||
356 | var query = Query.of("Diagonal", (builder, p1) -> builder.clause( | ||
357 | personView.call(p1), | ||
358 | subQuery.callTransitive(p1, p1) | ||
359 | )); | ||
360 | |||
361 | var store = ModelStore.builder() | ||
362 | .symbols(person, friend) | ||
363 | .with(ViatraModelQueryAdapter.builder() | ||
364 | .defaultHint(hint) | ||
365 | .queries(query)) | ||
366 | .build(); | ||
367 | |||
368 | var model = store.createEmptyModel(); | ||
369 | var personInterpretation = model.getInterpretation(person); | ||
370 | var friendInterpretation = model.getInterpretation(friend); | ||
371 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
372 | var queryResultSet = queryEngine.getResultSet(query); | ||
373 | |||
374 | personInterpretation.put(Tuple.of(0), true); | ||
375 | personInterpretation.put(Tuple.of(1), true); | ||
376 | personInterpretation.put(Tuple.of(2), true); | ||
377 | |||
378 | friendInterpretation.put(Tuple.of(0, 0), true); | ||
379 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
380 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
381 | |||
382 | queryEngine.flushChanges(); | ||
383 | assertResults(Map.of( | ||
384 | Tuple.of(0), true, | ||
385 | Tuple.of(1), false, | ||
386 | Tuple.of(2), false, | ||
387 | Tuple.of(3), false | ||
388 | ), queryResultSet); | ||
389 | } | ||
390 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java new file mode 100644 index 00000000..258127e7 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java | |||
@@ -0,0 +1,483 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.ModelQueryAdapter; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.viatra.tests.QueryEngineTest; | ||
15 | import tools.refinery.store.query.view.AnySymbolView; | ||
16 | import tools.refinery.store.query.view.FilteredView; | ||
17 | import tools.refinery.store.query.view.FunctionView; | ||
18 | import tools.refinery.store.query.view.KeyOnlyView; | ||
19 | import tools.refinery.store.representation.Symbol; | ||
20 | import tools.refinery.store.representation.TruthValue; | ||
21 | import tools.refinery.store.tuple.Tuple; | ||
22 | |||
23 | import java.util.List; | ||
24 | import java.util.Map; | ||
25 | import java.util.Optional; | ||
26 | |||
27 | import static org.hamcrest.MatcherAssert.assertThat; | ||
28 | import static org.hamcrest.Matchers.is; | ||
29 | import static org.hamcrest.Matchers.nullValue; | ||
30 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
31 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
32 | import static tools.refinery.store.query.literal.Literals.assume; | ||
33 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
34 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; | ||
35 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; | ||
36 | |||
37 | class FunctionalQueryTest { | ||
38 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
39 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
40 | private static final Symbol<TruthValue> friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); | ||
41 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
42 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
43 | private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must); | ||
44 | |||
45 | @QueryEngineTest | ||
46 | void inputKeyTest(QueryEvaluationHint hint) { | ||
47 | var query = Query.of("InputKey", Integer.class, (builder, p1, output) -> builder.clause( | ||
48 | personView.call(p1), | ||
49 | ageView.call(p1, output) | ||
50 | )); | ||
51 | |||
52 | var store = ModelStore.builder() | ||
53 | .symbols(person, age) | ||
54 | .with(ViatraModelQueryAdapter.builder() | ||
55 | .defaultHint(hint) | ||
56 | .queries(query)) | ||
57 | .build(); | ||
58 | |||
59 | var model = store.createEmptyModel(); | ||
60 | var personInterpretation = model.getInterpretation(person); | ||
61 | var ageInterpretation = model.getInterpretation(age); | ||
62 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
63 | var queryResultSet = queryEngine.getResultSet(query); | ||
64 | |||
65 | personInterpretation.put(Tuple.of(0), true); | ||
66 | personInterpretation.put(Tuple.of(1), true); | ||
67 | |||
68 | ageInterpretation.put(Tuple.of(0), 12); | ||
69 | ageInterpretation.put(Tuple.of(1), 24); | ||
70 | ageInterpretation.put(Tuple.of(2), 36); | ||
71 | |||
72 | queryEngine.flushChanges(); | ||
73 | assertNullableResults(Map.of( | ||
74 | Tuple.of(0), Optional.of(12), | ||
75 | Tuple.of(1), Optional.of(24), | ||
76 | Tuple.of(2), Optional.empty() | ||
77 | ), queryResultSet); | ||
78 | } | ||
79 | |||
80 | @QueryEngineTest | ||
81 | void predicateTest(QueryEvaluationHint hint) { | ||
82 | var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( | ||
83 | personView.call(p1), | ||
84 | ageView.call(p1, x) | ||
85 | )); | ||
86 | var query = Query.of("Predicate", Integer.class, (builder, p1, output) -> builder.clause( | ||
87 | personView.call(p1), | ||
88 | output.assign(subQuery.call(p1)) | ||
89 | )); | ||
90 | |||
91 | var store = ModelStore.builder() | ||
92 | .symbols(person, age) | ||
93 | .with(ViatraModelQueryAdapter.builder() | ||
94 | .defaultHint(hint) | ||
95 | .queries(query)) | ||
96 | .build(); | ||
97 | |||
98 | var model = store.createEmptyModel(); | ||
99 | var personInterpretation = model.getInterpretation(person); | ||
100 | var ageInterpretation = model.getInterpretation(age); | ||
101 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
102 | var queryResultSet = queryEngine.getResultSet(query); | ||
103 | |||
104 | personInterpretation.put(Tuple.of(0), true); | ||
105 | personInterpretation.put(Tuple.of(1), true); | ||
106 | |||
107 | ageInterpretation.put(Tuple.of(0), 12); | ||
108 | ageInterpretation.put(Tuple.of(1), 24); | ||
109 | ageInterpretation.put(Tuple.of(2), 36); | ||
110 | |||
111 | queryEngine.flushChanges(); | ||
112 | assertNullableResults(Map.of( | ||
113 | Tuple.of(0), Optional.of(12), | ||
114 | Tuple.of(1), Optional.of(24), | ||
115 | Tuple.of(2), Optional.empty() | ||
116 | ), queryResultSet); | ||
117 | } | ||
118 | |||
119 | @QueryEngineTest | ||
120 | void computationTest(QueryEvaluationHint hint) { | ||
121 | var query = Query.of("Computation", Integer.class, (builder, p1, output) -> builder.clause(() -> { | ||
122 | var x = Variable.of("x", Integer.class); | ||
123 | return List.of( | ||
124 | personView.call(p1), | ||
125 | ageView.call(p1, x), | ||
126 | output.assign(mul(x, constant(7))) | ||
127 | ); | ||
128 | })); | ||
129 | |||
130 | var store = ModelStore.builder() | ||
131 | .symbols(person, age) | ||
132 | .with(ViatraModelQueryAdapter.builder() | ||
133 | .defaultHint(hint) | ||
134 | .queries(query)) | ||
135 | .build(); | ||
136 | |||
137 | var model = store.createEmptyModel(); | ||
138 | var personInterpretation = model.getInterpretation(person); | ||
139 | var ageInterpretation = model.getInterpretation(age); | ||
140 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
141 | var queryResultSet = queryEngine.getResultSet(query); | ||
142 | |||
143 | personInterpretation.put(Tuple.of(0), true); | ||
144 | personInterpretation.put(Tuple.of(1), true); | ||
145 | |||
146 | ageInterpretation.put(Tuple.of(0), 12); | ||
147 | ageInterpretation.put(Tuple.of(1), 24); | ||
148 | |||
149 | queryEngine.flushChanges(); | ||
150 | assertNullableResults(Map.of( | ||
151 | Tuple.of(0), Optional.of(84), | ||
152 | Tuple.of(1), Optional.of(168), | ||
153 | Tuple.of(2), Optional.empty() | ||
154 | ), queryResultSet); | ||
155 | } | ||
156 | |||
157 | @QueryEngineTest | ||
158 | void inputKeyCountTest(QueryEvaluationHint hint) { | ||
159 | var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause( | ||
160 | personView.call(p1), | ||
161 | output.assign(friendMustView.count(p1, Variable.of())) | ||
162 | )); | ||
163 | |||
164 | var store = ModelStore.builder() | ||
165 | .symbols(person, friend) | ||
166 | .with(ViatraModelQueryAdapter.builder() | ||
167 | .defaultHint(hint) | ||
168 | .queries(query)) | ||
169 | .build(); | ||
170 | |||
171 | var model = store.createEmptyModel(); | ||
172 | var personInterpretation = model.getInterpretation(person); | ||
173 | var friendInterpretation = model.getInterpretation(friend); | ||
174 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
175 | var queryResultSet = queryEngine.getResultSet(query); | ||
176 | |||
177 | personInterpretation.put(Tuple.of(0), true); | ||
178 | personInterpretation.put(Tuple.of(1), true); | ||
179 | personInterpretation.put(Tuple.of(2), true); | ||
180 | |||
181 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
182 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
183 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
184 | |||
185 | queryEngine.flushChanges(); | ||
186 | assertNullableResults(Map.of( | ||
187 | Tuple.of(0), Optional.of(1), | ||
188 | Tuple.of(1), Optional.of(2), | ||
189 | Tuple.of(2), Optional.of(0), | ||
190 | Tuple.of(3), Optional.empty() | ||
191 | ), queryResultSet); | ||
192 | } | ||
193 | |||
194 | @QueryEngineTest | ||
195 | void predicateCountTest(QueryEvaluationHint hint) { | ||
196 | var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder.clause( | ||
197 | personView.call(p1), | ||
198 | personView.call(p2), | ||
199 | friendMustView.call(p1, p2) | ||
200 | )); | ||
201 | var query = Query.of("Count", Integer.class, (builder, p1, output) -> builder.clause( | ||
202 | personView.call(p1), | ||
203 | output.assign(subQuery.count(p1, Variable.of())) | ||
204 | )); | ||
205 | |||
206 | var store = ModelStore.builder() | ||
207 | .symbols(person, friend) | ||
208 | .with(ViatraModelQueryAdapter.builder() | ||
209 | .defaultHint(hint) | ||
210 | .queries(query)) | ||
211 | .build(); | ||
212 | |||
213 | var model = store.createEmptyModel(); | ||
214 | var personInterpretation = model.getInterpretation(person); | ||
215 | var friendInterpretation = model.getInterpretation(friend); | ||
216 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
217 | var queryResultSet = queryEngine.getResultSet(query); | ||
218 | |||
219 | personInterpretation.put(Tuple.of(0), true); | ||
220 | personInterpretation.put(Tuple.of(1), true); | ||
221 | personInterpretation.put(Tuple.of(2), true); | ||
222 | |||
223 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
224 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
225 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
226 | |||
227 | queryEngine.flushChanges(); | ||
228 | assertNullableResults(Map.of( | ||
229 | Tuple.of(0), Optional.of(1), | ||
230 | Tuple.of(1), Optional.of(2), | ||
231 | Tuple.of(2), Optional.of(0), | ||
232 | Tuple.of(3), Optional.empty() | ||
233 | ), queryResultSet); | ||
234 | } | ||
235 | |||
236 | @QueryEngineTest | ||
237 | void inputKeyAggregationTest(QueryEvaluationHint hint) { | ||
238 | var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause( | ||
239 | output.assign(ageView.aggregate(INT_SUM, Variable.of())) | ||
240 | )); | ||
241 | |||
242 | var store = ModelStore.builder() | ||
243 | .symbols(age) | ||
244 | .with(ViatraModelQueryAdapter.builder() | ||
245 | .defaultHint(hint) | ||
246 | .queries(query)) | ||
247 | .build(); | ||
248 | |||
249 | var model = store.createEmptyModel(); | ||
250 | var ageInterpretation = model.getInterpretation(age); | ||
251 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
252 | var queryResultSet = queryEngine.getResultSet(query); | ||
253 | |||
254 | ageInterpretation.put(Tuple.of(0), 12); | ||
255 | ageInterpretation.put(Tuple.of(1), 24); | ||
256 | |||
257 | queryEngine.flushChanges(); | ||
258 | assertResults(Map.of(Tuple.of(), 36), queryResultSet); | ||
259 | } | ||
260 | |||
261 | @QueryEngineTest | ||
262 | void predicateAggregationTest(QueryEvaluationHint hint) { | ||
263 | var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( | ||
264 | personView.call(p1), | ||
265 | ageView.call(p1, x) | ||
266 | )); | ||
267 | var query = Query.of("Aggregate", Integer.class, (builder, output) -> builder.clause( | ||
268 | output.assign(subQuery.aggregate(INT_SUM, Variable.of())) | ||
269 | )); | ||
270 | |||
271 | var store = ModelStore.builder() | ||
272 | .symbols(person, age) | ||
273 | .with(ViatraModelQueryAdapter.builder() | ||
274 | .defaultHint(hint) | ||
275 | .queries(query)) | ||
276 | .build(); | ||
277 | |||
278 | var model = store.createEmptyModel(); | ||
279 | var personInterpretation = model.getInterpretation(person); | ||
280 | var ageInterpretation = model.getInterpretation(age); | ||
281 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
282 | var queryResultSet = queryEngine.getResultSet(query); | ||
283 | |||
284 | personInterpretation.put(Tuple.of(0), true); | ||
285 | personInterpretation.put(Tuple.of(1), true); | ||
286 | |||
287 | ageInterpretation.put(Tuple.of(0), 12); | ||
288 | ageInterpretation.put(Tuple.of(1), 24); | ||
289 | |||
290 | queryEngine.flushChanges(); | ||
291 | assertResults(Map.of(Tuple.of(), 36), queryResultSet); | ||
292 | } | ||
293 | |||
294 | @QueryEngineTest | ||
295 | void extremeValueTest(QueryEvaluationHint hint) { | ||
296 | var subQuery = Query.of("SubQuery", Integer.class, (builder, p1, x) -> builder.clause( | ||
297 | personView.call(p1), | ||
298 | x.assign(friendMustView.count(p1, Variable.of())) | ||
299 | )); | ||
300 | var minQuery = Query.of("Min", Integer.class, (builder, output) -> builder.clause( | ||
301 | output.assign(subQuery.aggregate(INT_MIN, Variable.of())) | ||
302 | )); | ||
303 | var maxQuery = Query.of("Max", Integer.class, (builder, output) -> builder.clause( | ||
304 | output.assign(subQuery.aggregate(INT_MAX, Variable.of())) | ||
305 | )); | ||
306 | |||
307 | var store = ModelStore.builder() | ||
308 | .symbols(person, friend) | ||
309 | .with(ViatraModelQueryAdapter.builder() | ||
310 | .defaultHint(hint) | ||
311 | .queries(minQuery, maxQuery)) | ||
312 | .build(); | ||
313 | |||
314 | var model = store.createEmptyModel(); | ||
315 | var personInterpretation = model.getInterpretation(person); | ||
316 | var friendInterpretation = model.getInterpretation(friend); | ||
317 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
318 | var minResultSet = queryEngine.getResultSet(minQuery); | ||
319 | var maxResultSet = queryEngine.getResultSet(maxQuery); | ||
320 | |||
321 | assertResults(Map.of(Tuple.of(), Integer.MAX_VALUE), minResultSet); | ||
322 | assertResults(Map.of(Tuple.of(), Integer.MIN_VALUE), maxResultSet); | ||
323 | |||
324 | personInterpretation.put(Tuple.of(0), true); | ||
325 | personInterpretation.put(Tuple.of(1), true); | ||
326 | personInterpretation.put(Tuple.of(2), true); | ||
327 | |||
328 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
329 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
330 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
331 | |||
332 | queryEngine.flushChanges(); | ||
333 | assertResults(Map.of(Tuple.of(), 0), minResultSet); | ||
334 | assertResults(Map.of(Tuple.of(), 2), maxResultSet); | ||
335 | |||
336 | friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); | ||
337 | friendInterpretation.put(Tuple.of(2, 1), TruthValue.TRUE); | ||
338 | |||
339 | queryEngine.flushChanges(); | ||
340 | assertResults(Map.of(Tuple.of(), 1), minResultSet); | ||
341 | assertResults(Map.of(Tuple.of(), 2), maxResultSet); | ||
342 | |||
343 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.FALSE); | ||
344 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.FALSE); | ||
345 | friendInterpretation.put(Tuple.of(2, 0), TruthValue.FALSE); | ||
346 | |||
347 | queryEngine.flushChanges(); | ||
348 | assertResults(Map.of(Tuple.of(), 0), minResultSet); | ||
349 | assertResults(Map.of(Tuple.of(), 1), maxResultSet); | ||
350 | } | ||
351 | |||
352 | @QueryEngineTest | ||
353 | void invalidComputationTest(QueryEvaluationHint hint) { | ||
354 | var query = Query.of("InvalidComputation", Integer.class, | ||
355 | (builder, p1, output) -> builder.clause(Integer.class, (x) -> List.of( | ||
356 | personView.call(p1), | ||
357 | ageView.call(p1, x), | ||
358 | output.assign(div(constant(120), x)) | ||
359 | ))); | ||
360 | |||
361 | var store = ModelStore.builder() | ||
362 | .symbols(person, age) | ||
363 | .with(ViatraModelQueryAdapter.builder() | ||
364 | .defaultHint(hint) | ||
365 | .queries(query)) | ||
366 | .build(); | ||
367 | |||
368 | var model = store.createEmptyModel(); | ||
369 | var personInterpretation = model.getInterpretation(person); | ||
370 | var ageInterpretation = model.getInterpretation(age); | ||
371 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
372 | var queryResultSet = queryEngine.getResultSet(query); | ||
373 | |||
374 | personInterpretation.put(Tuple.of(0), true); | ||
375 | personInterpretation.put(Tuple.of(1), true); | ||
376 | |||
377 | ageInterpretation.put(Tuple.of(0), 0); | ||
378 | ageInterpretation.put(Tuple.of(1), 30); | ||
379 | |||
380 | queryEngine.flushChanges(); | ||
381 | assertNullableResults(Map.of( | ||
382 | Tuple.of(0), Optional.empty(), | ||
383 | Tuple.of(1), Optional.of(4), | ||
384 | Tuple.of(2), Optional.empty() | ||
385 | ), queryResultSet); | ||
386 | } | ||
387 | |||
388 | @QueryEngineTest | ||
389 | void invalidAssumeTest(QueryEvaluationHint hint) { | ||
390 | var query = Query.of("InvalidAssume", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( | ||
391 | personView.call(p1), | ||
392 | ageView.call(p1, x), | ||
393 | assume(lessEq(div(constant(120), x), constant(5))) | ||
394 | ))); | ||
395 | |||
396 | var store = ModelStore.builder() | ||
397 | .symbols(person, age) | ||
398 | .with(ViatraModelQueryAdapter.builder() | ||
399 | .defaultHint(hint) | ||
400 | .queries(query)) | ||
401 | .build(); | ||
402 | |||
403 | var model = store.createEmptyModel(); | ||
404 | var personInterpretation = model.getInterpretation(person); | ||
405 | var ageInterpretation = model.getInterpretation(age); | ||
406 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
407 | var queryResultSet = queryEngine.getResultSet(query); | ||
408 | |||
409 | personInterpretation.put(Tuple.of(0), true); | ||
410 | personInterpretation.put(Tuple.of(1), true); | ||
411 | personInterpretation.put(Tuple.of(2), true); | ||
412 | |||
413 | ageInterpretation.put(Tuple.of(0), 0); | ||
414 | ageInterpretation.put(Tuple.of(1), 30); | ||
415 | ageInterpretation.put(Tuple.of(2), 20); | ||
416 | |||
417 | queryEngine.flushChanges(); | ||
418 | assertResults(Map.of( | ||
419 | Tuple.of(0), false, | ||
420 | Tuple.of(1), true, | ||
421 | Tuple.of(2), false, | ||
422 | Tuple.of(3), false | ||
423 | ), queryResultSet); | ||
424 | } | ||
425 | |||
426 | @QueryEngineTest | ||
427 | void notFunctionalTest(QueryEvaluationHint hint) { | ||
428 | var query = Query.of("NotFunctional", Integer.class, (builder, p1, output) -> builder.clause((p2) -> List.of( | ||
429 | personView.call(p1), | ||
430 | friendMustView.call(p1, p2), | ||
431 | ageView.call(p2, output) | ||
432 | ))); | ||
433 | |||
434 | var store = ModelStore.builder() | ||
435 | .symbols(person, age, friend) | ||
436 | .with(ViatraModelQueryAdapter.builder() | ||
437 | .defaultHint(hint) | ||
438 | .queries(query)) | ||
439 | .build(); | ||
440 | |||
441 | var model = store.createEmptyModel(); | ||
442 | var personInterpretation = model.getInterpretation(person); | ||
443 | var ageInterpretation = model.getInterpretation(age); | ||
444 | var friendInterpretation = model.getInterpretation(friend); | ||
445 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
446 | var queryResultSet = queryEngine.getResultSet(query); | ||
447 | |||
448 | personInterpretation.put(Tuple.of(0), true); | ||
449 | personInterpretation.put(Tuple.of(1), true); | ||
450 | personInterpretation.put(Tuple.of(2), true); | ||
451 | |||
452 | ageInterpretation.put(Tuple.of(0), 24); | ||
453 | ageInterpretation.put(Tuple.of(1), 30); | ||
454 | ageInterpretation.put(Tuple.of(2), 36); | ||
455 | |||
456 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
457 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
458 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | ||
459 | |||
460 | queryEngine.flushChanges(); | ||
461 | var invalidTuple = Tuple.of(1); | ||
462 | var cursor = queryResultSet.getAll(); | ||
463 | assertAll( | ||
464 | () -> assertThat("value for key 0", queryResultSet.get(Tuple.of(0)), is(30)), | ||
465 | () -> assertThrows(IllegalStateException.class, () -> queryResultSet.get(invalidTuple), | ||
466 | "multiple values for key 1"), | ||
467 | () -> assertThat("value for key 2", queryResultSet.get(Tuple.of(2)), is(nullValue())), | ||
468 | () -> assertThat("value for key 3", queryResultSet.get(Tuple.of(3)), is(nullValue())) | ||
469 | ); | ||
470 | if (hint.getQueryBackendRequirementType() != QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH) { | ||
471 | // Local search doesn't support throwing an error on multiple function return values. | ||
472 | assertThat("results size", queryResultSet.size(), is(2)); | ||
473 | assertThrows(IllegalStateException.class, () -> enumerateValues(cursor), "move cursor"); | ||
474 | } | ||
475 | } | ||
476 | |||
477 | private static void enumerateValues(Cursor<?, ?> cursor) { | ||
478 | //noinspection StatementWithEmptyBody | ||
479 | while (cursor.move()) { | ||
480 | // Nothing do, just let the cursor move through the result set. | ||
481 | } | ||
482 | } | ||
483 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java new file mode 100644 index 00000000..8ede6c80 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java | |||
@@ -0,0 +1,117 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.resultset.OrderedResultSet; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.query.view.KeyOnlyView; | ||
16 | import tools.refinery.store.representation.Symbol; | ||
17 | import tools.refinery.store.tuple.Tuple; | ||
18 | |||
19 | import static org.hamcrest.MatcherAssert.assertThat; | ||
20 | import static org.hamcrest.Matchers.is; | ||
21 | |||
22 | class OrderedResultSetTest { | ||
23 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
24 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
25 | |||
26 | @Test | ||
27 | void relationalFlushTest() { | ||
28 | var query = Query.of("Relation", (builder, p1, p2) -> builder.clause( | ||
29 | friendView.call(p1, p2) | ||
30 | )); | ||
31 | |||
32 | var store = ModelStore.builder() | ||
33 | .symbols(friend) | ||
34 | .with(ViatraModelQueryAdapter.builder() | ||
35 | .queries(query)) | ||
36 | .build(); | ||
37 | |||
38 | var model = store.createEmptyModel(); | ||
39 | var friendInterpretation = model.getInterpretation(friend); | ||
40 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
41 | var resultSet = queryEngine.getResultSet(query); | ||
42 | |||
43 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
44 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
45 | friendInterpretation.put(Tuple.of(1, 1), true); | ||
46 | queryEngine.flushChanges(); | ||
47 | |||
48 | try (var orderedResultSet = new OrderedResultSet<>(resultSet)) { | ||
49 | assertThat(orderedResultSet.size(), is(3)); | ||
50 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1))); | ||
51 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(1, 1))); | ||
52 | assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 2))); | ||
53 | |||
54 | friendInterpretation.put(Tuple.of(1, 2), false); | ||
55 | friendInterpretation.put(Tuple.of(0, 2), true); | ||
56 | queryEngine.flushChanges(); | ||
57 | |||
58 | assertThat(orderedResultSet.size(), is(3)); | ||
59 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(0, 1))); | ||
60 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(0, 2))); | ||
61 | assertThat(orderedResultSet.getKey(2), is(Tuple.of(1, 1))); | ||
62 | } | ||
63 | } | ||
64 | |||
65 | @Test | ||
66 | void functionalFlushTest() { | ||
67 | var query = Query.of("Function", Integer.class, (builder, p1, output) -> builder.clause( | ||
68 | friendView.call(p1, Variable.of()), | ||
69 | output.assign(friendView.count(p1, Variable.of())) | ||
70 | )); | ||
71 | |||
72 | var store = ModelStore.builder() | ||
73 | .symbols(friend) | ||
74 | .with(ViatraModelQueryAdapter.builder() | ||
75 | .queries(query)) | ||
76 | .build(); | ||
77 | |||
78 | var model = store.createEmptyModel(); | ||
79 | var friendInterpretation = model.getInterpretation(friend); | ||
80 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
81 | var resultSet = queryEngine.getResultSet(query); | ||
82 | |||
83 | friendInterpretation.put(Tuple.of(0, 1), true); | ||
84 | friendInterpretation.put(Tuple.of(1, 2), true); | ||
85 | friendInterpretation.put(Tuple.of(1, 1), true); | ||
86 | queryEngine.flushChanges(); | ||
87 | |||
88 | try (var orderedResultSet = new OrderedResultSet<>(resultSet)) { | ||
89 | assertThat(orderedResultSet.size(), is(2)); | ||
90 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(0))); | ||
91 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(1))); | ||
92 | |||
93 | friendInterpretation.put(Tuple.of(0, 1), false); | ||
94 | friendInterpretation.put(Tuple.of(2, 1), true); | ||
95 | queryEngine.flushChanges(); | ||
96 | |||
97 | assertThat(orderedResultSet.size(), is(2)); | ||
98 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); | ||
99 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); | ||
100 | |||
101 | friendInterpretation.put(Tuple.of(1, 1), false); | ||
102 | queryEngine.flushChanges(); | ||
103 | |||
104 | assertThat(orderedResultSet.size(), is(2)); | ||
105 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); | ||
106 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); | ||
107 | |||
108 | friendInterpretation.put(Tuple.of(1, 2), false); | ||
109 | friendInterpretation.put(Tuple.of(1, 0), true); | ||
110 | queryEngine.flushChanges(); | ||
111 | |||
112 | assertThat(orderedResultSet.size(), is(2)); | ||
113 | assertThat(orderedResultSet.getKey(0), is(Tuple.of(1))); | ||
114 | assertThat(orderedResultSet.getKey(1), is(Tuple.of(2))); | ||
115 | } | ||
116 | } | ||
117 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java index 6a37b54a..25bcb0dc 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java | |||
@@ -1,47 +1,57 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra; | 6 | package tools.refinery.store.query.viatra; |
2 | 7 | ||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
3 | import org.junit.jupiter.api.Test; | 9 | import org.junit.jupiter.api.Test; |
4 | import tools.refinery.store.model.ModelStore; | 10 | import tools.refinery.store.model.ModelStore; |
5 | import tools.refinery.store.query.DNF; | 11 | import tools.refinery.store.query.ModelQueryAdapter; |
6 | import tools.refinery.store.query.ModelQuery; | 12 | import tools.refinery.store.query.dnf.Query; |
7 | import tools.refinery.store.query.Variable; | 13 | import tools.refinery.store.query.term.Variable; |
8 | import tools.refinery.store.query.atom.*; | 14 | import tools.refinery.store.query.viatra.tests.QueryEngineTest; |
9 | import tools.refinery.store.query.view.FilteredRelationView; | 15 | import tools.refinery.store.query.view.AnySymbolView; |
10 | import tools.refinery.store.query.view.KeyOnlyRelationView; | 16 | import tools.refinery.store.query.view.FilteredView; |
17 | import tools.refinery.store.query.view.FunctionView; | ||
18 | import tools.refinery.store.query.view.KeyOnlyView; | ||
11 | import tools.refinery.store.representation.Symbol; | 19 | import tools.refinery.store.representation.Symbol; |
12 | import tools.refinery.store.representation.TruthValue; | 20 | import tools.refinery.store.representation.TruthValue; |
13 | import tools.refinery.store.tuple.Tuple; | 21 | import tools.refinery.store.tuple.Tuple; |
14 | import tools.refinery.store.tuple.TupleLike; | ||
15 | 22 | ||
16 | import java.util.HashSet; | 23 | import java.util.List; |
17 | import java.util.Set; | 24 | import java.util.Map; |
18 | import java.util.stream.Stream; | ||
19 | 25 | ||
20 | import static org.junit.jupiter.api.Assertions.assertEquals; | 26 | import static tools.refinery.store.query.literal.Literals.assume; |
27 | import static tools.refinery.store.query.literal.Literals.not; | ||
28 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
29 | import static tools.refinery.store.query.term.int_.IntTerms.greaterEq; | ||
30 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; | ||
21 | 31 | ||
22 | class QueryTest { | 32 | class QueryTest { |
23 | @Test | 33 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); |
24 | void typeConstraintTest() { | 34 | private static final Symbol<TruthValue> friend = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); |
25 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 35 | private static final AnySymbolView personView = new KeyOnlyView<>(person); |
26 | var asset = new Symbol<>("Asset", 1, Boolean.class, false); | 36 | private static final AnySymbolView friendMustView = new FilteredView<>(friend, "must", TruthValue::must); |
27 | var personView = new KeyOnlyRelationView<>(person); | 37 | |
28 | 38 | @QueryEngineTest | |
29 | var p1 = new Variable("p1"); | 39 | void typeConstraintTest(QueryEvaluationHint hint) { |
30 | var predicate = DNF.builder("TypeConstraint") | 40 | var asset = Symbol.of("Asset", 1); |
31 | .parameters(p1) | 41 | |
32 | .clause(new RelationViewAtom(personView, p1)) | 42 | var predicate = Query.of("TypeConstraint", (builder, p1) -> builder.clause(personView.call(p1))); |
33 | .build(); | ||
34 | 43 | ||
35 | var store = ModelStore.builder() | 44 | var store = ModelStore.builder() |
36 | .symbols(person, asset) | 45 | .symbols(person, asset) |
37 | .with(ViatraModelQuery.ADAPTER) | 46 | .with(ViatraModelQueryAdapter.builder() |
38 | .queries(predicate) | 47 | .defaultHint(hint) |
48 | .queries(predicate)) | ||
39 | .build(); | 49 | .build(); |
40 | 50 | ||
41 | var model = store.createEmptyModel(); | 51 | var model = store.createEmptyModel(); |
42 | var personInterpretation = model.getInterpretation(person); | 52 | var personInterpretation = model.getInterpretation(person); |
43 | var assetInterpretation = model.getInterpretation(asset); | 53 | var assetInterpretation = model.getInterpretation(asset); |
44 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 54 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
45 | var predicateResultSet = queryEngine.getResultSet(predicate); | 55 | var predicateResultSet = queryEngine.getResultSet(predicate); |
46 | 56 | ||
47 | personInterpretation.put(Tuple.of(0), true); | 57 | personInterpretation.put(Tuple.of(0), true); |
@@ -51,42 +61,34 @@ class QueryTest { | |||
51 | assetInterpretation.put(Tuple.of(2), true); | 61 | assetInterpretation.put(Tuple.of(2), true); |
52 | 62 | ||
53 | queryEngine.flushChanges(); | 63 | queryEngine.flushChanges(); |
54 | assertEquals(2, predicateResultSet.countResults()); | 64 | assertResults(Map.of( |
55 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); | 65 | Tuple.of(0), true, |
66 | Tuple.of(1), true, | ||
67 | Tuple.of(2), false | ||
68 | ), predicateResultSet); | ||
56 | } | 69 | } |
57 | 70 | ||
58 | @Test | 71 | @QueryEngineTest |
59 | void relationConstraintTest() { | 72 | void relationConstraintTest(QueryEvaluationHint hint) { |
60 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 73 | var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause( |
61 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 74 | personView.call(p1), |
62 | var personView = new KeyOnlyRelationView<>(person); | 75 | personView.call(p2), |
63 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 76 | friendMustView.call(p1, p2) |
64 | 77 | )); | |
65 | var p1 = new Variable("p1"); | ||
66 | var p2 = new Variable("p2"); | ||
67 | var predicate = DNF.builder("RelationConstraint") | ||
68 | .parameters(p1, p2) | ||
69 | .clause( | ||
70 | new RelationViewAtom(personView, p1), | ||
71 | new RelationViewAtom(personView, p2), | ||
72 | new RelationViewAtom(friendMustView, p1, p2) | ||
73 | ) | ||
74 | .build(); | ||
75 | 78 | ||
76 | var store = ModelStore.builder() | 79 | var store = ModelStore.builder() |
77 | .symbols(person, friend) | 80 | .symbols(person, friend) |
78 | .with(ViatraModelQuery.ADAPTER) | 81 | .with(ViatraModelQueryAdapter.builder() |
79 | .queries(predicate) | 82 | .defaultHint(hint) |
83 | .queries(predicate)) | ||
80 | .build(); | 84 | .build(); |
81 | 85 | ||
82 | var model = store.createEmptyModel(); | 86 | var model = store.createEmptyModel(); |
83 | var personInterpretation = model.getInterpretation(person); | 87 | var personInterpretation = model.getInterpretation(person); |
84 | var friendInterpretation = model.getInterpretation(friend); | 88 | var friendInterpretation = model.getInterpretation(friend); |
85 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 89 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
86 | var predicateResultSet = queryEngine.getResultSet(predicate); | 90 | var predicateResultSet = queryEngine.getResultSet(predicate); |
87 | 91 | ||
88 | assertEquals(0, predicateResultSet.countResults()); | ||
89 | |||
90 | personInterpretation.put(Tuple.of(0), true); | 92 | personInterpretation.put(Tuple.of(0), true); |
91 | personInterpretation.put(Tuple.of(1), true); | 93 | personInterpretation.put(Tuple.of(1), true); |
92 | personInterpretation.put(Tuple.of(2), true); | 94 | personInterpretation.put(Tuple.of(2), true); |
@@ -94,97 +96,36 @@ class QueryTest { | |||
94 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | 96 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); |
95 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | 97 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); |
96 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 98 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
97 | 99 | friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE); | |
98 | assertEquals(0, predicateResultSet.countResults()); | ||
99 | 100 | ||
100 | queryEngine.flushChanges(); | 101 | queryEngine.flushChanges(); |
101 | assertEquals(3, predicateResultSet.countResults()); | 102 | assertResults(Map.of( |
102 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); | 103 | Tuple.of(0, 1), true, |
104 | Tuple.of(1, 0), true, | ||
105 | Tuple.of(1, 2), true, | ||
106 | Tuple.of(2, 1), false | ||
107 | ), predicateResultSet); | ||
103 | } | 108 | } |
104 | 109 | ||
105 | @Test | 110 | @QueryEngineTest |
106 | void andTest() { | 111 | void existTest(QueryEvaluationHint hint) { |
107 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 112 | var predicate = Query.of("Exists", (builder, p1) -> builder.clause((p2) -> List.of( |
108 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 113 | personView.call(p1), |
109 | var personView = new KeyOnlyRelationView<>(person); | 114 | personView.call(p2), |
110 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 115 | friendMustView.call(p1, p2) |
111 | 116 | ))); | |
112 | var p1 = new Variable("p1"); | ||
113 | var p2 = new Variable("p2"); | ||
114 | var predicate = DNF.builder("RelationConstraint") | ||
115 | .parameters(p1, p2) | ||
116 | .clause( | ||
117 | new RelationViewAtom(personView, p1), | ||
118 | new RelationViewAtom(personView, p2), | ||
119 | new RelationViewAtom(friendMustView, p1, p2), | ||
120 | new RelationViewAtom(friendMustView, p2, p1) | ||
121 | ) | ||
122 | .build(); | ||
123 | 117 | ||
124 | var store = ModelStore.builder() | 118 | var store = ModelStore.builder() |
125 | .symbols(person, friend) | 119 | .symbols(person, friend) |
126 | .with(ViatraModelQuery.ADAPTER) | 120 | .with(ViatraModelQueryAdapter.builder() |
127 | .queries(predicate) | 121 | .defaultHint(hint) |
122 | .queries(predicate)) | ||
128 | .build(); | 123 | .build(); |
129 | 124 | ||
130 | var model = store.createEmptyModel(); | 125 | var model = store.createEmptyModel(); |
131 | var personInterpretation = model.getInterpretation(person); | 126 | var personInterpretation = model.getInterpretation(person); |
132 | var friendInterpretation = model.getInterpretation(friend); | 127 | var friendInterpretation = model.getInterpretation(friend); |
133 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 128 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
134 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
135 | |||
136 | assertEquals(0, predicateResultSet.countResults()); | ||
137 | |||
138 | personInterpretation.put(Tuple.of(0), true); | ||
139 | personInterpretation.put(Tuple.of(1), true); | ||
140 | personInterpretation.put(Tuple.of(2), true); | ||
141 | |||
142 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | ||
143 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | ||
144 | |||
145 | queryEngine.flushChanges(); | ||
146 | assertEquals(0, predicateResultSet.countResults()); | ||
147 | |||
148 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | ||
149 | queryEngine.flushChanges(); | ||
150 | assertEquals(2, predicateResultSet.countResults()); | ||
151 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0))); | ||
152 | |||
153 | friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); | ||
154 | queryEngine.flushChanges(); | ||
155 | assertEquals(4, predicateResultSet.countResults()); | ||
156 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2), | ||
157 | Tuple.of(2, 0))); | ||
158 | } | ||
159 | |||
160 | @Test | ||
161 | void existTest() { | ||
162 | var person = new Symbol<>("Person", 1, Boolean.class, false); | ||
163 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | ||
164 | var personView = new KeyOnlyRelationView<>(person); | ||
165 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | ||
166 | |||
167 | var p1 = new Variable("p1"); | ||
168 | var p2 = new Variable("p2"); | ||
169 | var predicate = DNF.builder("RelationConstraint") | ||
170 | .parameters(p1) | ||
171 | .clause( | ||
172 | new RelationViewAtom(personView, p1), | ||
173 | new RelationViewAtom(personView, p2), | ||
174 | new RelationViewAtom(friendMustView, p1, p2) | ||
175 | ) | ||
176 | .build(); | ||
177 | |||
178 | var store = ModelStore.builder() | ||
179 | .symbols(person, friend) | ||
180 | .with(ViatraModelQuery.ADAPTER) | ||
181 | .queries(predicate) | ||
182 | .build(); | ||
183 | |||
184 | var model = store.createEmptyModel(); | ||
185 | var personInterpretation = model.getInterpretation(person); | ||
186 | var friendInterpretation = model.getInterpretation(friend); | ||
187 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | ||
188 | var predicateResultSet = queryEngine.getResultSet(predicate); | 129 | var predicateResultSet = queryEngine.getResultSet(predicate); |
189 | 130 | ||
190 | personInterpretation.put(Tuple.of(0), true); | 131 | personInterpretation.put(Tuple.of(0), true); |
@@ -194,50 +135,44 @@ class QueryTest { | |||
194 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); | 135 | friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); |
195 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); | 136 | friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); |
196 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 137 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
197 | 138 | friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE); | |
198 | assertEquals(0, predicateResultSet.countResults()); | ||
199 | 139 | ||
200 | queryEngine.flushChanges(); | 140 | queryEngine.flushChanges(); |
201 | assertEquals(2, predicateResultSet.countResults()); | 141 | assertResults(Map.of( |
202 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); | 142 | Tuple.of(0), true, |
143 | Tuple.of(1), true, | ||
144 | Tuple.of(2), false, | ||
145 | Tuple.of(3), false | ||
146 | ), predicateResultSet); | ||
203 | } | 147 | } |
204 | 148 | ||
205 | @Test | 149 | @QueryEngineTest |
206 | void orTest() { | 150 | void orTest(QueryEvaluationHint hint) { |
207 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 151 | var animal = Symbol.of("Animal", 1); |
208 | var animal = new Symbol<>("Animal", 1, Boolean.class, false); | 152 | var animalView = new KeyOnlyView<>(animal); |
209 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 153 | |
210 | var personView = new KeyOnlyRelationView<>(person); | 154 | var predicate = Query.of("Or", (builder, p1, p2) -> builder.clause( |
211 | var animalView = new KeyOnlyRelationView<>(animal); | 155 | personView.call(p1), |
212 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 156 | personView.call(p2), |
213 | 157 | friendMustView.call(p1, p2) | |
214 | var p1 = new Variable("p1"); | 158 | ).clause( |
215 | var p2 = new Variable("p2"); | 159 | animalView.call(p1), |
216 | var predicate = DNF.builder("Or") | 160 | animalView.call(p2), |
217 | .parameters(p1, p2) | 161 | friendMustView.call(p1, p2) |
218 | .clause( | 162 | )); |
219 | new RelationViewAtom(personView, p1), | ||
220 | new RelationViewAtom(personView, p2), | ||
221 | new RelationViewAtom(friendMustView, p1, p2) | ||
222 | ) | ||
223 | .clause( | ||
224 | new RelationViewAtom(animalView, p1), | ||
225 | new RelationViewAtom(animalView, p2), | ||
226 | new RelationViewAtom(friendMustView, p1, p2) | ||
227 | ) | ||
228 | .build(); | ||
229 | 163 | ||
230 | var store = ModelStore.builder() | 164 | var store = ModelStore.builder() |
231 | .symbols(person, animal, friend) | 165 | .symbols(person, animal, friend) |
232 | .with(ViatraModelQuery.ADAPTER) | 166 | .with(ViatraModelQueryAdapter.builder() |
233 | .queries(predicate) | 167 | .defaultHint(hint) |
168 | .queries(predicate)) | ||
234 | .build(); | 169 | .build(); |
235 | 170 | ||
236 | var model = store.createEmptyModel(); | 171 | var model = store.createEmptyModel(); |
237 | var personInterpretation = model.getInterpretation(person); | 172 | var personInterpretation = model.getInterpretation(person); |
238 | var animalInterpretation = model.getInterpretation(animal); | 173 | var animalInterpretation = model.getInterpretation(animal); |
239 | var friendInterpretation = model.getInterpretation(friend); | 174 | var friendInterpretation = model.getInterpretation(friend); |
240 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 175 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
241 | var predicateResultSet = queryEngine.getResultSet(predicate); | 176 | var predicateResultSet = queryEngine.getResultSet(predicate); |
242 | 177 | ||
243 | personInterpretation.put(Tuple.of(0), true); | 178 | personInterpretation.put(Tuple.of(0), true); |
@@ -252,35 +187,33 @@ class QueryTest { | |||
252 | friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); | 187 | friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); |
253 | 188 | ||
254 | queryEngine.flushChanges(); | 189 | queryEngine.flushChanges(); |
255 | assertEquals(2, predicateResultSet.countResults()); | 190 | assertResults(Map.of( |
256 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); | 191 | Tuple.of(0, 1), true, |
192 | Tuple.of(0, 2), false, | ||
193 | Tuple.of(2, 3), true, | ||
194 | Tuple.of(3, 0), false, | ||
195 | Tuple.of(3, 2), false | ||
196 | ), predicateResultSet); | ||
257 | } | 197 | } |
258 | 198 | ||
259 | @Test | 199 | @QueryEngineTest |
260 | void equalityTest() { | 200 | void equalityTest(QueryEvaluationHint hint) { |
261 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 201 | var predicate = Query.of("Equality", (builder, p1, p2) -> builder.clause( |
262 | var personView = new KeyOnlyRelationView<>(person); | 202 | personView.call(p1), |
263 | 203 | personView.call(p2), | |
264 | var p1 = new Variable("p1"); | 204 | p1.isEquivalent(p2) |
265 | var p2 = new Variable("p2"); | 205 | )); |
266 | var predicate = DNF.builder("Equality") | ||
267 | .parameters(p1, p2) | ||
268 | .clause( | ||
269 | new RelationViewAtom(personView, p1), | ||
270 | new RelationViewAtom(personView, p2), | ||
271 | new EquivalenceAtom(p1, p2) | ||
272 | ) | ||
273 | .build(); | ||
274 | 206 | ||
275 | var store = ModelStore.builder() | 207 | var store = ModelStore.builder() |
276 | .symbols(person) | 208 | .symbols(person) |
277 | .with(ViatraModelQuery.ADAPTER) | 209 | .with(ViatraModelQueryAdapter.builder() |
278 | .queries(predicate) | 210 | .defaultHint(hint) |
211 | .queries(predicate)) | ||
279 | .build(); | 212 | .build(); |
280 | 213 | ||
281 | var model = store.createEmptyModel(); | 214 | var model = store.createEmptyModel(); |
282 | var personInterpretation = model.getInterpretation(person); | 215 | var personInterpretation = model.getInterpretation(person); |
283 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 216 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
284 | var predicateResultSet = queryEngine.getResultSet(predicate); | 217 | var predicateResultSet = queryEngine.getResultSet(predicate); |
285 | 218 | ||
286 | personInterpretation.put(Tuple.of(0), true); | 219 | personInterpretation.put(Tuple.of(0), true); |
@@ -288,41 +221,36 @@ class QueryTest { | |||
288 | personInterpretation.put(Tuple.of(2), true); | 221 | personInterpretation.put(Tuple.of(2), true); |
289 | 222 | ||
290 | queryEngine.flushChanges(); | 223 | queryEngine.flushChanges(); |
291 | assertEquals(3, predicateResultSet.countResults()); | 224 | assertResults(Map.of( |
292 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); | 225 | Tuple.of(0, 0), true, |
226 | Tuple.of(1, 1), true, | ||
227 | Tuple.of(2, 2), true, | ||
228 | Tuple.of(0, 1), false, | ||
229 | Tuple.of(3, 3), false | ||
230 | ), predicateResultSet); | ||
293 | } | 231 | } |
294 | 232 | ||
295 | @Test | 233 | @QueryEngineTest |
296 | void inequalityTest() { | 234 | void inequalityTest(QueryEvaluationHint hint) { |
297 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 235 | var predicate = Query.of("Inequality", (builder, p1, p2, p3) -> builder.clause( |
298 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 236 | personView.call(p1), |
299 | var personView = new KeyOnlyRelationView<>(person); | 237 | personView.call(p2), |
300 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 238 | friendMustView.call(p1, p3), |
301 | 239 | friendMustView.call(p2, p3), | |
302 | var p1 = new Variable("p1"); | 240 | p1.notEquivalent(p2) |
303 | var p2 = new Variable("p2"); | 241 | )); |
304 | var p3 = new Variable("p3"); | ||
305 | var predicate = DNF.builder("Inequality") | ||
306 | .parameters(p1, p2, p3) | ||
307 | .clause( | ||
308 | new RelationViewAtom(personView, p1), | ||
309 | new RelationViewAtom(personView, p2), | ||
310 | new RelationViewAtom(friendMustView, p1, p3), | ||
311 | new RelationViewAtom(friendMustView, p2, p3), | ||
312 | new EquivalenceAtom(false, p1, p2) | ||
313 | ) | ||
314 | .build(); | ||
315 | 242 | ||
316 | var store = ModelStore.builder() | 243 | var store = ModelStore.builder() |
317 | .symbols(person, friend) | 244 | .symbols(person, friend) |
318 | .with(ViatraModelQuery.ADAPTER) | 245 | .with(ViatraModelQueryAdapter.builder() |
319 | .queries(predicate) | 246 | .defaultHint(hint) |
247 | .queries(predicate)) | ||
320 | .build(); | 248 | .build(); |
321 | 249 | ||
322 | var model = store.createEmptyModel(); | 250 | var model = store.createEmptyModel(); |
323 | var personInterpretation = model.getInterpretation(person); | 251 | var personInterpretation = model.getInterpretation(person); |
324 | var friendInterpretation = model.getInterpretation(friend); | 252 | var friendInterpretation = model.getInterpretation(friend); |
325 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 253 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
326 | var predicateResultSet = queryEngine.getResultSet(predicate); | 254 | var predicateResultSet = queryEngine.getResultSet(predicate); |
327 | 255 | ||
328 | personInterpretation.put(Tuple.of(0), true); | 256 | personInterpretation.put(Tuple.of(0), true); |
@@ -333,49 +261,37 @@ class QueryTest { | |||
333 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 261 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
334 | 262 | ||
335 | queryEngine.flushChanges(); | 263 | queryEngine.flushChanges(); |
336 | assertEquals(2, predicateResultSet.countResults()); | 264 | assertResults(Map.of( |
337 | compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); | 265 | Tuple.of(0, 1, 2), true, |
266 | Tuple.of(1, 0, 2), true, | ||
267 | Tuple.of(0, 0, 2), false | ||
268 | ), predicateResultSet); | ||
338 | } | 269 | } |
339 | 270 | ||
340 | @Test | 271 | @QueryEngineTest |
341 | void patternCallTest() { | 272 | void patternCallTest(QueryEvaluationHint hint) { |
342 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 273 | var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause( |
343 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 274 | personView.call(p1), |
344 | var personView = new KeyOnlyRelationView<>(person); | 275 | personView.call(p2), |
345 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 276 | friendMustView.call(p1, p2) |
346 | 277 | )); | |
347 | var p1 = new Variable("p1"); | 278 | var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause( |
348 | var p2 = new Variable("p2"); | 279 | personView.call(p3), |
349 | var friendPredicate = DNF.builder("RelationConstraint") | 280 | personView.call(p4), |
350 | .parameters(p1, p2) | 281 | friendPredicate.call(p3, p4) |
351 | .clause( | 282 | )); |
352 | new RelationViewAtom(personView, p1), | ||
353 | new RelationViewAtom(personView, p2), | ||
354 | new RelationViewAtom(friendMustView, p1, p2) | ||
355 | ) | ||
356 | .build(); | ||
357 | |||
358 | var p3 = new Variable("p3"); | ||
359 | var p4 = new Variable("p4"); | ||
360 | var predicate = DNF.builder("PositivePatternCall") | ||
361 | .parameters(p3, p4) | ||
362 | .clause( | ||
363 | new RelationViewAtom(personView, p3), | ||
364 | new RelationViewAtom(personView, p4), | ||
365 | new DNFCallAtom(friendPredicate, p3, p4) | ||
366 | ) | ||
367 | .build(); | ||
368 | 283 | ||
369 | var store = ModelStore.builder() | 284 | var store = ModelStore.builder() |
370 | .symbols(person, friend) | 285 | .symbols(person, friend) |
371 | .with(ViatraModelQuery.ADAPTER) | 286 | .with(ViatraModelQueryAdapter.builder() |
372 | .queries(predicate) | 287 | .defaultHint(hint) |
288 | .queries(predicate)) | ||
373 | .build(); | 289 | .build(); |
374 | 290 | ||
375 | var model = store.createEmptyModel(); | 291 | var model = store.createEmptyModel(); |
376 | var personInterpretation = model.getInterpretation(person); | 292 | var personInterpretation = model.getInterpretation(person); |
377 | var friendInterpretation = model.getInterpretation(friend); | 293 | var friendInterpretation = model.getInterpretation(friend); |
378 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 294 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
379 | var predicateResultSet = queryEngine.getResultSet(predicate); | 295 | var predicateResultSet = queryEngine.getResultSet(predicate); |
380 | 296 | ||
381 | personInterpretation.put(Tuple.of(0), true); | 297 | personInterpretation.put(Tuple.of(0), true); |
@@ -387,37 +303,33 @@ class QueryTest { | |||
387 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 303 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
388 | 304 | ||
389 | queryEngine.flushChanges(); | 305 | queryEngine.flushChanges(); |
390 | assertEquals(3, predicateResultSet.countResults()); | 306 | assertResults(Map.of( |
307 | Tuple.of(0, 1), true, | ||
308 | Tuple.of(1, 0), true, | ||
309 | Tuple.of(1, 2), true, | ||
310 | Tuple.of(2, 1), false | ||
311 | ), predicateResultSet); | ||
391 | } | 312 | } |
392 | 313 | ||
393 | @Test | 314 | @QueryEngineTest |
394 | void negativeRelationViewTest() { | 315 | void negativeRelationViewTest(QueryEvaluationHint hint) { |
395 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 316 | var predicate = Query.of("NegativePatternCall", (builder, p1, p2) -> builder.clause( |
396 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 317 | personView.call(p1), |
397 | var personView = new KeyOnlyRelationView<>(person); | 318 | personView.call(p2), |
398 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 319 | not(friendMustView.call(p1, p2)) |
399 | 320 | )); | |
400 | var p1 = new Variable("p1"); | ||
401 | var p2 = new Variable("p2"); | ||
402 | var predicate = DNF.builder("NegativePatternCall") | ||
403 | .parameters(p1, p2) | ||
404 | .clause( | ||
405 | new RelationViewAtom(personView, p1), | ||
406 | new RelationViewAtom(personView, p2), | ||
407 | new RelationViewAtom(false, friendMustView, p1, p2) | ||
408 | ) | ||
409 | .build(); | ||
410 | 321 | ||
411 | var store = ModelStore.builder() | 322 | var store = ModelStore.builder() |
412 | .symbols(person, friend) | 323 | .symbols(person, friend) |
413 | .with(ViatraModelQuery.ADAPTER) | 324 | .with(ViatraModelQueryAdapter.builder() |
414 | .queries(predicate) | 325 | .defaultHint(hint) |
326 | .queries(predicate)) | ||
415 | .build(); | 327 | .build(); |
416 | 328 | ||
417 | var model = store.createEmptyModel(); | 329 | var model = store.createEmptyModel(); |
418 | var personInterpretation = model.getInterpretation(person); | 330 | var personInterpretation = model.getInterpretation(person); |
419 | var friendInterpretation = model.getInterpretation(friend); | 331 | var friendInterpretation = model.getInterpretation(friend); |
420 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 332 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
421 | var predicateResultSet = queryEngine.getResultSet(predicate); | 333 | var predicateResultSet = queryEngine.getResultSet(predicate); |
422 | 334 | ||
423 | personInterpretation.put(Tuple.of(0), true); | 335 | personInterpretation.put(Tuple.of(0), true); |
@@ -429,48 +341,44 @@ class QueryTest { | |||
429 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 341 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
430 | 342 | ||
431 | queryEngine.flushChanges(); | 343 | queryEngine.flushChanges(); |
432 | assertEquals(6, predicateResultSet.countResults()); | 344 | assertResults(Map.of( |
345 | Tuple.of(0, 0), true, | ||
346 | Tuple.of(0, 2), true, | ||
347 | Tuple.of(1, 1), true, | ||
348 | Tuple.of(2, 0), true, | ||
349 | Tuple.of(2, 1), true, | ||
350 | Tuple.of(2, 2), true, | ||
351 | Tuple.of(0, 1), false, | ||
352 | Tuple.of(1, 0), false, | ||
353 | Tuple.of(1, 2), false, | ||
354 | Tuple.of(0, 3), false | ||
355 | ), predicateResultSet); | ||
433 | } | 356 | } |
434 | 357 | ||
435 | @Test | 358 | @QueryEngineTest |
436 | void negativePatternCallTest() { | 359 | void negativePatternCallTest(QueryEvaluationHint hint) { |
437 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 360 | var friendPredicate = Query.of("Friend", (builder, p1, p2) -> builder.clause( |
438 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 361 | personView.call(p1), |
439 | var personView = new KeyOnlyRelationView<>(person); | 362 | personView.call(p2), |
440 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 363 | friendMustView.call(p1, p2) |
441 | 364 | )); | |
442 | var p1 = new Variable("p1"); | 365 | var predicate = Query.of("NegativePatternCall", (builder, p3, p4) -> builder.clause( |
443 | var p2 = new Variable("p2"); | 366 | personView.call(p3), |
444 | var friendPredicate = DNF.builder("RelationConstraint") | 367 | personView.call(p4), |
445 | .parameters(p1, p2) | 368 | not(friendPredicate.call(p3, p4)) |
446 | .clause( | 369 | )); |
447 | new RelationViewAtom(personView, p1), | ||
448 | new RelationViewAtom(personView, p2), | ||
449 | new RelationViewAtom(friendMustView, p1, p2) | ||
450 | ) | ||
451 | .build(); | ||
452 | |||
453 | var p3 = new Variable("p3"); | ||
454 | var p4 = new Variable("p4"); | ||
455 | var predicate = DNF.builder("NegativePatternCall") | ||
456 | .parameters(p3, p4) | ||
457 | .clause( | ||
458 | new RelationViewAtom(personView, p3), | ||
459 | new RelationViewAtom(personView, p4), | ||
460 | new DNFCallAtom(false, friendPredicate, p3, p4) | ||
461 | ) | ||
462 | .build(); | ||
463 | 370 | ||
464 | var store = ModelStore.builder() | 371 | var store = ModelStore.builder() |
465 | .symbols(person, friend) | 372 | .symbols(person, friend) |
466 | .with(ViatraModelQuery.ADAPTER) | 373 | .with(ViatraModelQueryAdapter.builder() |
467 | .queries(predicate) | 374 | .defaultHint(hint) |
375 | .queries(predicate)) | ||
468 | .build(); | 376 | .build(); |
469 | 377 | ||
470 | var model = store.createEmptyModel(); | 378 | var model = store.createEmptyModel(); |
471 | var personInterpretation = model.getInterpretation(person); | 379 | var personInterpretation = model.getInterpretation(person); |
472 | var friendInterpretation = model.getInterpretation(friend); | 380 | var friendInterpretation = model.getInterpretation(friend); |
473 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 381 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
474 | var predicateResultSet = queryEngine.getResultSet(predicate); | 382 | var predicateResultSet = queryEngine.getResultSet(predicate); |
475 | 383 | ||
476 | personInterpretation.put(Tuple.of(0), true); | 384 | personInterpretation.put(Tuple.of(0), true); |
@@ -482,37 +390,38 @@ class QueryTest { | |||
482 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 390 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
483 | 391 | ||
484 | queryEngine.flushChanges(); | 392 | queryEngine.flushChanges(); |
485 | assertEquals(6, predicateResultSet.countResults()); | 393 | assertResults(Map.of( |
394 | Tuple.of(0, 0), true, | ||
395 | Tuple.of(0, 2), true, | ||
396 | Tuple.of(1, 1), true, | ||
397 | Tuple.of(2, 0), true, | ||
398 | Tuple.of(2, 1), true, | ||
399 | Tuple.of(2, 2), true, | ||
400 | Tuple.of(0, 1), false, | ||
401 | Tuple.of(1, 0), false, | ||
402 | Tuple.of(1, 2), false, | ||
403 | Tuple.of(0, 3), false | ||
404 | ), predicateResultSet); | ||
486 | } | 405 | } |
487 | 406 | ||
488 | @Test | 407 | @QueryEngineTest |
489 | void negativeRelationViewWithQuantificationTest() { | 408 | void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) { |
490 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 409 | var predicate = Query.of("Negative", (builder, p1) -> builder.clause( |
491 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 410 | personView.call(p1), |
492 | var personView = new KeyOnlyRelationView<>(person); | 411 | not(friendMustView.call(p1, Variable.of())) |
493 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 412 | )); |
494 | |||
495 | var p1 = new Variable("p1"); | ||
496 | var p2 = new Variable("p2"); | ||
497 | |||
498 | var predicate = DNF.builder("Count") | ||
499 | .parameters(p1) | ||
500 | .clause( | ||
501 | new RelationViewAtom(personView, p1), | ||
502 | new RelationViewAtom(false, friendMustView, p1, p2) | ||
503 | ) | ||
504 | .build(); | ||
505 | 413 | ||
506 | var store = ModelStore.builder() | 414 | var store = ModelStore.builder() |
507 | .symbols(person, friend) | 415 | .symbols(person, friend) |
508 | .with(ViatraModelQuery.ADAPTER) | 416 | .with(ViatraModelQueryAdapter.builder() |
509 | .queries(predicate) | 417 | .defaultHint(hint) |
418 | .queries(predicate)) | ||
510 | .build(); | 419 | .build(); |
511 | 420 | ||
512 | var model = store.createEmptyModel(); | 421 | var model = store.createEmptyModel(); |
513 | var personInterpretation = model.getInterpretation(person); | 422 | var personInterpretation = model.getInterpretation(person); |
514 | var friendInterpretation = model.getInterpretation(friend); | 423 | var friendInterpretation = model.getInterpretation(friend); |
515 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 424 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
516 | var predicateResultSet = queryEngine.getResultSet(predicate); | 425 | var predicateResultSet = queryEngine.getResultSet(predicate); |
517 | 426 | ||
518 | personInterpretation.put(Tuple.of(0), true); | 427 | personInterpretation.put(Tuple.of(0), true); |
@@ -523,46 +432,37 @@ class QueryTest { | |||
523 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | 432 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); |
524 | 433 | ||
525 | queryEngine.flushChanges(); | 434 | queryEngine.flushChanges(); |
526 | assertEquals(2, predicateResultSet.countResults()); | 435 | assertResults(Map.of( |
436 | Tuple.of(0), false, | ||
437 | Tuple.of(1), true, | ||
438 | Tuple.of(2), true, | ||
439 | Tuple.of(3), false | ||
440 | ), predicateResultSet); | ||
527 | } | 441 | } |
528 | 442 | ||
529 | @Test | 443 | @QueryEngineTest |
530 | void negativeWithQuantificationTest() { | 444 | void negativeWithQuantificationTest(QueryEvaluationHint hint) { |
531 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 445 | var called = Query.of("Called", (builder, p1, p2) -> builder.clause( |
532 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 446 | personView.call(p1), |
533 | var personView = new KeyOnlyRelationView<>(person); | 447 | personView.call(p2), |
534 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 448 | friendMustView.call(p1, p2) |
535 | 449 | )); | |
536 | var p1 = new Variable("p1"); | 450 | var predicate = Query.of("Negative", (builder, p1) -> builder.clause( |
537 | var p2 = new Variable("p2"); | 451 | personView.call(p1), |
538 | 452 | not(called.call(p1, Variable.of())) | |
539 | var called = DNF.builder("Called") | 453 | )); |
540 | .parameters(p1, p2) | ||
541 | .clause( | ||
542 | new RelationViewAtom(personView, p1), | ||
543 | new RelationViewAtom(personView, p2), | ||
544 | new RelationViewAtom(friendMustView, p1, p2) | ||
545 | ) | ||
546 | .build(); | ||
547 | |||
548 | var predicate = DNF.builder("Count") | ||
549 | .parameters(p1) | ||
550 | .clause( | ||
551 | new RelationViewAtom(personView, p1), | ||
552 | new DNFCallAtom(false, called, p1, p2) | ||
553 | ) | ||
554 | .build(); | ||
555 | 454 | ||
556 | var store = ModelStore.builder() | 455 | var store = ModelStore.builder() |
557 | .symbols(person, friend) | 456 | .symbols(person, friend) |
558 | .with(ViatraModelQuery.ADAPTER) | 457 | .with(ViatraModelQueryAdapter.builder() |
559 | .queries(predicate) | 458 | .defaultHint(hint) |
459 | .queries(predicate)) | ||
560 | .build(); | 460 | .build(); |
561 | 461 | ||
562 | var model = store.createEmptyModel(); | 462 | var model = store.createEmptyModel(); |
563 | var personInterpretation = model.getInterpretation(person); | 463 | var personInterpretation = model.getInterpretation(person); |
564 | var friendInterpretation = model.getInterpretation(friend); | 464 | var friendInterpretation = model.getInterpretation(friend); |
565 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 465 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
566 | var predicateResultSet = queryEngine.getResultSet(predicate); | 466 | var predicateResultSet = queryEngine.getResultSet(predicate); |
567 | 467 | ||
568 | personInterpretation.put(Tuple.of(0), true); | 468 | personInterpretation.put(Tuple.of(0), true); |
@@ -573,37 +473,33 @@ class QueryTest { | |||
573 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); | 473 | friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); |
574 | 474 | ||
575 | queryEngine.flushChanges(); | 475 | queryEngine.flushChanges(); |
576 | assertEquals(2, predicateResultSet.countResults()); | 476 | assertResults(Map.of( |
477 | Tuple.of(0), false, | ||
478 | Tuple.of(1), true, | ||
479 | Tuple.of(2), true, | ||
480 | Tuple.of(3), false | ||
481 | ), predicateResultSet); | ||
577 | } | 482 | } |
578 | 483 | ||
579 | @Test | 484 | @QueryEngineTest |
580 | void transitiveRelationViewTest() { | 485 | void transitiveRelationViewTest(QueryEvaluationHint hint) { |
581 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 486 | var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause( |
582 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 487 | personView.call(p1), |
583 | var personView = new KeyOnlyRelationView<>(person); | 488 | personView.call(p2), |
584 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 489 | friendMustView.callTransitive(p1, p2) |
585 | 490 | )); | |
586 | var p1 = new Variable("p1"); | ||
587 | var p2 = new Variable("p2"); | ||
588 | var predicate = DNF.builder("TransitivePatternCall") | ||
589 | .parameters(p1, p2) | ||
590 | .clause( | ||
591 | new RelationViewAtom(personView, p1), | ||
592 | new RelationViewAtom(personView, p2), | ||
593 | new RelationViewAtom(CallPolarity.TRANSITIVE, friendMustView, p1, p2) | ||
594 | ) | ||
595 | .build(); | ||
596 | 491 | ||
597 | var store = ModelStore.builder() | 492 | var store = ModelStore.builder() |
598 | .symbols(person, friend) | 493 | .symbols(person, friend) |
599 | .with(ViatraModelQuery.ADAPTER) | 494 | .with(ViatraModelQueryAdapter.builder() |
600 | .queries(predicate) | 495 | .defaultHint(hint) |
496 | .queries(predicate)) | ||
601 | .build(); | 497 | .build(); |
602 | 498 | ||
603 | var model = store.createEmptyModel(); | 499 | var model = store.createEmptyModel(); |
604 | var personInterpretation = model.getInterpretation(person); | 500 | var personInterpretation = model.getInterpretation(person); |
605 | var friendInterpretation = model.getInterpretation(friend); | 501 | var friendInterpretation = model.getInterpretation(friend); |
606 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 502 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
607 | var predicateResultSet = queryEngine.getResultSet(predicate); | 503 | var predicateResultSet = queryEngine.getResultSet(predicate); |
608 | 504 | ||
609 | personInterpretation.put(Tuple.of(0), true); | 505 | personInterpretation.put(Tuple.of(0), true); |
@@ -614,48 +510,44 @@ class QueryTest { | |||
614 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 510 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
615 | 511 | ||
616 | queryEngine.flushChanges(); | 512 | queryEngine.flushChanges(); |
617 | assertEquals(3, predicateResultSet.countResults()); | 513 | assertResults(Map.of( |
514 | Tuple.of(0, 0), false, | ||
515 | Tuple.of(0, 1), true, | ||
516 | Tuple.of(0, 2), true, | ||
517 | Tuple.of(1, 0), false, | ||
518 | Tuple.of(1, 1), false, | ||
519 | Tuple.of(1, 2), true, | ||
520 | Tuple.of(2, 0), false, | ||
521 | Tuple.of(2, 1), false, | ||
522 | Tuple.of(2, 2), false, | ||
523 | Tuple.of(2, 3), false | ||
524 | ), predicateResultSet); | ||
618 | } | 525 | } |
619 | 526 | ||
620 | @Test | 527 | @QueryEngineTest |
621 | void transitivePatternCallTest() { | 528 | void transitivePatternCallTest(QueryEvaluationHint hint) { |
622 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 529 | var called = Query.of("Called", (builder, p1, p2) -> builder.clause( |
623 | var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); | 530 | personView.call(p1), |
624 | var personView = new KeyOnlyRelationView<>(person); | 531 | personView.call(p2), |
625 | var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); | 532 | friendMustView.call(p1, p2) |
626 | 533 | )); | |
627 | var p1 = new Variable("p1"); | 534 | var predicate = Query.of("Transitive", (builder, p1, p2) -> builder.clause( |
628 | var p2 = new Variable("p2"); | 535 | personView.call(p1), |
629 | var friendPredicate = DNF.builder("RelationConstraint") | 536 | personView.call(p2), |
630 | .parameters(p1, p2) | 537 | called.callTransitive(p1, p2) |
631 | .clause( | 538 | )); |
632 | new RelationViewAtom(personView, p1), | ||
633 | new RelationViewAtom(personView, p2), | ||
634 | new RelationViewAtom(friendMustView, p1, p2) | ||
635 | ) | ||
636 | .build(); | ||
637 | |||
638 | var p3 = new Variable("p3"); | ||
639 | var p4 = new Variable("p4"); | ||
640 | var predicate = DNF.builder("TransitivePatternCall") | ||
641 | .parameters(p3, p4) | ||
642 | .clause( | ||
643 | new RelationViewAtom(personView, p3), | ||
644 | new RelationViewAtom(personView, p4), | ||
645 | new DNFCallAtom(CallPolarity.TRANSITIVE, friendPredicate, p3, p4) | ||
646 | ) | ||
647 | .build(); | ||
648 | 539 | ||
649 | var store = ModelStore.builder() | 540 | var store = ModelStore.builder() |
650 | .symbols(person, friend) | 541 | .symbols(person, friend) |
651 | .with(ViatraModelQuery.ADAPTER) | 542 | .with(ViatraModelQueryAdapter.builder() |
652 | .queries(predicate) | 543 | .defaultHint(hint) |
544 | .queries(predicate)) | ||
653 | .build(); | 545 | .build(); |
654 | 546 | ||
655 | var model = store.createEmptyModel(); | 547 | var model = store.createEmptyModel(); |
656 | var personInterpretation = model.getInterpretation(person); | 548 | var personInterpretation = model.getInterpretation(person); |
657 | var friendInterpretation = model.getInterpretation(friend); | 549 | var friendInterpretation = model.getInterpretation(friend); |
658 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 550 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
659 | var predicateResultSet = queryEngine.getResultSet(predicate); | 551 | var predicateResultSet = queryEngine.getResultSet(predicate); |
660 | 552 | ||
661 | personInterpretation.put(Tuple.of(0), true); | 553 | personInterpretation.put(Tuple.of(0), true); |
@@ -666,16 +558,150 @@ class QueryTest { | |||
666 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); | 558 | friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); |
667 | 559 | ||
668 | queryEngine.flushChanges(); | 560 | queryEngine.flushChanges(); |
669 | assertEquals(3, predicateResultSet.countResults()); | 561 | assertResults(Map.of( |
562 | Tuple.of(0, 0), false, | ||
563 | Tuple.of(0, 1), true, | ||
564 | Tuple.of(0, 2), true, | ||
565 | Tuple.of(1, 0), false, | ||
566 | Tuple.of(1, 1), false, | ||
567 | Tuple.of(1, 2), true, | ||
568 | Tuple.of(2, 0), false, | ||
569 | Tuple.of(2, 1), false, | ||
570 | Tuple.of(2, 2), false, | ||
571 | Tuple.of(2, 3), false | ||
572 | ), predicateResultSet); | ||
573 | } | ||
574 | |||
575 | @Test | ||
576 | void filteredIntegerViewTest() { | ||
577 | var distance = Symbol.of("distance", 2, Integer.class); | ||
578 | var nearView = new FilteredView<>(distance, value -> value < 2); | ||
579 | var farView = new FilteredView<>(distance, value -> value >= 5); | ||
580 | var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of( | ||
581 | a1.notEquivalent(a2), | ||
582 | nearView.call(a1, a3), | ||
583 | nearView.call(a2, a3), | ||
584 | not(farView.call(a1, a2)) | ||
585 | ))); | ||
586 | var store = ModelStore.builder() | ||
587 | .symbols(distance) | ||
588 | .with(ViatraModelQueryAdapter.builder() | ||
589 | .queries(dangerQuery)) | ||
590 | .build(); | ||
591 | |||
592 | var model = store.createEmptyModel(); | ||
593 | var distanceInterpretation = model.getInterpretation(distance); | ||
594 | distanceInterpretation.put(Tuple.of(0, 1), 1); | ||
595 | distanceInterpretation.put(Tuple.of(1, 0), 1); | ||
596 | distanceInterpretation.put(Tuple.of(0, 2), 1); | ||
597 | distanceInterpretation.put(Tuple.of(2, 0), 1); | ||
598 | distanceInterpretation.put(Tuple.of(1, 2), 3); | ||
599 | distanceInterpretation.put(Tuple.of(2, 1), 3); | ||
600 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
601 | var dangerResultSet = queryEngine.getResultSet(dangerQuery); | ||
602 | queryEngine.flushChanges(); | ||
603 | assertResults(Map.of( | ||
604 | Tuple.of(0, 1), false, | ||
605 | Tuple.of(0, 2), false, | ||
606 | Tuple.of(1, 2), true, | ||
607 | Tuple.of(2, 1), true | ||
608 | ), dangerResultSet); | ||
609 | } | ||
610 | |||
611 | @Test | ||
612 | void filteredDoubleViewTest() { | ||
613 | var distance = Symbol.of("distance", 2, Double.class); | ||
614 | var nearView = new FilteredView<>(distance, value -> value < 2); | ||
615 | var farView = new FilteredView<>(distance, value -> value >= 5); | ||
616 | var dangerQuery = Query.of("danger", (builder, a1, a2) -> builder.clause((a3) -> List.of( | ||
617 | a1.notEquivalent(a2), | ||
618 | nearView.call(a1, a3), | ||
619 | nearView.call(a2, a3), | ||
620 | not(farView.call(a1, a2)) | ||
621 | ))); | ||
622 | var store = ModelStore.builder() | ||
623 | .symbols(distance) | ||
624 | .with(ViatraModelQueryAdapter.builder() | ||
625 | .queries(dangerQuery)) | ||
626 | .build(); | ||
627 | |||
628 | var model = store.createEmptyModel(); | ||
629 | var distanceInterpretation = model.getInterpretation(distance); | ||
630 | distanceInterpretation.put(Tuple.of(0, 1), 1.0); | ||
631 | distanceInterpretation.put(Tuple.of(1, 0), 1.0); | ||
632 | distanceInterpretation.put(Tuple.of(0, 2), 1.0); | ||
633 | distanceInterpretation.put(Tuple.of(2, 0), 1.0); | ||
634 | distanceInterpretation.put(Tuple.of(1, 2), 3.0); | ||
635 | distanceInterpretation.put(Tuple.of(2, 1), 3.0); | ||
636 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
637 | var dangerResultSet = queryEngine.getResultSet(dangerQuery); | ||
638 | queryEngine.flushChanges(); | ||
639 | assertResults(Map.of( | ||
640 | Tuple.of(0, 1), false, | ||
641 | Tuple.of(0, 2), false, | ||
642 | Tuple.of(1, 2), true, | ||
643 | Tuple.of(2, 1), true | ||
644 | ), dangerResultSet); | ||
645 | } | ||
646 | |||
647 | @QueryEngineTest | ||
648 | void assumeTest(QueryEvaluationHint hint) { | ||
649 | var age = Symbol.of("age", 1, Integer.class); | ||
650 | var ageView = new FunctionView<>(age); | ||
651 | |||
652 | var query = Query.of("Constraint", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( | ||
653 | personView.call(p1), | ||
654 | ageView.call(p1, x), | ||
655 | assume(greaterEq(x, constant(18))) | ||
656 | ))); | ||
657 | |||
658 | var store = ModelStore.builder() | ||
659 | .symbols(person, age) | ||
660 | .with(ViatraModelQueryAdapter.builder() | ||
661 | .defaultHint(hint) | ||
662 | .queries(query)) | ||
663 | .build(); | ||
664 | |||
665 | var model = store.createEmptyModel(); | ||
666 | var personInterpretation = model.getInterpretation(person); | ||
667 | var ageInterpretation = model.getInterpretation(age); | ||
668 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
669 | var queryResultSet = queryEngine.getResultSet(query); | ||
670 | |||
671 | personInterpretation.put(Tuple.of(0), true); | ||
672 | personInterpretation.put(Tuple.of(1), true); | ||
673 | |||
674 | ageInterpretation.put(Tuple.of(0), 12); | ||
675 | ageInterpretation.put(Tuple.of(1), 24); | ||
676 | |||
677 | queryEngine.flushChanges(); | ||
678 | assertResults(Map.of( | ||
679 | Tuple.of(0), false, | ||
680 | Tuple.of(1), true, | ||
681 | Tuple.of(2), false | ||
682 | ), queryResultSet); | ||
670 | } | 683 | } |
671 | 684 | ||
672 | static void compareMatchSets(Stream<TupleLike> matchSet, Set<Tuple> expected) { | 685 | @Test |
673 | Set<Tuple> translatedMatchSet = new HashSet<>(); | 686 | void alwaysFalseTest() { |
674 | var iterator = matchSet.iterator(); | 687 | var predicate = Query.of("AlwaysFalse", builder -> builder.parameter("p1")); |
675 | while (iterator.hasNext()) { | 688 | |
676 | var element = iterator.next(); | 689 | var store = ModelStore.builder() |
677 | translatedMatchSet.add(element.toTuple()); | 690 | .symbols(person) |
678 | } | 691 | .with(ViatraModelQueryAdapter.builder() |
679 | assertEquals(expected, translatedMatchSet); | 692 | .queries(predicate)) |
693 | .build(); | ||
694 | |||
695 | var model = store.createEmptyModel(); | ||
696 | var personInterpretation = model.getInterpretation(person); | ||
697 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
698 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
699 | |||
700 | personInterpretation.put(Tuple.of(0), true); | ||
701 | personInterpretation.put(Tuple.of(1), true); | ||
702 | personInterpretation.put(Tuple.of(2), true); | ||
703 | |||
704 | queryEngine.flushChanges(); | ||
705 | assertResults(Map.of(), predicateResultSet); | ||
680 | } | 706 | } |
681 | } | 707 | } |
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java index 98995339..66f043c6 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java | |||
@@ -1,43 +1,163 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.viatra; | 6 | package tools.refinery.store.query.viatra; |
2 | 7 | ||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
9 | import org.junit.jupiter.api.Disabled; | ||
3 | import org.junit.jupiter.api.Test; | 10 | import org.junit.jupiter.api.Test; |
4 | import tools.refinery.store.model.ModelStore; | 11 | import tools.refinery.store.model.ModelStore; |
5 | import tools.refinery.store.query.DNF; | 12 | import tools.refinery.store.query.ModelQueryAdapter; |
6 | import tools.refinery.store.query.ModelQuery; | 13 | import tools.refinery.store.query.dnf.Query; |
7 | import tools.refinery.store.query.Variable; | 14 | import tools.refinery.store.query.dnf.RelationalQuery; |
8 | import tools.refinery.store.query.atom.RelationViewAtom; | 15 | import tools.refinery.store.query.view.AnySymbolView; |
9 | import tools.refinery.store.query.view.KeyOnlyRelationView; | 16 | import tools.refinery.store.query.view.FilteredView; |
17 | import tools.refinery.store.query.view.FunctionView; | ||
18 | import tools.refinery.store.query.view.KeyOnlyView; | ||
10 | import tools.refinery.store.representation.Symbol; | 19 | import tools.refinery.store.representation.Symbol; |
11 | import tools.refinery.store.tuple.Tuple; | 20 | import tools.refinery.store.tuple.Tuple; |
12 | 21 | ||
13 | import static org.junit.jupiter.api.Assertions.*; | 22 | import java.util.Map; |
23 | import java.util.Optional; | ||
24 | |||
25 | import static org.junit.jupiter.api.Assertions.assertFalse; | ||
26 | import static org.junit.jupiter.api.Assertions.assertTrue; | ||
27 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; | ||
28 | import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; | ||
14 | 29 | ||
15 | class QueryTransactionTest { | 30 | class QueryTransactionTest { |
31 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
32 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
33 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
34 | private static final AnySymbolView ageView = new FunctionView<>(age); | ||
35 | private static final RelationalQuery predicate = Query.of("TypeConstraint", (builder, p1) -> | ||
36 | builder.clause(personView.call(p1))); | ||
37 | |||
16 | @Test | 38 | @Test |
17 | void flushTest() { | 39 | void flushTest() { |
18 | var person = new Symbol<>("Person", 1, Boolean.class, false); | 40 | var store = ModelStore.builder() |
19 | var asset = new Symbol<>("Asset", 1, Boolean.class, false); | 41 | .symbols(person) |
20 | var personView = new KeyOnlyRelationView<>(person); | 42 | .with(ViatraModelQueryAdapter.builder() |
21 | 43 | .queries(predicate)) | |
22 | var p1 = new Variable("p1"); | ||
23 | var predicate = DNF.builder("TypeConstraint") | ||
24 | .parameters(p1) | ||
25 | .clause(new RelationViewAtom(personView, p1)) | ||
26 | .build(); | 44 | .build(); |
27 | 45 | ||
46 | var model = store.createEmptyModel(); | ||
47 | var personInterpretation = model.getInterpretation(person); | ||
48 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
49 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
50 | |||
51 | assertResults(Map.of( | ||
52 | Tuple.of(0), false, | ||
53 | Tuple.of(1), false, | ||
54 | Tuple.of(2), false, | ||
55 | Tuple.of(3), false | ||
56 | ), predicateResultSet); | ||
57 | assertFalse(queryEngine.hasPendingChanges()); | ||
58 | |||
59 | personInterpretation.put(Tuple.of(0), true); | ||
60 | personInterpretation.put(Tuple.of(1), true); | ||
61 | |||
62 | assertResults(Map.of( | ||
63 | Tuple.of(0), false, | ||
64 | Tuple.of(1), false, | ||
65 | Tuple.of(2), false, | ||
66 | Tuple.of(3), false | ||
67 | ), predicateResultSet); | ||
68 | assertTrue(queryEngine.hasPendingChanges()); | ||
69 | |||
70 | queryEngine.flushChanges(); | ||
71 | assertResults(Map.of( | ||
72 | Tuple.of(0), true, | ||
73 | Tuple.of(1), true, | ||
74 | Tuple.of(2), false, | ||
75 | Tuple.of(3), false | ||
76 | ), predicateResultSet); | ||
77 | assertFalse(queryEngine.hasPendingChanges()); | ||
78 | |||
79 | personInterpretation.put(Tuple.of(1), false); | ||
80 | personInterpretation.put(Tuple.of(2), true); | ||
81 | |||
82 | assertResults(Map.of( | ||
83 | Tuple.of(0), true, | ||
84 | Tuple.of(1), true, | ||
85 | Tuple.of(2), false, | ||
86 | Tuple.of(3), false | ||
87 | ), predicateResultSet); | ||
88 | assertTrue(queryEngine.hasPendingChanges()); | ||
89 | |||
90 | queryEngine.flushChanges(); | ||
91 | assertResults(Map.of( | ||
92 | Tuple.of(0), true, | ||
93 | Tuple.of(1), false, | ||
94 | Tuple.of(2), true, | ||
95 | Tuple.of(3), false | ||
96 | ), predicateResultSet); | ||
97 | assertFalse(queryEngine.hasPendingChanges()); | ||
98 | } | ||
99 | |||
100 | @Test | ||
101 | void localSearchTest() { | ||
102 | var store = ModelStore.builder() | ||
103 | .symbols(person) | ||
104 | .with(ViatraModelQueryAdapter.builder() | ||
105 | .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) | ||
106 | .queries(predicate)) | ||
107 | .build(); | ||
108 | |||
109 | var model = store.createEmptyModel(); | ||
110 | var personInterpretation = model.getInterpretation(person); | ||
111 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
112 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
113 | |||
114 | assertResults(Map.of( | ||
115 | Tuple.of(0), false, | ||
116 | Tuple.of(1), false, | ||
117 | Tuple.of(2), false, | ||
118 | Tuple.of(3), false | ||
119 | ), predicateResultSet); | ||
120 | assertFalse(queryEngine.hasPendingChanges()); | ||
121 | |||
122 | personInterpretation.put(Tuple.of(0), true); | ||
123 | personInterpretation.put(Tuple.of(1), true); | ||
124 | |||
125 | assertResults(Map.of( | ||
126 | Tuple.of(0), true, | ||
127 | Tuple.of(1), true, | ||
128 | Tuple.of(2), false, | ||
129 | Tuple.of(3), false | ||
130 | ), predicateResultSet); | ||
131 | assertFalse(queryEngine.hasPendingChanges()); | ||
132 | |||
133 | personInterpretation.put(Tuple.of(1), false); | ||
134 | personInterpretation.put(Tuple.of(2), true); | ||
135 | |||
136 | assertResults(Map.of( | ||
137 | Tuple.of(0), true, | ||
138 | Tuple.of(1), false, | ||
139 | Tuple.of(2), true, | ||
140 | Tuple.of(3), false | ||
141 | ), predicateResultSet); | ||
142 | assertFalse(queryEngine.hasPendingChanges()); | ||
143 | } | ||
144 | |||
145 | @Test | ||
146 | void unrelatedChangesTest() { | ||
147 | var asset = Symbol.of("Asset", 1); | ||
148 | |||
28 | var store = ModelStore.builder() | 149 | var store = ModelStore.builder() |
29 | .symbols(person, asset) | 150 | .symbols(person, asset) |
30 | .with(ViatraModelQuery.ADAPTER) | 151 | .with(ViatraModelQueryAdapter.builder() |
31 | .queries(predicate) | 152 | .queries(predicate)) |
32 | .build(); | 153 | .build(); |
33 | 154 | ||
34 | var model = store.createEmptyModel(); | 155 | var model = store.createEmptyModel(); |
35 | var personInterpretation = model.getInterpretation(person); | 156 | var personInterpretation = model.getInterpretation(person); |
36 | var assetInterpretation = model.getInterpretation(asset); | 157 | var assetInterpretation = model.getInterpretation(asset); |
37 | var queryEngine = model.getAdapter(ModelQuery.ADAPTER); | 158 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); |
38 | var predicateResultSet = queryEngine.getResultSet(predicate); | 159 | var predicateResultSet = queryEngine.getResultSet(predicate); |
39 | 160 | ||
40 | assertEquals(0, predicateResultSet.countResults()); | ||
41 | assertFalse(queryEngine.hasPendingChanges()); | 161 | assertFalse(queryEngine.hasPendingChanges()); |
42 | 162 | ||
43 | personInterpretation.put(Tuple.of(0), true); | 163 | personInterpretation.put(Tuple.of(0), true); |
@@ -46,19 +166,208 @@ class QueryTransactionTest { | |||
46 | assetInterpretation.put(Tuple.of(1), true); | 166 | assetInterpretation.put(Tuple.of(1), true); |
47 | assetInterpretation.put(Tuple.of(2), true); | 167 | assetInterpretation.put(Tuple.of(2), true); |
48 | 168 | ||
49 | assertEquals(0, predicateResultSet.countResults()); | 169 | assertResults(Map.of( |
170 | Tuple.of(0), false, | ||
171 | Tuple.of(1), false, | ||
172 | Tuple.of(2), false, | ||
173 | Tuple.of(3), false, | ||
174 | Tuple.of(4), false | ||
175 | ), predicateResultSet); | ||
50 | assertTrue(queryEngine.hasPendingChanges()); | 176 | assertTrue(queryEngine.hasPendingChanges()); |
51 | 177 | ||
52 | queryEngine.flushChanges(); | 178 | queryEngine.flushChanges(); |
53 | assertEquals(2, predicateResultSet.countResults()); | 179 | assertResults(Map.of( |
180 | Tuple.of(0), true, | ||
181 | Tuple.of(1), true, | ||
182 | Tuple.of(2), false, | ||
183 | Tuple.of(3), false, | ||
184 | Tuple.of(4), false | ||
185 | ), predicateResultSet); | ||
54 | assertFalse(queryEngine.hasPendingChanges()); | 186 | assertFalse(queryEngine.hasPendingChanges()); |
55 | 187 | ||
56 | personInterpretation.put(Tuple.of(4), true); | 188 | assetInterpretation.put(Tuple.of(3), true); |
57 | assertEquals(2, predicateResultSet.countResults()); | 189 | assertFalse(queryEngine.hasPendingChanges()); |
58 | assertTrue(queryEngine.hasPendingChanges()); | 190 | |
191 | assertResults(Map.of( | ||
192 | Tuple.of(0), true, | ||
193 | Tuple.of(1), true, | ||
194 | Tuple.of(2), false, | ||
195 | Tuple.of(3), false, | ||
196 | Tuple.of(4), false | ||
197 | ), predicateResultSet); | ||
198 | |||
199 | queryEngine.flushChanges(); | ||
200 | assertResults(Map.of( | ||
201 | Tuple.of(0), true, | ||
202 | Tuple.of(1), true, | ||
203 | Tuple.of(2), false, | ||
204 | Tuple.of(3), false, | ||
205 | Tuple.of(4), false | ||
206 | ), predicateResultSet); | ||
207 | assertFalse(queryEngine.hasPendingChanges()); | ||
208 | } | ||
209 | |||
210 | @Test | ||
211 | void tupleChangingChangeTest() { | ||
212 | var query = Query.of("TypeConstraint", Integer.class, (builder, p1, output) -> builder.clause( | ||
213 | personView.call(p1), | ||
214 | ageView.call(p1, output) | ||
215 | )); | ||
216 | |||
217 | var store = ModelStore.builder() | ||
218 | .symbols(person, age) | ||
219 | .with(ViatraModelQueryAdapter.builder() | ||
220 | .queries(query)) | ||
221 | .build(); | ||
222 | |||
223 | var model = store.createEmptyModel(); | ||
224 | var personInterpretation = model.getInterpretation(person); | ||
225 | var ageInterpretation = model.getInterpretation(age); | ||
226 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
227 | var queryResultSet = queryEngine.getResultSet(query); | ||
228 | |||
229 | personInterpretation.put(Tuple.of(0), true); | ||
230 | |||
231 | ageInterpretation.put(Tuple.of(0), 24); | ||
232 | |||
233 | queryEngine.flushChanges(); | ||
234 | assertResults(Map.of(Tuple.of(0), 24), queryResultSet); | ||
235 | |||
236 | ageInterpretation.put(Tuple.of(0), 25); | ||
59 | 237 | ||
60 | queryEngine.flushChanges(); | 238 | queryEngine.flushChanges(); |
61 | assertEquals(3, predicateResultSet.countResults()); | 239 | assertResults(Map.of(Tuple.of(0), 25), queryResultSet); |
240 | |||
241 | ageInterpretation.put(Tuple.of(0), null); | ||
242 | |||
243 | queryEngine.flushChanges(); | ||
244 | assertNullableResults(Map.of(Tuple.of(0), Optional.empty()), queryResultSet); | ||
245 | } | ||
246 | |||
247 | @Test | ||
248 | void tuplePreservingUnchangedTest() { | ||
249 | var adultView = new FilteredView<>(age, "adult", n -> n != null && n >= 18); | ||
250 | |||
251 | var query = Query.of("TypeConstraint", (builder, p1) -> builder.clause( | ||
252 | personView.call(p1), | ||
253 | adultView.call(p1) | ||
254 | )); | ||
255 | |||
256 | var store = ModelStore.builder() | ||
257 | .symbols(person, age) | ||
258 | .with(ViatraModelQueryAdapter.builder() | ||
259 | .queries(query)) | ||
260 | .build(); | ||
261 | |||
262 | var model = store.createEmptyModel(); | ||
263 | var personInterpretation = model.getInterpretation(person); | ||
264 | var ageInterpretation = model.getInterpretation(age); | ||
265 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
266 | var queryResultSet = queryEngine.getResultSet(query); | ||
267 | |||
268 | personInterpretation.put(Tuple.of(0), true); | ||
269 | |||
270 | ageInterpretation.put(Tuple.of(0), 24); | ||
271 | |||
272 | queryEngine.flushChanges(); | ||
273 | assertResults(Map.of(Tuple.of(0), true), queryResultSet); | ||
274 | |||
275 | ageInterpretation.put(Tuple.of(0), 25); | ||
276 | |||
277 | queryEngine.flushChanges(); | ||
278 | assertResults(Map.of(Tuple.of(0), true), queryResultSet); | ||
279 | |||
280 | ageInterpretation.put(Tuple.of(0), 17); | ||
281 | |||
282 | queryEngine.flushChanges(); | ||
283 | assertResults(Map.of(Tuple.of(0), false), queryResultSet); | ||
284 | } | ||
285 | |||
286 | @Disabled("TODO Fix DiffCursor") | ||
287 | @Test | ||
288 | void commitAfterFlushTest() { | ||
289 | var store = ModelStore.builder() | ||
290 | .symbols(person) | ||
291 | .with(ViatraModelQueryAdapter.builder() | ||
292 | .queries(predicate)) | ||
293 | .build(); | ||
294 | |||
295 | var model = store.createEmptyModel(); | ||
296 | var personInterpretation = model.getInterpretation(person); | ||
297 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
298 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
299 | |||
300 | personInterpretation.put(Tuple.of(0), true); | ||
301 | personInterpretation.put(Tuple.of(1), true); | ||
302 | |||
303 | queryEngine.flushChanges(); | ||
304 | assertResults(Map.of( | ||
305 | Tuple.of(0), true, | ||
306 | Tuple.of(1), true, | ||
307 | Tuple.of(2), false, | ||
308 | Tuple.of(3), false | ||
309 | ), predicateResultSet); | ||
310 | |||
311 | var state1 = model.commit(); | ||
312 | |||
313 | personInterpretation.put(Tuple.of(1), false); | ||
314 | personInterpretation.put(Tuple.of(2), true); | ||
315 | |||
316 | queryEngine.flushChanges(); | ||
317 | assertResults(Map.of( | ||
318 | Tuple.of(0), true, | ||
319 | Tuple.of(1), false, | ||
320 | Tuple.of(2), true, | ||
321 | Tuple.of(3), false | ||
322 | ), predicateResultSet); | ||
323 | |||
324 | model.restore(state1); | ||
325 | |||
326 | assertFalse(queryEngine.hasPendingChanges()); | ||
327 | assertResults(Map.of( | ||
328 | Tuple.of(0), true, | ||
329 | Tuple.of(1), true, | ||
330 | Tuple.of(2), false, | ||
331 | Tuple.of(3), false | ||
332 | ), predicateResultSet); | ||
333 | } | ||
334 | |||
335 | @Disabled("TODO Fix DiffCursor") | ||
336 | @Test | ||
337 | void commitWithoutFlushTest() { | ||
338 | var store = ModelStore.builder() | ||
339 | .symbols(person) | ||
340 | .with(ViatraModelQueryAdapter.builder() | ||
341 | .queries(predicate)) | ||
342 | .build(); | ||
343 | |||
344 | var model = store.createEmptyModel(); | ||
345 | var personInterpretation = model.getInterpretation(person); | ||
346 | var queryEngine = model.getAdapter(ModelQueryAdapter.class); | ||
347 | var predicateResultSet = queryEngine.getResultSet(predicate); | ||
348 | |||
349 | personInterpretation.put(Tuple.of(0), true); | ||
350 | personInterpretation.put(Tuple.of(1), true); | ||
351 | |||
352 | assertResults(Map.of(), predicateResultSet); | ||
353 | assertTrue(queryEngine.hasPendingChanges()); | ||
354 | |||
355 | var state1 = model.commit(); | ||
356 | |||
357 | personInterpretation.put(Tuple.of(1), false); | ||
358 | personInterpretation.put(Tuple.of(2), true); | ||
359 | |||
360 | assertResults(Map.of(), predicateResultSet); | ||
361 | assertTrue(queryEngine.hasPendingChanges()); | ||
362 | |||
363 | model.restore(state1); | ||
364 | |||
365 | assertResults(Map.of( | ||
366 | Tuple.of(0), true, | ||
367 | Tuple.of(1), true, | ||
368 | Tuple.of(2), false, | ||
369 | Tuple.of(3), false | ||
370 | ), predicateResultSet); | ||
62 | assertFalse(queryEngine.hasPendingChanges()); | 371 | assertFalse(queryEngine.hasPendingChanges()); |
63 | } | 372 | } |
64 | } | 373 | } |
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java deleted file mode 100644 index 20dad543..00000000 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java +++ /dev/null | |||
@@ -1,87 +0,0 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.cardinality; | ||
2 | |||
3 | import org.junit.jupiter.api.BeforeEach; | ||
4 | import org.junit.jupiter.api.Test; | ||
5 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
6 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
7 | |||
8 | import static org.hamcrest.MatcherAssert.assertThat; | ||
9 | import static org.hamcrest.Matchers.equalTo; | ||
10 | |||
11 | class UpperCardinalitySumAggregationOperatorTest { | ||
12 | private UpperCardinalitySumAggregationOperator.Accumulator accumulator; | ||
13 | |||
14 | @BeforeEach | ||
15 | void beforeEach() { | ||
16 | accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.createNeutral(); | ||
17 | } | ||
18 | |||
19 | @Test | ||
20 | void emptyAggregationTest() { | ||
21 | assertResult(UpperCardinality.of(0)); | ||
22 | } | ||
23 | |||
24 | @Test | ||
25 | void singleBoundedTest() { | ||
26 | insert(UpperCardinality.of(3)); | ||
27 | assertResult(UpperCardinality.of(3)); | ||
28 | } | ||
29 | |||
30 | @Test | ||
31 | void multipleBoundedTest() { | ||
32 | insert(UpperCardinality.of(2)); | ||
33 | insert(UpperCardinality.of(3)); | ||
34 | assertResult(UpperCardinality.of(5)); | ||
35 | } | ||
36 | |||
37 | @Test | ||
38 | void singleUnboundedTest() { | ||
39 | insert(UpperCardinalities.UNBOUNDED); | ||
40 | assertResult(UpperCardinalities.UNBOUNDED); | ||
41 | } | ||
42 | |||
43 | @Test | ||
44 | void multipleUnboundedTest() { | ||
45 | insert(UpperCardinalities.UNBOUNDED); | ||
46 | insert(UpperCardinalities.UNBOUNDED); | ||
47 | assertResult(UpperCardinalities.UNBOUNDED); | ||
48 | } | ||
49 | |||
50 | @Test | ||
51 | void removeBoundedTest() { | ||
52 | insert(UpperCardinality.of(2)); | ||
53 | insert(UpperCardinality.of(3)); | ||
54 | remove(UpperCardinality.of(2)); | ||
55 | assertResult(UpperCardinality.of(3)); | ||
56 | } | ||
57 | |||
58 | @Test | ||
59 | void removeAllUnboundedTest() { | ||
60 | insert(UpperCardinalities.UNBOUNDED); | ||
61 | insert(UpperCardinality.of(3)); | ||
62 | remove(UpperCardinalities.UNBOUNDED); | ||
63 | assertResult(UpperCardinality.of(3)); | ||
64 | } | ||
65 | |||
66 | @Test | ||
67 | void removeSomeUnboundedTest() { | ||
68 | insert(UpperCardinalities.UNBOUNDED); | ||
69 | insert(UpperCardinalities.UNBOUNDED); | ||
70 | insert(UpperCardinality.of(3)); | ||
71 | remove(UpperCardinalities.UNBOUNDED); | ||
72 | assertResult(UpperCardinalities.UNBOUNDED); | ||
73 | } | ||
74 | |||
75 | private void insert(UpperCardinality value) { | ||
76 | accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.update(accumulator, value, true); | ||
77 | } | ||
78 | |||
79 | private void remove(UpperCardinality value) { | ||
80 | accumulator = UpperCardinalitySumAggregationOperator.INSTANCE.update(accumulator, value, false); | ||
81 | } | ||
82 | |||
83 | private void assertResult(UpperCardinality expected) { | ||
84 | var result = UpperCardinalitySumAggregationOperator.INSTANCE.getAggregate(accumulator); | ||
85 | assertThat(result, equalTo(expected)); | ||
86 | } | ||
87 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java new file mode 100644 index 00000000..968c6c5e --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java | |||
@@ -0,0 +1,239 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.internal.matcher; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.tuple.*; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | import tools.refinery.store.tuple.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | |||
15 | import static org.hamcrest.MatcherAssert.assertThat; | ||
16 | import static org.hamcrest.Matchers.*; | ||
17 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
18 | |||
19 | class MatcherUtilsTest { | ||
20 | @Test | ||
21 | void toViatra0Test() { | ||
22 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of()); | ||
23 | assertThat(viatraTuple.getSize(), is(0)); | ||
24 | assertThat(viatraTuple, instanceOf(FlatTuple0.class)); | ||
25 | } | ||
26 | |||
27 | @Test | ||
28 | void toViatra1Test() { | ||
29 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2)); | ||
30 | assertThat(viatraTuple.getSize(), is(1)); | ||
31 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
32 | assertThat(viatraTuple, instanceOf(FlatTuple1.class)); | ||
33 | } | ||
34 | |||
35 | @Test | ||
36 | void toViatra2Test() { | ||
37 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3)); | ||
38 | assertThat(viatraTuple.getSize(), is(2)); | ||
39 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
40 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
41 | assertThat(viatraTuple, instanceOf(FlatTuple2.class)); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void toViatra3Test() { | ||
46 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5)); | ||
47 | assertThat(viatraTuple.getSize(), is(3)); | ||
48 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
49 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
50 | assertThat(viatraTuple.get(2), is(Tuple.of(5))); | ||
51 | assertThat(viatraTuple, instanceOf(FlatTuple3.class)); | ||
52 | } | ||
53 | |||
54 | @Test | ||
55 | void toViatra4Test() { | ||
56 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8)); | ||
57 | assertThat(viatraTuple.getSize(), is(4)); | ||
58 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
59 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
60 | assertThat(viatraTuple.get(2), is(Tuple.of(5))); | ||
61 | assertThat(viatraTuple.get(3), is(Tuple.of(8))); | ||
62 | assertThat(viatraTuple, instanceOf(FlatTuple4.class)); | ||
63 | } | ||
64 | |||
65 | @Test | ||
66 | void toViatra5Test() { | ||
67 | var viatraTuple = MatcherUtils.toViatraTuple(Tuple.of(2, 3, 5, 8, 13)); | ||
68 | assertThat(viatraTuple.getSize(), is(5)); | ||
69 | assertThat(viatraTuple.get(0), is(Tuple.of(2))); | ||
70 | assertThat(viatraTuple.get(1), is(Tuple.of(3))); | ||
71 | assertThat(viatraTuple.get(2), is(Tuple.of(5))); | ||
72 | assertThat(viatraTuple.get(3), is(Tuple.of(8))); | ||
73 | assertThat(viatraTuple.get(4), is(Tuple.of(13))); | ||
74 | assertThat(viatraTuple, instanceOf(FlatTuple.class)); | ||
75 | } | ||
76 | |||
77 | @Test | ||
78 | void toRefinery0Test() { | ||
79 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf()); | ||
80 | assertThat(refineryTuple.getSize(), is(0)); | ||
81 | assertThat(refineryTuple, instanceOf(Tuple0.class)); | ||
82 | } | ||
83 | |||
84 | @Test | ||
85 | void toRefinery1Test() { | ||
86 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2))); | ||
87 | assertThat(refineryTuple.getSize(), is(1)); | ||
88 | assertThat(refineryTuple.get(0), is(2)); | ||
89 | assertThat(refineryTuple, instanceOf(Tuple1.class)); | ||
90 | } | ||
91 | |||
92 | @Test | ||
93 | void toRefinery2Test() { | ||
94 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3))); | ||
95 | assertThat(refineryTuple.getSize(), is(2)); | ||
96 | assertThat(refineryTuple.get(0), is(2)); | ||
97 | assertThat(refineryTuple.get(1), is(3)); | ||
98 | assertThat(refineryTuple, instanceOf(Tuple2.class)); | ||
99 | } | ||
100 | |||
101 | @Test | ||
102 | void toRefinery3Test() { | ||
103 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5))); | ||
104 | assertThat(refineryTuple.getSize(), is(3)); | ||
105 | assertThat(refineryTuple.get(0), is(2)); | ||
106 | assertThat(refineryTuple.get(1), is(3)); | ||
107 | assertThat(refineryTuple.get(2), is(5)); | ||
108 | assertThat(refineryTuple, instanceOf(Tuple3.class)); | ||
109 | } | ||
110 | |||
111 | @Test | ||
112 | void toRefinery4Test() { | ||
113 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
114 | Tuple.of(8))); | ||
115 | assertThat(refineryTuple.getSize(), is(4)); | ||
116 | assertThat(refineryTuple.get(0), is(2)); | ||
117 | assertThat(refineryTuple.get(1), is(3)); | ||
118 | assertThat(refineryTuple.get(2), is(5)); | ||
119 | assertThat(refineryTuple.get(3), is(8)); | ||
120 | assertThat(refineryTuple, instanceOf(Tuple4.class)); | ||
121 | } | ||
122 | |||
123 | @Test | ||
124 | void toRefinery5Test() { | ||
125 | var refineryTuple = MatcherUtils.toRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
126 | Tuple.of(8), Tuple.of(13))); | ||
127 | assertThat(refineryTuple.getSize(), is(5)); | ||
128 | assertThat(refineryTuple.get(0), is(2)); | ||
129 | assertThat(refineryTuple.get(1), is(3)); | ||
130 | assertThat(refineryTuple.get(2), is(5)); | ||
131 | assertThat(refineryTuple.get(3), is(8)); | ||
132 | assertThat(refineryTuple.get(4), is(13)); | ||
133 | assertThat(refineryTuple, instanceOf(TupleN.class)); | ||
134 | } | ||
135 | |||
136 | @Test | ||
137 | void toRefineryInvalidValueTest() { | ||
138 | var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98); | ||
139 | assertThrows(IllegalArgumentException.class, () -> MatcherUtils.toRefineryTuple(viatraTuple)); | ||
140 | } | ||
141 | |||
142 | @Test | ||
143 | void keyToRefinery0Test() { | ||
144 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(-99)); | ||
145 | assertThat(refineryTuple.getSize(), is(0)); | ||
146 | assertThat(refineryTuple, instanceOf(Tuple0.class)); | ||
147 | } | ||
148 | |||
149 | @Test | ||
150 | void keyToRefinery1Test() { | ||
151 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), -99)); | ||
152 | assertThat(refineryTuple.getSize(), is(1)); | ||
153 | assertThat(refineryTuple.get(0), is(2)); | ||
154 | assertThat(refineryTuple, instanceOf(Tuple1.class)); | ||
155 | } | ||
156 | |||
157 | @Test | ||
158 | void keyToRefinery2Test() { | ||
159 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), -99)); | ||
160 | assertThat(refineryTuple.getSize(), is(2)); | ||
161 | assertThat(refineryTuple.get(0), is(2)); | ||
162 | assertThat(refineryTuple.get(1), is(3)); | ||
163 | assertThat(refineryTuple, instanceOf(Tuple2.class)); | ||
164 | } | ||
165 | |||
166 | @Test | ||
167 | void keyToRefinery3Test() { | ||
168 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
169 | -99)); | ||
170 | assertThat(refineryTuple.getSize(), is(3)); | ||
171 | assertThat(refineryTuple.get(0), is(2)); | ||
172 | assertThat(refineryTuple.get(1), is(3)); | ||
173 | assertThat(refineryTuple.get(2), is(5)); | ||
174 | assertThat(refineryTuple, instanceOf(Tuple3.class)); | ||
175 | } | ||
176 | |||
177 | @Test | ||
178 | void keyToRefinery4Test() { | ||
179 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
180 | Tuple.of(8), -99)); | ||
181 | assertThat(refineryTuple.getSize(), is(4)); | ||
182 | assertThat(refineryTuple.get(0), is(2)); | ||
183 | assertThat(refineryTuple.get(1), is(3)); | ||
184 | assertThat(refineryTuple.get(2), is(5)); | ||
185 | assertThat(refineryTuple.get(3), is(8)); | ||
186 | assertThat(refineryTuple, instanceOf(Tuple4.class)); | ||
187 | } | ||
188 | |||
189 | @Test | ||
190 | void keyToRefinery5Test() { | ||
191 | var refineryTuple = MatcherUtils.keyToRefineryTuple(Tuples.flatTupleOf(Tuple.of(2), Tuple.of(3), Tuple.of(5), | ||
192 | Tuple.of(8), Tuple.of(13), -99)); | ||
193 | assertThat(refineryTuple.getSize(), is(5)); | ||
194 | assertThat(refineryTuple.get(0), is(2)); | ||
195 | assertThat(refineryTuple.get(1), is(3)); | ||
196 | assertThat(refineryTuple.get(2), is(5)); | ||
197 | assertThat(refineryTuple.get(3), is(8)); | ||
198 | assertThat(refineryTuple.get(4), is(13)); | ||
199 | assertThat(refineryTuple, instanceOf(TupleN.class)); | ||
200 | } | ||
201 | |||
202 | @Test | ||
203 | void keyToRefineryTooShortTest() { | ||
204 | var viatraTuple = Tuples.flatTupleOf(); | ||
205 | assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple)); | ||
206 | } | ||
207 | |||
208 | @Test | ||
209 | void keyToRefineryInvalidValueTest() { | ||
210 | var viatraTuple = Tuples.flatTupleOf(Tuple.of(2), -98, -99); | ||
211 | assertThrows(IllegalArgumentException.class, () -> MatcherUtils.keyToRefineryTuple(viatraTuple)); | ||
212 | } | ||
213 | |||
214 | @Test | ||
215 | void getSingleValueTest() { | ||
216 | var value = MatcherUtils.getSingleValue(List.of(Tuples.flatTupleOf(Tuple.of(2), -99))); | ||
217 | assertThat(value, is(-99)); | ||
218 | } | ||
219 | |||
220 | // Static analysis accurately determines that the result is always {@code null}, but we check anyways. | ||
221 | @SuppressWarnings("ConstantValue") | ||
222 | @Test | ||
223 | void getSingleValueNullTest() { | ||
224 | var value = MatcherUtils.getSingleValue((Iterable<? extends ITuple>) null); | ||
225 | assertThat(value, nullValue()); | ||
226 | } | ||
227 | |||
228 | @Test | ||
229 | void getSingleValueEmptyTest() { | ||
230 | var value = MatcherUtils.getSingleValue(List.of()); | ||
231 | assertThat(value, nullValue()); | ||
232 | } | ||
233 | |||
234 | @Test | ||
235 | void getSingleValueMultipleTest() { | ||
236 | var viatraTuples = List.of(Tuples.flatTupleOf(Tuple.of(2), -98), Tuples.flatTupleOf(Tuple.of(2), -99)); | ||
237 | assertThrows(IllegalStateException.class, () -> MatcherUtils.getSingleValue(viatraTuples)); | ||
238 | } | ||
239 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java new file mode 100644 index 00000000..ca089a9d --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java | |||
@@ -0,0 +1,57 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.tests; | ||
7 | |||
8 | import org.junit.jupiter.api.function.Executable; | ||
9 | import tools.refinery.store.query.resultset.ResultSet; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.*; | ||
13 | |||
14 | import static org.hamcrest.MatcherAssert.assertThat; | ||
15 | import static org.hamcrest.Matchers.is; | ||
16 | import static org.hamcrest.Matchers.nullValue; | ||
17 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
18 | |||
19 | public final class QueryAssertions { | ||
20 | private QueryAssertions() { | ||
21 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
22 | } | ||
23 | |||
24 | public static <T> void assertNullableResults(Map<Tuple, Optional<T>> expected, ResultSet<T> resultSet) { | ||
25 | var nullableValuesMap = new LinkedHashMap<Tuple, T>(expected.size()); | ||
26 | for (var entry : expected.entrySet()) { | ||
27 | nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null)); | ||
28 | } | ||
29 | assertResults(nullableValuesMap, resultSet); | ||
30 | } | ||
31 | |||
32 | public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) { | ||
33 | var defaultValue = resultSet.getQuery().defaultValue(); | ||
34 | var filteredExpected = new LinkedHashMap<Tuple, T>(); | ||
35 | var executables = new ArrayList<Executable>(); | ||
36 | for (var entry : expected.entrySet()) { | ||
37 | var key = entry.getKey(); | ||
38 | var value = entry.getValue(); | ||
39 | if (!Objects.equals(value, defaultValue)) { | ||
40 | filteredExpected.put(key, value); | ||
41 | } | ||
42 | executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value))); | ||
43 | } | ||
44 | executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size()))); | ||
45 | |||
46 | var actual = new LinkedHashMap<Tuple, T>(); | ||
47 | var cursor = resultSet.getAll(); | ||
48 | while (cursor.move()) { | ||
49 | var key = cursor.getKey(); | ||
50 | var previous = actual.put(key, cursor.getValue()); | ||
51 | assertThat("duplicate value for key " + key, previous, nullValue()); | ||
52 | } | ||
53 | executables.add(() -> assertThat("results cursor", actual, is(filteredExpected))); | ||
54 | |||
55 | assertAll(executables); | ||
56 | } | ||
57 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java new file mode 100644 index 00000000..dc0e92c8 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.tests; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
9 | |||
10 | /** | ||
11 | * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names. | ||
12 | */ | ||
13 | class QueryBackendHint extends QueryEvaluationHint { | ||
14 | public QueryBackendHint(BackendRequirement backendRequirementType) { | ||
15 | super(null, backendRequirementType); | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public String toString() { | ||
20 | return switch (getQueryBackendRequirementType()) { | ||
21 | case UNSPECIFIED -> "default"; | ||
22 | case DEFAULT_CACHING -> "incremental"; | ||
23 | case DEFAULT_SEARCH -> "localSearch"; | ||
24 | default -> throw new IllegalStateException("Unknown BackendRequirement"); | ||
25 | }; | ||
26 | } | ||
27 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java new file mode 100644 index 00000000..d4f16da7 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.tests; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.ArgumentsSource; | ||
10 | |||
11 | import java.lang.annotation.ElementType; | ||
12 | import java.lang.annotation.Retention; | ||
13 | import java.lang.annotation.RetentionPolicy; | ||
14 | import java.lang.annotation.Target; | ||
15 | |||
16 | @ParameterizedTest(name = "backend = {0}") | ||
17 | @ArgumentsSource(QueryEvaluationHintSource.class) | ||
18 | @Target(ElementType.METHOD) | ||
19 | @Retention(RetentionPolicy.RUNTIME) | ||
20 | public @interface QueryEngineTest { | ||
21 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java new file mode 100644 index 00000000..9e75d5f3 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java | |||
@@ -0,0 +1,24 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.viatra.tests; | ||
7 | |||
8 | import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; | ||
9 | import org.junit.jupiter.api.extension.ExtensionContext; | ||
10 | import org.junit.jupiter.params.provider.Arguments; | ||
11 | import org.junit.jupiter.params.provider.ArgumentsProvider; | ||
12 | |||
13 | import java.util.stream.Stream; | ||
14 | |||
15 | public class QueryEvaluationHintSource implements ArgumentsProvider { | ||
16 | @Override | ||
17 | public Stream<? extends Arguments> provideArguments(ExtensionContext context) { | ||
18 | return Stream.of( | ||
19 | Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.UNSPECIFIED)), | ||
20 | Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_CACHING)), | ||
21 | Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) | ||
22 | ); | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-query/build.gradle.kts b/subprojects/store-query/build.gradle.kts new file mode 100644 index 00000000..4d8e2605 --- /dev/null +++ b/subprojects/store-query/build.gradle.kts | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-library") | ||
9 | id("tools.refinery.gradle.java-test-fixtures") | ||
10 | } | ||
11 | |||
12 | dependencies { | ||
13 | api(project(":refinery-store")) | ||
14 | testFixturesApi(libs.hamcrest) | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java new file mode 100644 index 00000000..916fb35c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java | |||
@@ -0,0 +1,72 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.literal.*; | ||
10 | import tools.refinery.store.query.term.*; | ||
11 | |||
12 | import java.util.List; | ||
13 | |||
14 | public interface Constraint { | ||
15 | String name(); | ||
16 | |||
17 | List<Parameter> getParameters(); | ||
18 | |||
19 | default int arity() { | ||
20 | return getParameters().size(); | ||
21 | } | ||
22 | |||
23 | default boolean invalidIndex(int i) { | ||
24 | return i < 0 || i >= arity(); | ||
25 | } | ||
26 | |||
27 | default Reduction getReduction() { | ||
28 | return Reduction.NOT_REDUCIBLE; | ||
29 | } | ||
30 | |||
31 | default boolean equals(LiteralEqualityHelper helper, Constraint other) { | ||
32 | return equals(other); | ||
33 | } | ||
34 | |||
35 | default String toReferenceString() { | ||
36 | return name(); | ||
37 | } | ||
38 | |||
39 | default CallLiteral call(CallPolarity polarity, List<Variable> arguments) { | ||
40 | return new CallLiteral(polarity, this, arguments); | ||
41 | } | ||
42 | |||
43 | default CallLiteral call(CallPolarity polarity, Variable... arguments) { | ||
44 | return call(polarity, List.of(arguments)); | ||
45 | } | ||
46 | |||
47 | default CallLiteral call(Variable... arguments) { | ||
48 | return call(CallPolarity.POSITIVE, arguments); | ||
49 | } | ||
50 | |||
51 | default CallLiteral callTransitive(NodeVariable left, NodeVariable right) { | ||
52 | return call(CallPolarity.TRANSITIVE, List.of(left, right)); | ||
53 | } | ||
54 | |||
55 | default AssignedValue<Integer> count(List<Variable> arguments) { | ||
56 | return targetVariable -> new CountLiteral(targetVariable, this, arguments); | ||
57 | } | ||
58 | |||
59 | default AssignedValue<Integer> count(Variable... arguments) { | ||
60 | return count(List.of(arguments)); | ||
61 | } | ||
62 | |||
63 | default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator, | ||
64 | List<Variable> arguments) { | ||
65 | return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments); | ||
66 | } | ||
67 | |||
68 | default <R, T> AssignedValue<R> aggregateBy(DataVariable<T> inputVariable, Aggregator<R, T> aggregator, | ||
69 | Variable... arguments) { | ||
70 | return aggregateBy(inputVariable, aggregator, List.of(arguments)); | ||
71 | } | ||
72 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java new file mode 100644 index 00000000..1fa96a07 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java | |||
@@ -0,0 +1,26 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapter; | ||
9 | import tools.refinery.store.query.dnf.AnyQuery; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.resultset.AnyResultSet; | ||
12 | import tools.refinery.store.query.resultset.ResultSet; | ||
13 | |||
14 | public interface ModelQueryAdapter extends ModelAdapter { | ||
15 | ModelQueryStoreAdapter getStoreAdapter(); | ||
16 | |||
17 | default AnyResultSet getResultSet(AnyQuery query) { | ||
18 | return getResultSet((Query<?>) query); | ||
19 | } | ||
20 | |||
21 | <T> ResultSet<T> getResultSet(Query<T> query); | ||
22 | |||
23 | boolean hasPendingChanges(); | ||
24 | |||
25 | void flushChanges(); | ||
26 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java new file mode 100644 index 00000000..c62a95b5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | import java.util.List; | ||
14 | |||
15 | @SuppressWarnings("UnusedReturnValue") | ||
16 | public interface ModelQueryBuilder extends ModelAdapterBuilder { | ||
17 | default ModelQueryBuilder queries(AnyQuery... queries) { | ||
18 | return queries(List.of(queries)); | ||
19 | } | ||
20 | |||
21 | default ModelQueryBuilder queries(Collection<? extends AnyQuery> queries) { | ||
22 | queries.forEach(this::query); | ||
23 | return this; | ||
24 | } | ||
25 | |||
26 | ModelQueryBuilder query(AnyQuery query); | ||
27 | |||
28 | @Override | ||
29 | ModelQueryStoreAdapter build(ModelStore store); | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java new file mode 100644 index 00000000..f0a950a6 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.query.dnf.AnyQuery; | ||
11 | import tools.refinery.store.query.view.AnySymbolView; | ||
12 | |||
13 | import java.util.Collection; | ||
14 | |||
15 | public interface ModelQueryStoreAdapter extends ModelStoreAdapter { | ||
16 | Collection<AnySymbolView> getSymbolViews(); | ||
17 | |||
18 | Collection<AnyQuery> getQueries(); | ||
19 | |||
20 | @Override | ||
21 | ModelQueryAdapter createModelAdapter(Model model); | ||
22 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java new file mode 100644 index 00000000..2a3e3ce0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java | |||
@@ -0,0 +1,175 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.dnf.callback.*; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.Collection; | ||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public abstract class AbstractQueryBuilder<T extends AbstractQueryBuilder<T>> { | ||
19 | protected final DnfBuilder dnfBuilder; | ||
20 | |||
21 | protected AbstractQueryBuilder(DnfBuilder dnfBuilder) { | ||
22 | this.dnfBuilder = dnfBuilder; | ||
23 | } | ||
24 | |||
25 | protected abstract T self(); | ||
26 | |||
27 | public NodeVariable parameter() { | ||
28 | return dnfBuilder.parameter(); | ||
29 | } | ||
30 | |||
31 | public NodeVariable parameter(String name) { | ||
32 | return dnfBuilder.parameter(name); | ||
33 | } | ||
34 | |||
35 | public NodeVariable parameter(ParameterDirection direction) { | ||
36 | return dnfBuilder.parameter(direction); | ||
37 | } | ||
38 | |||
39 | public NodeVariable parameter(String name, ParameterDirection direction) { | ||
40 | return dnfBuilder.parameter(name, direction); | ||
41 | } | ||
42 | |||
43 | public T parameter(NodeVariable variable) { | ||
44 | dnfBuilder.parameter(variable); | ||
45 | return self(); | ||
46 | } | ||
47 | |||
48 | public T parameter(NodeVariable variable, ParameterDirection direction) { | ||
49 | dnfBuilder.parameter(variable, direction); | ||
50 | return self(); | ||
51 | } | ||
52 | |||
53 | public T parameters(NodeVariable... variables) { | ||
54 | dnfBuilder.parameters(variables); | ||
55 | return self(); | ||
56 | } | ||
57 | |||
58 | public T parameters(List<NodeVariable> variables) { | ||
59 | dnfBuilder.parameters(variables); | ||
60 | return self(); | ||
61 | } | ||
62 | |||
63 | public T parameters(List<NodeVariable> variables, ParameterDirection direction) { | ||
64 | dnfBuilder.parameters(variables, direction); | ||
65 | return self(); | ||
66 | } | ||
67 | |||
68 | public T symbolicParameters(List<SymbolicParameter> parameters) { | ||
69 | dnfBuilder.symbolicParameters(parameters); | ||
70 | return self(); | ||
71 | } | ||
72 | |||
73 | public T functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
74 | dnfBuilder.functionalDependencies(functionalDependencies); | ||
75 | return self(); | ||
76 | } | ||
77 | |||
78 | public T functionalDependency(FunctionalDependency<Variable> functionalDependency) { | ||
79 | dnfBuilder.functionalDependency(functionalDependency); | ||
80 | return self(); | ||
81 | } | ||
82 | |||
83 | public T functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) { | ||
84 | dnfBuilder.functionalDependency(forEach, unique); | ||
85 | return self(); | ||
86 | } | ||
87 | |||
88 | public T clause(ClauseCallback0 callback) { | ||
89 | dnfBuilder.clause(callback); | ||
90 | return self(); | ||
91 | } | ||
92 | |||
93 | public T clause(ClauseCallback1Data0 callback) { | ||
94 | dnfBuilder.clause(callback); | ||
95 | return self(); | ||
96 | } | ||
97 | |||
98 | public <U1> T clause(Class<U1> type1, ClauseCallback1Data1<U1> callback) { | ||
99 | dnfBuilder.clause(type1, callback); | ||
100 | return self(); | ||
101 | } | ||
102 | |||
103 | public T clause(ClauseCallback2Data0 callback) { | ||
104 | dnfBuilder.clause(callback); | ||
105 | return self(); | ||
106 | } | ||
107 | |||
108 | public <U1> T clause(Class<U1> type1, ClauseCallback2Data1<U1> callback) { | ||
109 | dnfBuilder.clause(type1, callback); | ||
110 | return self(); | ||
111 | } | ||
112 | |||
113 | public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback2Data2<U1, U2> callback) { | ||
114 | dnfBuilder.clause(type1, type2, callback); | ||
115 | return self(); | ||
116 | } | ||
117 | |||
118 | public T clause(ClauseCallback3Data0 callback) { | ||
119 | dnfBuilder.clause(callback); | ||
120 | return self(); | ||
121 | } | ||
122 | |||
123 | public <U1> T clause(Class<U1> type1, ClauseCallback3Data1<U1> callback) { | ||
124 | dnfBuilder.clause(type1, callback); | ||
125 | return self(); | ||
126 | } | ||
127 | |||
128 | public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback3Data2<U1, U2> callback) { | ||
129 | dnfBuilder.clause(type1, type2, callback); | ||
130 | return self(); | ||
131 | } | ||
132 | |||
133 | public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3, | ||
134 | ClauseCallback3Data3<U1, U2, U3> callback) { | ||
135 | dnfBuilder.clause(type1, type2, type3, callback); | ||
136 | return self(); | ||
137 | } | ||
138 | |||
139 | public T clause(ClauseCallback4Data0 callback) { | ||
140 | dnfBuilder.clause(callback); | ||
141 | return self(); | ||
142 | } | ||
143 | |||
144 | public <U1> T clause(Class<U1> type1, ClauseCallback4Data1<U1> callback) { | ||
145 | dnfBuilder.clause(type1, callback); | ||
146 | return self(); | ||
147 | } | ||
148 | |||
149 | public <U1, U2> T clause(Class<U1> type1, Class<U2> type2, ClauseCallback4Data2<U1, U2> callback) { | ||
150 | dnfBuilder.clause(type1, type2, callback); | ||
151 | return self(); | ||
152 | } | ||
153 | |||
154 | public <U1, U2, U3> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3, | ||
155 | ClauseCallback4Data3<U1, U2, U3> callback) { | ||
156 | dnfBuilder.clause(type1, type2, type3, callback); | ||
157 | return self(); | ||
158 | } | ||
159 | |||
160 | public <U1, U2, U3, U4> T clause(Class<U1> type1, Class<U2> type2, Class<U3> type3, Class<U4> type4, | ||
161 | ClauseCallback4Data4<U1, U2, U3, U4> callback) { | ||
162 | dnfBuilder.clause(type1, type2, type3, type4, callback); | ||
163 | return self(); | ||
164 | } | ||
165 | |||
166 | public T clause(Literal... literals) { | ||
167 | dnfBuilder.clause(literals); | ||
168 | return self(); | ||
169 | } | ||
170 | |||
171 | public T clause(Collection<? extends Literal> literals) { | ||
172 | dnfBuilder.clause(literals); | ||
173 | return self(); | ||
174 | } | ||
175 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java new file mode 100644 index 00000000..5e28af68 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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 | public sealed interface AnyQuery permits Query { | ||
9 | String name(); | ||
10 | |||
11 | int arity(); | ||
12 | |||
13 | Class<?> valueType(); | ||
14 | |||
15 | Dnf getDnf(); | ||
16 | } | ||
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 new file mode 100644 index 00000000..b5e7092b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java | |||
@@ -0,0 +1,324 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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 org.jetbrains.annotations.NotNull; | ||
9 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.substitution.MapBasedSubstitution; | ||
13 | import tools.refinery.store.query.substitution.StatelessSubstitution; | ||
14 | import tools.refinery.store.query.substitution.Substitution; | ||
15 | import tools.refinery.store.query.term.NodeVariable; | ||
16 | import tools.refinery.store.query.term.ParameterDirection; | ||
17 | import tools.refinery.store.query.term.Variable; | ||
18 | |||
19 | import java.util.*; | ||
20 | import java.util.function.Function; | ||
21 | |||
22 | class ClausePostProcessor { | ||
23 | private final Map<Variable, ParameterInfo> parameters; | ||
24 | private final List<Literal> literals; | ||
25 | private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>(); | ||
26 | private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>(); | ||
27 | private List<Literal> substitutedLiterals; | ||
28 | private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>(); | ||
29 | private Set<Variable> positiveVariables; | ||
30 | private Map<Variable, Set<SortableLiteral>> variableToLiteralInputMap; | ||
31 | private PriorityQueue<SortableLiteral> literalsWithAllInputsBound; | ||
32 | private LinkedHashSet<Literal> topologicallySortedLiterals; | ||
33 | |||
34 | public ClausePostProcessor(Map<Variable, ParameterInfo> parameters, List<Literal> literals) { | ||
35 | this.parameters = parameters; | ||
36 | this.literals = literals; | ||
37 | } | ||
38 | |||
39 | public Result postProcessClause() { | ||
40 | mergeEquivalentNodeVariables(); | ||
41 | substitutedLiterals = new ArrayList<>(literals.size()); | ||
42 | keepParameterEquivalences(); | ||
43 | substituteLiterals(); | ||
44 | computeExistentiallyQuantifiedVariables(); | ||
45 | computePositiveVariables(); | ||
46 | validatePositiveRepresentatives(); | ||
47 | validatePrivateVariables(); | ||
48 | topologicallySortLiterals(); | ||
49 | var filteredLiterals = new ArrayList<Literal>(topologicallySortedLiterals.size()); | ||
50 | for (var literal : topologicallySortedLiterals) { | ||
51 | var reducedLiteral = literal.reduce(); | ||
52 | if (BooleanLiteral.FALSE.equals(reducedLiteral)) { | ||
53 | return ConstantResult.ALWAYS_FALSE; | ||
54 | } else if (!BooleanLiteral.TRUE.equals(reducedLiteral)) { | ||
55 | filteredLiterals.add(reducedLiteral); | ||
56 | } | ||
57 | } | ||
58 | if (filteredLiterals.isEmpty()) { | ||
59 | return ConstantResult.ALWAYS_TRUE; | ||
60 | } | ||
61 | var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables), | ||
62 | Collections.unmodifiableList(filteredLiterals)); | ||
63 | return new ClauseResult(clause); | ||
64 | } | ||
65 | |||
66 | private void mergeEquivalentNodeVariables() { | ||
67 | for (var literal : literals) { | ||
68 | if (isPositiveEquivalence(literal)) { | ||
69 | var equivalenceLiteral = (EquivalenceLiteral) literal; | ||
70 | mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right()); | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | private static boolean isPositiveEquivalence(Literal literal) { | ||
76 | return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive(); | ||
77 | } | ||
78 | |||
79 | private void mergeVariables(NodeVariable left, NodeVariable right) { | ||
80 | var leftRepresentative = getRepresentative(left); | ||
81 | var rightRepresentative = getRepresentative(right); | ||
82 | var leftInfo = parameters.get(leftRepresentative); | ||
83 | var rightInfo = parameters.get(rightRepresentative); | ||
84 | if (leftInfo != null && (rightInfo == null || leftInfo.index() <= rightInfo.index())) { | ||
85 | // Prefer the variable occurring earlier in the parameter list as a representative. | ||
86 | doMergeVariables(leftRepresentative, rightRepresentative); | ||
87 | } else { | ||
88 | doMergeVariables(rightRepresentative, leftRepresentative); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | private void doMergeVariables(NodeVariable parentRepresentative, NodeVariable newChildRepresentative) { | ||
93 | var parentSet = getEquivalentVariables(parentRepresentative); | ||
94 | var childSet = getEquivalentVariables(newChildRepresentative); | ||
95 | parentSet.addAll(childSet); | ||
96 | equivalencePartition.remove(newChildRepresentative); | ||
97 | for (var childEquivalentNodeVariable : childSet) { | ||
98 | representatives.put(childEquivalentNodeVariable, parentRepresentative); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | private NodeVariable getRepresentative(NodeVariable variable) { | ||
103 | return representatives.computeIfAbsent(variable, Function.identity()); | ||
104 | } | ||
105 | |||
106 | private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) { | ||
107 | var representative = getRepresentative(variable); | ||
108 | if (!representative.equals(variable)) { | ||
109 | throw new AssertionError("NodeVariable %s already has a representative %s" | ||
110 | .formatted(variable, representative)); | ||
111 | } | ||
112 | return equivalencePartition.computeIfAbsent(variable, key -> { | ||
113 | var set = new HashSet<NodeVariable>(1); | ||
114 | set.add(key); | ||
115 | return set; | ||
116 | }); | ||
117 | } | ||
118 | |||
119 | private void keepParameterEquivalences() { | ||
120 | for (var pair : representatives.entrySet()) { | ||
121 | var left = pair.getKey(); | ||
122 | var right = pair.getValue(); | ||
123 | if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) { | ||
124 | substitutedLiterals.add(left.isEquivalent(right)); | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | private void substituteLiterals() { | ||
130 | Substitution substitution; | ||
131 | if (representatives.isEmpty()) { | ||
132 | substitution = null; | ||
133 | } else { | ||
134 | substitution = new MapBasedSubstitution(Collections.unmodifiableMap(representatives), | ||
135 | StatelessSubstitution.IDENTITY); | ||
136 | } | ||
137 | for (var literal : literals) { | ||
138 | if (isPositiveEquivalence(literal)) { | ||
139 | // We already retained all equivalences that cannot be replaced with substitutions in | ||
140 | // {@link#keepParameterEquivalences()}. | ||
141 | continue; | ||
142 | } | ||
143 | var substitutedLiteral = substitution == null ? literal : literal.substitute(substitution); | ||
144 | substitutedLiterals.add(substitutedLiteral); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | private void computeExistentiallyQuantifiedVariables() { | ||
149 | for (var literal : substitutedLiterals) { | ||
150 | for (var variable : literal.getOutputVariables()) { | ||
151 | boolean added = existentiallyQuantifiedVariables.add(variable); | ||
152 | if (!variable.isUnifiable()) { | ||
153 | var parameterInfo = parameters.get(variable); | ||
154 | if (parameterInfo != null && parameterInfo.direction() == ParameterDirection.IN) { | ||
155 | throw new IllegalArgumentException("Trying to bind %s parameter %s" | ||
156 | .formatted(ParameterDirection.IN, variable)); | ||
157 | } | ||
158 | if (!added) { | ||
159 | throw new IllegalArgumentException("Variable %s has multiple assigned values" | ||
160 | .formatted(variable)); | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | |||
167 | private void computePositiveVariables() { | ||
168 | positiveVariables = new LinkedHashSet<>(); | ||
169 | for (var pair : parameters.entrySet()) { | ||
170 | var variable = pair.getKey(); | ||
171 | if (pair.getValue().direction() == ParameterDirection.IN) { | ||
172 | // Inputs count as positive, because they are already bound when we evaluate literals. | ||
173 | positiveVariables.add(variable); | ||
174 | } else if (!existentiallyQuantifiedVariables.contains(variable)) { | ||
175 | throw new IllegalArgumentException("Unbound %s parameter %s" | ||
176 | .formatted(ParameterDirection.OUT, variable)); | ||
177 | } | ||
178 | } | ||
179 | positiveVariables.addAll(existentiallyQuantifiedVariables); | ||
180 | } | ||
181 | |||
182 | private void validatePositiveRepresentatives() { | ||
183 | for (var pair : equivalencePartition.entrySet()) { | ||
184 | var representative = pair.getKey(); | ||
185 | if (!positiveVariables.contains(representative)) { | ||
186 | var variableSet = pair.getValue(); | ||
187 | throw new IllegalArgumentException("Variables %s were merged by equivalence but are not bound" | ||
188 | .formatted(variableSet)); | ||
189 | } | ||
190 | } | ||
191 | } | ||
192 | |||
193 | private void validatePrivateVariables() { | ||
194 | var negativeVariablesMap = new HashMap<Variable, Literal>(); | ||
195 | for (var literal : substitutedLiterals) { | ||
196 | for (var variable : literal.getPrivateVariables(positiveVariables)) { | ||
197 | var oldLiteral = negativeVariablesMap.put(variable, literal); | ||
198 | if (oldLiteral != null) { | ||
199 | throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s" | ||
200 | .formatted(variable, oldLiteral, literal)); | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | private void topologicallySortLiterals() { | ||
207 | topologicallySortedLiterals = new LinkedHashSet<>(substitutedLiterals.size()); | ||
208 | variableToLiteralInputMap = new HashMap<>(); | ||
209 | literalsWithAllInputsBound = new PriorityQueue<>(); | ||
210 | int size = substitutedLiterals.size(); | ||
211 | for (int i = 0; i < size; i++) { | ||
212 | var literal = substitutedLiterals.get(i); | ||
213 | var sortableLiteral = new SortableLiteral(i, literal); | ||
214 | sortableLiteral.enqueue(); | ||
215 | } | ||
216 | while (!literalsWithAllInputsBound.isEmpty()) { | ||
217 | var variable = literalsWithAllInputsBound.remove(); | ||
218 | variable.addToSortedLiterals(); | ||
219 | } | ||
220 | if (!variableToLiteralInputMap.isEmpty()) { | ||
221 | throw new IllegalArgumentException("Unbound input variables %s" | ||
222 | .formatted(variableToLiteralInputMap.keySet())); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | private class SortableLiteral implements Comparable<SortableLiteral> { | ||
227 | private final int index; | ||
228 | private final Literal literal; | ||
229 | private final Set<Variable> remainingInputs; | ||
230 | |||
231 | private SortableLiteral(int index, Literal literal) { | ||
232 | this.index = index; | ||
233 | this.literal = literal; | ||
234 | remainingInputs = new HashSet<>(literal.getInputVariables(positiveVariables)); | ||
235 | for (var pair : parameters.entrySet()) { | ||
236 | if (pair.getValue().direction() == ParameterDirection.IN) { | ||
237 | remainingInputs.remove(pair.getKey()); | ||
238 | } | ||
239 | } | ||
240 | } | ||
241 | |||
242 | public void enqueue() { | ||
243 | if (allInputsBound()) { | ||
244 | addToAllInputsBoundQueue(); | ||
245 | } else { | ||
246 | addToVariableToLiteralInputMap(); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | private void bindVariable(Variable input) { | ||
251 | if (!remainingInputs.remove(input)) { | ||
252 | throw new AssertionError("Already processed input %s of literal %s".formatted(input, literal)); | ||
253 | } | ||
254 | if (allInputsBound()) { | ||
255 | addToAllInputsBoundQueue(); | ||
256 | } | ||
257 | } | ||
258 | |||
259 | private boolean allInputsBound() { | ||
260 | return remainingInputs.isEmpty(); | ||
261 | } | ||
262 | |||
263 | private void addToVariableToLiteralInputMap() { | ||
264 | for (var inputVariable : remainingInputs) { | ||
265 | var literalSetForInput = variableToLiteralInputMap.computeIfAbsent( | ||
266 | inputVariable, key -> new HashSet<>()); | ||
267 | literalSetForInput.add(this); | ||
268 | } | ||
269 | } | ||
270 | |||
271 | private void addToAllInputsBoundQueue() { | ||
272 | literalsWithAllInputsBound.add(this); | ||
273 | } | ||
274 | |||
275 | public void addToSortedLiterals() { | ||
276 | if (!allInputsBound()) { | ||
277 | throw new AssertionError("Inputs %s of %s are not yet bound".formatted(remainingInputs, literal)); | ||
278 | } | ||
279 | // Add literal if we haven't yet added a duplicate of this literal. | ||
280 | topologicallySortedLiterals.add(literal); | ||
281 | for (var variable : literal.getOutputVariables()) { | ||
282 | var literalSetForInput = variableToLiteralInputMap.remove(variable); | ||
283 | if (literalSetForInput == null) { | ||
284 | continue; | ||
285 | } | ||
286 | for (var targetSortableLiteral : literalSetForInput) { | ||
287 | targetSortableLiteral.bindVariable(variable); | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | @Override | ||
293 | public int compareTo(@NotNull ClausePostProcessor.SortableLiteral other) { | ||
294 | return Integer.compare(index, other.index); | ||
295 | } | ||
296 | |||
297 | @Override | ||
298 | public boolean equals(Object o) { | ||
299 | if (this == o) return true; | ||
300 | if (o == null || getClass() != o.getClass()) return false; | ||
301 | SortableLiteral that = (SortableLiteral) o; | ||
302 | return index == that.index && Objects.equals(literal, that.literal); | ||
303 | } | ||
304 | |||
305 | @Override | ||
306 | public int hashCode() { | ||
307 | return Objects.hash(index, literal); | ||
308 | } | ||
309 | } | ||
310 | |||
311 | public sealed interface Result permits ClauseResult, ConstantResult { | ||
312 | } | ||
313 | |||
314 | public record ClauseResult(DnfClause clause) implements Result { | ||
315 | } | ||
316 | |||
317 | public enum ConstantResult implements Result { | ||
318 | ALWAYS_TRUE, | ||
319 | ALWAYS_FALSE | ||
320 | } | ||
321 | |||
322 | public record ParameterInfo(ParameterDirection direction, int index) { | ||
323 | } | ||
324 | } | ||
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 new file mode 100644 index 00000000..50b245f7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java | |||
@@ -0,0 +1,213 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.Constraint; | ||
9 | import tools.refinery.store.query.literal.Reduction; | ||
10 | import tools.refinery.store.query.equality.DnfEqualityChecker; | ||
11 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
12 | import tools.refinery.store.query.term.Parameter; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.Collection; | ||
16 | import java.util.Collections; | ||
17 | import java.util.List; | ||
18 | import java.util.Set; | ||
19 | import java.util.function.Consumer; | ||
20 | import java.util.stream.Collectors; | ||
21 | |||
22 | public final class Dnf implements Constraint { | ||
23 | private static final String INDENTATION = " "; | ||
24 | |||
25 | private final String name; | ||
26 | private final String uniqueName; | ||
27 | private final List<SymbolicParameter> symbolicParameters; | ||
28 | private final List<FunctionalDependency<Variable>> functionalDependencies; | ||
29 | private final List<DnfClause> clauses; | ||
30 | |||
31 | Dnf(String name, List<SymbolicParameter> symbolicParameters, | ||
32 | List<FunctionalDependency<Variable>> functionalDependencies, List<DnfClause> clauses) { | ||
33 | validateFunctionalDependencies(symbolicParameters, functionalDependencies); | ||
34 | this.name = name; | ||
35 | this.uniqueName = DnfUtils.generateUniqueName(name); | ||
36 | this.symbolicParameters = symbolicParameters; | ||
37 | this.functionalDependencies = functionalDependencies; | ||
38 | this.clauses = clauses; | ||
39 | } | ||
40 | |||
41 | private static void validateFunctionalDependencies( | ||
42 | Collection<SymbolicParameter> symbolicParameters, | ||
43 | Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
44 | var parameterSet = symbolicParameters.stream().map(SymbolicParameter::getVariable).collect(Collectors.toSet()); | ||
45 | for (var functionalDependency : functionalDependencies) { | ||
46 | validateParameters(symbolicParameters, parameterSet, functionalDependency.forEach(), functionalDependency); | ||
47 | validateParameters(symbolicParameters, parameterSet, functionalDependency.unique(), functionalDependency); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | private static void validateParameters(Collection<SymbolicParameter> symbolicParameters, | ||
52 | Set<Variable> parameterSet, Collection<Variable> toValidate, | ||
53 | FunctionalDependency<Variable> functionalDependency) { | ||
54 | for (var variable : toValidate) { | ||
55 | if (!parameterSet.contains(variable)) { | ||
56 | throw new IllegalArgumentException( | ||
57 | "Variable %s of functional dependency %s does not appear in the parameter list %s" | ||
58 | .formatted(variable, functionalDependency, symbolicParameters)); | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public String name() { | ||
65 | return name == null ? uniqueName : name; | ||
66 | } | ||
67 | |||
68 | public boolean isExplicitlyNamed() { | ||
69 | return name == null; | ||
70 | } | ||
71 | |||
72 | public String getUniqueName() { | ||
73 | return uniqueName; | ||
74 | } | ||
75 | |||
76 | public List<SymbolicParameter> getSymbolicParameters() { | ||
77 | return symbolicParameters; | ||
78 | } | ||
79 | |||
80 | public List<Parameter> getParameters() { | ||
81 | return Collections.unmodifiableList(symbolicParameters); | ||
82 | } | ||
83 | |||
84 | public List<FunctionalDependency<Variable>> getFunctionalDependencies() { | ||
85 | return functionalDependencies; | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public int arity() { | ||
90 | return symbolicParameters.size(); | ||
91 | } | ||
92 | |||
93 | public List<DnfClause> getClauses() { | ||
94 | return clauses; | ||
95 | } | ||
96 | |||
97 | public RelationalQuery asRelation() { | ||
98 | return new RelationalQuery(this); | ||
99 | } | ||
100 | |||
101 | public <T> FunctionalQuery<T> asFunction(Class<T> type) { | ||
102 | return new FunctionalQuery<>(this, type); | ||
103 | } | ||
104 | |||
105 | @Override | ||
106 | public Reduction getReduction() { | ||
107 | if (clauses.isEmpty()) { | ||
108 | return Reduction.ALWAYS_FALSE; | ||
109 | } | ||
110 | for (var clause : clauses) { | ||
111 | if (clause.literals().isEmpty()) { | ||
112 | return Reduction.ALWAYS_TRUE; | ||
113 | } | ||
114 | } | ||
115 | return Reduction.NOT_REDUCIBLE; | ||
116 | } | ||
117 | |||
118 | public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) { | ||
119 | if (arity() != other.arity()) { | ||
120 | return false; | ||
121 | } | ||
122 | for (int i = 0; i < arity(); i++) { | ||
123 | if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) { | ||
124 | return false; | ||
125 | } | ||
126 | } | ||
127 | int numClauses = clauses.size(); | ||
128 | if (numClauses != other.clauses.size()) { | ||
129 | return false; | ||
130 | } | ||
131 | for (int i = 0; i < numClauses; i++) { | ||
132 | var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, symbolicParameters, | ||
133 | other.symbolicParameters); | ||
134 | if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { | ||
135 | return false; | ||
136 | } | ||
137 | } | ||
138 | return true; | ||
139 | } | ||
140 | |||
141 | @Override | ||
142 | public boolean equals(LiteralEqualityHelper helper, Constraint other) { | ||
143 | if (other instanceof Dnf otherDnf) { | ||
144 | return helper.dnfEqual(this, otherDnf); | ||
145 | } | ||
146 | return false; | ||
147 | } | ||
148 | |||
149 | @Override | ||
150 | public String toString() { | ||
151 | return "%s/%d".formatted(name(), arity()); | ||
152 | } | ||
153 | |||
154 | @Override | ||
155 | public String toReferenceString() { | ||
156 | return "@Dnf " + name(); | ||
157 | } | ||
158 | |||
159 | public String toDefinitionString() { | ||
160 | var builder = new StringBuilder(); | ||
161 | builder.append("pred ").append(name()).append("("); | ||
162 | var parameterIterator = symbolicParameters.iterator(); | ||
163 | if (parameterIterator.hasNext()) { | ||
164 | builder.append(parameterIterator.next()); | ||
165 | while (parameterIterator.hasNext()) { | ||
166 | builder.append(", ").append(parameterIterator.next()); | ||
167 | } | ||
168 | } | ||
169 | builder.append(") <->"); | ||
170 | var clauseIterator = clauses.iterator(); | ||
171 | if (clauseIterator.hasNext()) { | ||
172 | appendClause(clauseIterator.next(), builder); | ||
173 | while (clauseIterator.hasNext()) { | ||
174 | builder.append("\n;"); | ||
175 | appendClause(clauseIterator.next(), builder); | ||
176 | } | ||
177 | } else { | ||
178 | builder.append("\n").append(INDENTATION).append("<no clauses>"); | ||
179 | } | ||
180 | builder.append(".\n"); | ||
181 | return builder.toString(); | ||
182 | } | ||
183 | |||
184 | private static void appendClause(DnfClause clause, StringBuilder builder) { | ||
185 | var iterator = clause.literals().iterator(); | ||
186 | if (!iterator.hasNext()) { | ||
187 | builder.append("\n").append(INDENTATION).append("<empty>"); | ||
188 | return; | ||
189 | } | ||
190 | builder.append("\n").append(INDENTATION).append(iterator.next()); | ||
191 | while (iterator.hasNext()) { | ||
192 | builder.append(",\n").append(INDENTATION).append(iterator.next()); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | public static DnfBuilder builder() { | ||
197 | return builder(null); | ||
198 | } | ||
199 | |||
200 | public static DnfBuilder builder(String name) { | ||
201 | return new DnfBuilder(name); | ||
202 | } | ||
203 | |||
204 | public static Dnf of(Consumer<DnfBuilder> callback) { | ||
205 | return of(null, callback); | ||
206 | } | ||
207 | |||
208 | public static Dnf of(String name, Consumer<DnfBuilder> callback) { | ||
209 | var builder = builder(name); | ||
210 | callback.accept(builder); | ||
211 | return builder.build(); | ||
212 | } | ||
213 | } | ||
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 new file mode 100644 index 00000000..8e38ca6b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java | |||
@@ -0,0 +1,262 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.dnf.callback.*; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.DataVariable; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.*; | ||
16 | |||
17 | @SuppressWarnings("UnusedReturnValue") | ||
18 | public final class DnfBuilder { | ||
19 | private final String name; | ||
20 | private final Set<Variable> parameterVariables = new LinkedHashSet<>(); | ||
21 | private final List<SymbolicParameter> parameters = new ArrayList<>(); | ||
22 | private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>(); | ||
23 | private final List<List<Literal>> clauses = new ArrayList<>(); | ||
24 | |||
25 | DnfBuilder(String name) { | ||
26 | this.name = name; | ||
27 | } | ||
28 | |||
29 | public NodeVariable parameter() { | ||
30 | return parameter((String) null); | ||
31 | } | ||
32 | |||
33 | public NodeVariable parameter(String name) { | ||
34 | return parameter(name, ParameterDirection.OUT); | ||
35 | } | ||
36 | |||
37 | public NodeVariable parameter(ParameterDirection direction) { | ||
38 | return parameter((String) null, direction); | ||
39 | } | ||
40 | |||
41 | public NodeVariable parameter(String name, ParameterDirection direction) { | ||
42 | var variable = Variable.of(name); | ||
43 | parameter(variable, direction); | ||
44 | return variable; | ||
45 | } | ||
46 | |||
47 | public <T> DataVariable<T> parameter(Class<T> type) { | ||
48 | return parameter(null, type); | ||
49 | } | ||
50 | |||
51 | public <T> DataVariable<T> parameter(String name, Class<T> type) { | ||
52 | return parameter(name, type, ParameterDirection.OUT); | ||
53 | } | ||
54 | |||
55 | public <T> DataVariable<T> parameter(Class<T> type, ParameterDirection direction) { | ||
56 | return parameter(null, type, direction); | ||
57 | } | ||
58 | |||
59 | public <T> DataVariable<T> parameter(String name, Class<T> type, ParameterDirection direction) { | ||
60 | var variable = Variable.of(name, type); | ||
61 | parameter(variable, direction); | ||
62 | return variable; | ||
63 | } | ||
64 | |||
65 | public DnfBuilder parameter(Variable variable) { | ||
66 | return parameter(variable, ParameterDirection.OUT); | ||
67 | } | ||
68 | |||
69 | public DnfBuilder parameter(Variable variable, ParameterDirection direction) { | ||
70 | return symbolicParameter(new SymbolicParameter(variable, direction)); | ||
71 | } | ||
72 | |||
73 | public DnfBuilder parameters(Variable... variables) { | ||
74 | return parameters(List.of(variables)); | ||
75 | } | ||
76 | |||
77 | public DnfBuilder parameters(Collection<? extends Variable> variables) { | ||
78 | return parameters(variables, ParameterDirection.OUT); | ||
79 | } | ||
80 | |||
81 | public DnfBuilder parameters(Collection<? extends Variable> variables, ParameterDirection direction) { | ||
82 | for (var variable : variables) { | ||
83 | parameter(variable, direction); | ||
84 | } | ||
85 | return this; | ||
86 | } | ||
87 | |||
88 | public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { | ||
89 | var variable = symbolicParameter.getVariable(); | ||
90 | if (!parameterVariables.add(variable)) { | ||
91 | throw new IllegalArgumentException("Variable %s is already on the parameter list %s" | ||
92 | .formatted(variable, parameters)); | ||
93 | } | ||
94 | parameters.add(symbolicParameter); | ||
95 | return this; | ||
96 | } | ||
97 | |||
98 | public DnfBuilder symbolicParameters(SymbolicParameter... symbolicParameters) { | ||
99 | return symbolicParameters(List.of(symbolicParameters)); | ||
100 | } | ||
101 | |||
102 | public DnfBuilder symbolicParameters(Collection<SymbolicParameter> symbolicParameters) { | ||
103 | for (var symbolicParameter : symbolicParameters) { | ||
104 | symbolicParameter(symbolicParameter); | ||
105 | } | ||
106 | return this; | ||
107 | } | ||
108 | |||
109 | public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
110 | this.functionalDependencies.addAll(functionalDependencies); | ||
111 | return this; | ||
112 | } | ||
113 | |||
114 | public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) { | ||
115 | functionalDependencies.add(functionalDependency); | ||
116 | return this; | ||
117 | } | ||
118 | |||
119 | public DnfBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) { | ||
120 | return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique))); | ||
121 | } | ||
122 | |||
123 | public DnfBuilder clause(ClauseCallback0 callback) { | ||
124 | return clause(callback.toLiterals()); | ||
125 | } | ||
126 | |||
127 | public DnfBuilder clause(ClauseCallback1Data0 callback) { | ||
128 | return clause(callback.toLiterals(Variable.of("v1"))); | ||
129 | } | ||
130 | |||
131 | public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) { | ||
132 | return clause(callback.toLiterals(Variable.of("v1", type1))); | ||
133 | } | ||
134 | |||
135 | public DnfBuilder clause(ClauseCallback2Data0 callback) { | ||
136 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"))); | ||
137 | } | ||
138 | |||
139 | public <T> DnfBuilder clause(Class<T> type1, ClauseCallback2Data1<T> callback) { | ||
140 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1))); | ||
141 | } | ||
142 | |||
143 | public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback2Data2<T1, T2> callback) { | ||
144 | return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2))); | ||
145 | } | ||
146 | |||
147 | public DnfBuilder clause(ClauseCallback3Data0 callback) { | ||
148 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"))); | ||
149 | } | ||
150 | |||
151 | public <T> DnfBuilder clause(Class<T> type1, ClauseCallback3Data1<T> callback) { | ||
152 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1))); | ||
153 | } | ||
154 | |||
155 | public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback3Data2<T1, T2> callback) { | ||
156 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2))); | ||
157 | } | ||
158 | |||
159 | public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3, | ||
160 | ClauseCallback3Data3<T1, T2, T3> callback) { | ||
161 | return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2), | ||
162 | Variable.of("d3", type3))); | ||
163 | } | ||
164 | |||
165 | public DnfBuilder clause(ClauseCallback4Data0 callback) { | ||
166 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("v4"))); | ||
167 | } | ||
168 | |||
169 | public <T> DnfBuilder clause(Class<T> type1, ClauseCallback4Data1<T> callback) { | ||
170 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("d1", | ||
171 | type1))); | ||
172 | } | ||
173 | |||
174 | public <T1, T2> DnfBuilder clause(Class<T1> type1, Class<T2> type2, ClauseCallback4Data2<T1, T2> callback) { | ||
175 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("d1", type1), | ||
176 | Variable.of("d2", type2))); | ||
177 | } | ||
178 | |||
179 | public <T1, T2, T3> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3, | ||
180 | ClauseCallback4Data3<T1, T2, T3> callback) { | ||
181 | return clause(callback.toLiterals(Variable.of("v1"), Variable.of("d1", type1), Variable.of("d2", type2), | ||
182 | Variable.of("d3", type3))); | ||
183 | } | ||
184 | |||
185 | public <T1, T2, T3, T4> DnfBuilder clause(Class<T1> type1, Class<T2> type2, Class<T3> type3, Class<T4> type4, | ||
186 | ClauseCallback4Data4<T1, T2, T3, T4> callback) { | ||
187 | return clause(callback.toLiterals(Variable.of("d1", type1), Variable.of("d2", type2), | ||
188 | Variable.of("d3", type3), Variable.of("d4", type4))); | ||
189 | } | ||
190 | |||
191 | public DnfBuilder clause(Literal... literals) { | ||
192 | clause(List.of(literals)); | ||
193 | return this; | ||
194 | } | ||
195 | |||
196 | public DnfBuilder clause(Collection<? extends Literal> literals) { | ||
197 | clauses.add(List.copyOf(literals)); | ||
198 | return this; | ||
199 | } | ||
200 | |||
201 | <T> void output(DataVariable<T> outputVariable) { | ||
202 | // Copy parameter variables to exclude the newly added {@code outputVariable}. | ||
203 | var fromParameters = Set.copyOf(parameterVariables); | ||
204 | parameter(outputVariable, ParameterDirection.OUT); | ||
205 | functionalDependency(fromParameters, Set.of(outputVariable)); | ||
206 | } | ||
207 | |||
208 | public Dnf build() { | ||
209 | var postProcessedClauses = postProcessClauses(); | ||
210 | return new Dnf(name, Collections.unmodifiableList(parameters), | ||
211 | Collections.unmodifiableList(functionalDependencies), | ||
212 | Collections.unmodifiableList(postProcessedClauses)); | ||
213 | } | ||
214 | |||
215 | private List<DnfClause> postProcessClauses() { | ||
216 | var parameterInfoMap = getParameterInfoMap(); | ||
217 | var postProcessedClauses = new ArrayList<DnfClause>(clauses.size()); | ||
218 | for (var literals : clauses) { | ||
219 | var postProcessor = new ClausePostProcessor(parameterInfoMap, literals); | ||
220 | var result = postProcessor.postProcessClause(); | ||
221 | if (result instanceof ClausePostProcessor.ClauseResult clauseResult) { | ||
222 | postProcessedClauses.add(clauseResult.clause()); | ||
223 | } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) { | ||
224 | switch (constantResult) { | ||
225 | case ALWAYS_TRUE -> { | ||
226 | var inputVariables = getInputVariables(); | ||
227 | return List.of(new DnfClause(inputVariables, List.of())); | ||
228 | } | ||
229 | case ALWAYS_FALSE -> { | ||
230 | // Skip this clause because it can never match. | ||
231 | } | ||
232 | default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " + | ||
233 | constantResult); | ||
234 | } | ||
235 | } else { | ||
236 | throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result); | ||
237 | } | ||
238 | } | ||
239 | return postProcessedClauses; | ||
240 | } | ||
241 | |||
242 | private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() { | ||
243 | var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>(); | ||
244 | int arity = parameters.size(); | ||
245 | for (int i = 0; i < arity; i++) { | ||
246 | var parameter = parameters.get(i); | ||
247 | mutableParameterInfoMap.put(parameter.getVariable(), | ||
248 | new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i)); | ||
249 | } | ||
250 | return Collections.unmodifiableMap(mutableParameterInfoMap); | ||
251 | } | ||
252 | |||
253 | private Set<Variable> getInputVariables() { | ||
254 | var inputParameters = new LinkedHashSet<Variable>(); | ||
255 | for (var parameter : parameters) { | ||
256 | if (parameter.getDirection() == ParameterDirection.IN) { | ||
257 | inputParameters.add(parameter.getVariable()); | ||
258 | } | ||
259 | } | ||
260 | return Collections.unmodifiableSet(inputParameters); | ||
261 | } | ||
262 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java new file mode 100644 index 00000000..fdd0d47c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java | |||
@@ -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 | package tools.refinery.store.query.dnf; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.List; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public record DnfClause(Set<Variable> positiveVariables, List<Literal> literals) { | ||
16 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) { | ||
17 | int size = literals.size(); | ||
18 | if (size != other.literals.size()) { | ||
19 | return false; | ||
20 | } | ||
21 | for (int i = 0; i < size; i++) { | ||
22 | if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) { | ||
23 | return false; | ||
24 | } | ||
25 | } | ||
26 | return true; | ||
27 | } | ||
28 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNFUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java index 0ef77d49..65ab3634 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/DNFUtils.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java | |||
@@ -1,9 +1,14 @@ | |||
1 | package tools.refinery.store.query; | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
2 | 7 | ||
3 | import java.util.UUID; | 8 | import java.util.UUID; |
4 | 9 | ||
5 | public final class DNFUtils { | 10 | public final class DnfUtils { |
6 | private DNFUtils() { | 11 | private DnfUtils() { |
7 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | 12 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); |
8 | } | 13 | } |
9 | 14 | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java index 63a81713..b00b2cb7 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/FunctionalDependency.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java | |||
@@ -1,4 +1,9 @@ | |||
1 | package tools.refinery.store.query; | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf; | ||
2 | 7 | ||
3 | import java.util.HashSet; | 8 | import java.util.HashSet; |
4 | import java.util.Set; | 9 | import java.util.Set; |
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 new file mode 100644 index 00000000..5a32b1ba --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java | |||
@@ -0,0 +1,99 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.literal.CallPolarity; | ||
9 | import tools.refinery.store.query.term.Aggregator; | ||
10 | import tools.refinery.store.query.term.AssignedValue; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.ArrayList; | ||
15 | import java.util.List; | ||
16 | import java.util.Objects; | ||
17 | |||
18 | public final class FunctionalQuery<T> extends Query<T> { | ||
19 | private final Class<T> type; | ||
20 | |||
21 | FunctionalQuery(Dnf dnf, Class<T> type) { | ||
22 | super(dnf); | ||
23 | var parameters = dnf.getSymbolicParameters(); | ||
24 | int outputIndex = dnf.arity() - 1; | ||
25 | for (int i = 0; i < outputIndex; i++) { | ||
26 | var parameter = parameters.get(i); | ||
27 | var parameterType = parameter.tryGetType(); | ||
28 | if (parameterType.isPresent()) { | ||
29 | throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" | ||
30 | .formatted(parameter, dnf, parameterType.get().getName())); | ||
31 | } | ||
32 | } | ||
33 | var outputParameter = parameters.get(outputIndex); | ||
34 | var outputParameterType = outputParameter.tryGetType(); | ||
35 | 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 | outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); | ||
38 | } | ||
39 | this.type = type; | ||
40 | } | ||
41 | |||
42 | @Override | ||
43 | public int arity() { | ||
44 | return getDnf().arity() - 1; | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public Class<T> valueType() { | ||
49 | return type; | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public T defaultValue() { | ||
54 | return null; | ||
55 | } | ||
56 | |||
57 | public AssignedValue<T> call(List<NodeVariable> arguments) { | ||
58 | return targetVariable -> { | ||
59 | var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1); | ||
60 | argumentsWithTarget.addAll(arguments); | ||
61 | argumentsWithTarget.add(targetVariable); | ||
62 | return getDnf().call(CallPolarity.POSITIVE, argumentsWithTarget); | ||
63 | }; | ||
64 | } | ||
65 | |||
66 | public AssignedValue<T> call(NodeVariable... arguments) { | ||
67 | return call(List.of(arguments)); | ||
68 | } | ||
69 | |||
70 | public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) { | ||
71 | return targetVariable -> { | ||
72 | var placeholderVariable = Variable.of(type); | ||
73 | var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1); | ||
74 | argumentsWithPlaceholder.addAll(arguments); | ||
75 | argumentsWithPlaceholder.add(placeholderVariable); | ||
76 | return getDnf() | ||
77 | .aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder) | ||
78 | .toLiteral(targetVariable); | ||
79 | }; | ||
80 | } | ||
81 | |||
82 | public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) { | ||
83 | return aggregate(aggregator, List.of(arguments)); | ||
84 | } | ||
85 | |||
86 | @Override | ||
87 | public boolean equals(Object o) { | ||
88 | if (this == o) return true; | ||
89 | if (o == null || getClass() != o.getClass()) return false; | ||
90 | if (!super.equals(o)) return false; | ||
91 | FunctionalQuery<?> that = (FunctionalQuery<?>) o; | ||
92 | return Objects.equals(type, that.type); | ||
93 | } | ||
94 | |||
95 | @Override | ||
96 | public int hashCode() { | ||
97 | return Objects.hash(super.hashCode(), type); | ||
98 | } | ||
99 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java new file mode 100644 index 00000000..d1cd7ba8 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java | |||
@@ -0,0 +1,29 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.term.DataVariable; | ||
9 | |||
10 | public final class FunctionalQueryBuilder<T> extends AbstractQueryBuilder<FunctionalQueryBuilder<T>> { | ||
11 | private final DataVariable<T> outputVariable; | ||
12 | private final Class<T> type; | ||
13 | |||
14 | FunctionalQueryBuilder(DataVariable<T> outputVariable, DnfBuilder dnfBuilder, Class<T> type) { | ||
15 | super(dnfBuilder); | ||
16 | this.outputVariable = outputVariable; | ||
17 | this.type = type; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | protected FunctionalQueryBuilder<T> self() { | ||
22 | return this; | ||
23 | } | ||
24 | |||
25 | public FunctionalQuery<T> build() { | ||
26 | dnfBuilder.output(outputVariable); | ||
27 | return dnfBuilder.build().asFunction(type); | ||
28 | } | ||
29 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java new file mode 100644 index 00000000..aaa52ce6 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java | |||
@@ -0,0 +1,179 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.dnf.callback.*; | ||
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuery, RelationalQuery { | ||
15 | private static final String OUTPUT_VARIABLE_NAME = "output"; | ||
16 | |||
17 | private final Dnf dnf; | ||
18 | |||
19 | protected Query(Dnf dnf) { | ||
20 | for (var parameter : dnf.getSymbolicParameters()) { | ||
21 | if (parameter.getDirection() != ParameterDirection.OUT) { | ||
22 | throw new IllegalArgumentException("Query parameter %s with direction %s is not allowed" | ||
23 | .formatted(parameter.getVariable(), parameter.getDirection())); | ||
24 | } | ||
25 | } | ||
26 | this.dnf = dnf; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public String name() { | ||
31 | return dnf.name(); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Dnf getDnf() { | ||
36 | return dnf; | ||
37 | } | ||
38 | |||
39 | // Allow redeclaration of the method with refined return type. | ||
40 | @SuppressWarnings("squid:S3038") | ||
41 | @Override | ||
42 | public abstract Class<T> valueType(); | ||
43 | |||
44 | public abstract T defaultValue(); | ||
45 | |||
46 | @Override | ||
47 | public boolean equals(Object o) { | ||
48 | if (this == o) return true; | ||
49 | if (o == null || getClass() != o.getClass()) return false; | ||
50 | Query<?> that = (Query<?>) o; | ||
51 | return Objects.equals(dnf, that.dnf); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public int hashCode() { | ||
56 | return Objects.hash(dnf); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public String toString() { | ||
61 | return dnf.toString(); | ||
62 | } | ||
63 | |||
64 | public static QueryBuilder builder() { | ||
65 | return builder(null); | ||
66 | } | ||
67 | |||
68 | public static QueryBuilder builder(String name) { | ||
69 | return new QueryBuilder(name); | ||
70 | } | ||
71 | |||
72 | public static RelationalQuery of(QueryCallback0 callback) { | ||
73 | return of(null, callback); | ||
74 | } | ||
75 | |||
76 | public static RelationalQuery of(String name, QueryCallback0 callback) { | ||
77 | var builder = builder(name); | ||
78 | callback.accept(builder); | ||
79 | return builder.build(); | ||
80 | } | ||
81 | |||
82 | public static RelationalQuery of(QueryCallback1 callback) { | ||
83 | return of(null, callback); | ||
84 | } | ||
85 | |||
86 | public static RelationalQuery of(String name, QueryCallback1 callback) { | ||
87 | var builder = builder(name); | ||
88 | callback.accept(builder, builder.parameter("p1")); | ||
89 | return builder.build(); | ||
90 | } | ||
91 | |||
92 | public static RelationalQuery of(QueryCallback2 callback) { | ||
93 | return of(null, callback); | ||
94 | } | ||
95 | |||
96 | public static RelationalQuery of(String name, QueryCallback2 callback) { | ||
97 | var builder = builder(name); | ||
98 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2")); | ||
99 | return builder.build(); | ||
100 | } | ||
101 | |||
102 | public static RelationalQuery of(QueryCallback3 callback) { | ||
103 | return of(null, callback); | ||
104 | } | ||
105 | |||
106 | public static RelationalQuery of(String name, QueryCallback3 callback) { | ||
107 | var builder = builder(name); | ||
108 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3")); | ||
109 | return builder.build(); | ||
110 | } | ||
111 | |||
112 | public static RelationalQuery of(QueryCallback4 callback) { | ||
113 | return of(null, callback); | ||
114 | } | ||
115 | |||
116 | public static RelationalQuery of(String name, QueryCallback4 callback) { | ||
117 | var builder = builder(name); | ||
118 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), | ||
119 | builder.parameter("p4")); | ||
120 | return builder.build(); | ||
121 | } | ||
122 | |||
123 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback0<T> callback) { | ||
124 | return of(null, type, callback); | ||
125 | } | ||
126 | |||
127 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback0<T> callback) { | ||
128 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | ||
129 | var builder = builder(name).output(outputVariable); | ||
130 | callback.accept(builder, outputVariable); | ||
131 | return builder.build(); | ||
132 | } | ||
133 | |||
134 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback1<T> callback) { | ||
135 | return of(null, type, callback); | ||
136 | } | ||
137 | |||
138 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback1<T> callback) { | ||
139 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | ||
140 | var builder = builder(name).output(outputVariable); | ||
141 | callback.accept(builder, builder.parameter("p1"), outputVariable); | ||
142 | return builder.build(); | ||
143 | } | ||
144 | |||
145 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback2<T> callback) { | ||
146 | return of(null, type, callback); | ||
147 | } | ||
148 | |||
149 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback2<T> callback) { | ||
150 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | ||
151 | var builder = builder(name).output(outputVariable); | ||
152 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), outputVariable); | ||
153 | return builder.build(); | ||
154 | } | ||
155 | |||
156 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback3<T> callback) { | ||
157 | return of(null, type, callback); | ||
158 | } | ||
159 | |||
160 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback3<T> callback) { | ||
161 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | ||
162 | var builder = builder(name).output(outputVariable); | ||
163 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), | ||
164 | outputVariable); | ||
165 | return builder.build(); | ||
166 | } | ||
167 | |||
168 | public static <T> FunctionalQuery<T> of(Class<T> type, FunctionalQueryCallback4<T> callback) { | ||
169 | return of(null, type, callback); | ||
170 | } | ||
171 | |||
172 | public static <T> FunctionalQuery<T> of(String name, Class<T> type, FunctionalQueryCallback4<T> callback) { | ||
173 | var outputVariable = Variable.of(OUTPUT_VARIABLE_NAME, type); | ||
174 | var builder = builder(name).output(outputVariable); | ||
175 | callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"), | ||
176 | builder.parameter("p4"), outputVariable); | ||
177 | return builder.build(); | ||
178 | } | ||
179 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java new file mode 100644 index 00000000..138911bc --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.term.DataVariable; | ||
9 | |||
10 | public final class QueryBuilder extends AbstractQueryBuilder<QueryBuilder> { | ||
11 | QueryBuilder(String name) { | ||
12 | super(Dnf.builder(name)); | ||
13 | } | ||
14 | |||
15 | @Override | ||
16 | protected QueryBuilder self() { | ||
17 | return this; | ||
18 | } | ||
19 | |||
20 | public <T> FunctionalQueryBuilder<T> output(DataVariable<T> outputVariable) { | ||
21 | return new FunctionalQueryBuilder<>(outputVariable, dnfBuilder, outputVariable.getType()); | ||
22 | } | ||
23 | |||
24 | public RelationalQuery build() { | ||
25 | return dnfBuilder.build().asRelation(); | ||
26 | } | ||
27 | } | ||
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 new file mode 100644 index 00000000..d34a7ace --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java | |||
@@ -0,0 +1,66 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.literal.CallLiteral; | ||
9 | import tools.refinery.store.query.literal.CallPolarity; | ||
10 | import tools.refinery.store.query.term.AssignedValue; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | |||
13 | import java.util.Collections; | ||
14 | import java.util.List; | ||
15 | |||
16 | public final class RelationalQuery extends Query<Boolean> { | ||
17 | RelationalQuery(Dnf dnf) { | ||
18 | super(dnf); | ||
19 | for (var parameter : dnf.getSymbolicParameters()) { | ||
20 | var parameterType = parameter.tryGetType(); | ||
21 | if (parameterType.isPresent()) { | ||
22 | throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" | ||
23 | .formatted(parameter, dnf, parameterType.get().getName())); | ||
24 | } | ||
25 | } | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public int arity() { | ||
30 | return getDnf().arity(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public Class<Boolean> valueType() { | ||
35 | return Boolean.class; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public Boolean defaultValue() { | ||
40 | return false; | ||
41 | } | ||
42 | |||
43 | public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) { | ||
44 | return getDnf().call(polarity, Collections.unmodifiableList(arguments)); | ||
45 | } | ||
46 | |||
47 | public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) { | ||
48 | return getDnf().call(polarity, arguments); | ||
49 | } | ||
50 | |||
51 | public CallLiteral call(NodeVariable... arguments) { | ||
52 | return getDnf().call(arguments); | ||
53 | } | ||
54 | |||
55 | public CallLiteral callTransitive(NodeVariable left, NodeVariable right) { | ||
56 | return getDnf().callTransitive(left, right); | ||
57 | } | ||
58 | |||
59 | public AssignedValue<Integer> count(List<NodeVariable> arguments) { | ||
60 | return getDnf().count(Collections.unmodifiableList(arguments)); | ||
61 | } | ||
62 | |||
63 | public AssignedValue<Integer> count(NodeVariable... arguments) { | ||
64 | return getDnf().count(arguments); | ||
65 | } | ||
66 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java new file mode 100644 index 00000000..e0d3ba1f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java | |||
@@ -0,0 +1,52 @@ | |||
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.term.Parameter; | ||
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | public final class SymbolicParameter extends Parameter { | ||
15 | private final Variable variable; | ||
16 | |||
17 | public SymbolicParameter(Variable variable, ParameterDirection direction) { | ||
18 | super(variable.tryGetType().orElse(null), direction); | ||
19 | this.variable = variable; | ||
20 | } | ||
21 | |||
22 | public Variable getVariable() { | ||
23 | return variable; | ||
24 | } | ||
25 | |||
26 | public boolean isUnifiable() { | ||
27 | return variable.isUnifiable(); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public String toString() { | ||
32 | var direction = getDirection(); | ||
33 | if (direction == ParameterDirection.OUT) { | ||
34 | return variable.toString(); | ||
35 | } | ||
36 | return "%s %s".formatted(getDirection(), variable); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public boolean equals(Object o) { | ||
41 | if (this == o) return true; | ||
42 | if (o == null || getClass() != o.getClass()) return false; | ||
43 | if (!super.equals(o)) return false; | ||
44 | SymbolicParameter that = (SymbolicParameter) o; | ||
45 | return Objects.equals(variable, that.variable); | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public int hashCode() { | ||
50 | return Objects.hash(super.hashCode(), variable); | ||
51 | } | ||
52 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java new file mode 100644 index 00000000..d98dda2e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | |||
10 | import java.util.Collection; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface ClauseCallback0 { | ||
14 | Collection<Literal> toLiterals(); | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java new file mode 100644 index 00000000..4c01a527 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback1Data0 { | ||
15 | Collection<Literal> toLiterals(NodeVariable v1); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java new file mode 100644 index 00000000..2c0cb6eb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback1Data1<T> { | ||
15 | Collection<Literal> toLiterals(DataVariable<T> d1); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java new file mode 100644 index 00000000..d764bdba --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback2Data0 { | ||
15 | Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java new file mode 100644 index 00000000..140af03a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public interface ClauseCallback2Data1<T> { | ||
16 | Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T> x1); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java new file mode 100644 index 00000000..bfc8637c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback2Data2<T1, T2> { | ||
15 | Collection<Literal> toLiterals(DataVariable<T1> x1, DataVariable<T2> x2); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java new file mode 100644 index 00000000..074df65b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback3Data0 { | ||
15 | Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java new file mode 100644 index 00000000..24ba5187 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public interface ClauseCallback3Data1<T> { | ||
16 | Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T> d1); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java new file mode 100644 index 00000000..2a2e837a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public interface ClauseCallback3Data2<T1, T2> { | ||
16 | Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java new file mode 100644 index 00000000..8f4bdd01 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback3Data3<T1, T2, T3> { | ||
15 | Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java new file mode 100644 index 00000000..ed0f87b2 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback4Data0 { | ||
15 | Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, NodeVariable v4); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java new file mode 100644 index 00000000..9b27e2e1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public interface ClauseCallback4Data1<T> { | ||
16 | Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, DataVariable<T> d1); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java new file mode 100644 index 00000000..cbc4808e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public interface ClauseCallback4Data2<T1, T2> { | ||
16 | Collection<Literal> toLiterals(NodeVariable v1, NodeVariable v2, DataVariable<T1> d1, DataVariable<T2> d2); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java new file mode 100644 index 00000000..a6258f36 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | import java.util.Collection; | ||
13 | |||
14 | @FunctionalInterface | ||
15 | public interface ClauseCallback4Data3<T1, T2, T3> { | ||
16 | Collection<Literal> toLiterals(NodeVariable v1, DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java new file mode 100644 index 00000000..b52a911a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | @FunctionalInterface | ||
14 | public interface ClauseCallback4Data4<T1, T2, T3, T4> { | ||
15 | Collection<Literal> toLiterals(DataVariable<T1> d1, DataVariable<T2> d2, DataVariable<T3> d3, DataVariable<T4> d4); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java new file mode 100644 index 00000000..63b3eee6 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public interface FunctionalQueryCallback0<T> { | ||
13 | void accept(FunctionalQueryBuilder<T> builder, DataVariable<T> output); | ||
14 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java new file mode 100644 index 00000000..1295a118 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface FunctionalQueryCallback1<T> { | ||
14 | void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, DataVariable<T> output); | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java new file mode 100644 index 00000000..d5b7f9ff --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface FunctionalQueryCallback2<T> { | ||
14 | void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, DataVariable<T> output); | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java new file mode 100644 index 00000000..dc8404a0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface FunctionalQueryCallback3<T> { | ||
14 | void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, | ||
15 | DataVariable<T> output); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java new file mode 100644 index 00000000..b6d3ddb0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.FunctionalQueryBuilder; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface FunctionalQueryCallback4<T> { | ||
14 | void accept(FunctionalQueryBuilder<T> builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4, | ||
15 | DataVariable<T> output); | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java new file mode 100644 index 00000000..3cf1de48 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java | |||
@@ -0,0 +1,13 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public interface QueryCallback0 { | ||
12 | void accept(QueryBuilder builder); | ||
13 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java new file mode 100644 index 00000000..0a150955 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public interface QueryCallback1 { | ||
13 | void accept(QueryBuilder builder, NodeVariable p1); | ||
14 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java new file mode 100644 index 00000000..9493a7b4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public interface QueryCallback2 { | ||
13 | void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2); | ||
14 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java new file mode 100644 index 00000000..358c7da7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public interface QueryCallback3 { | ||
13 | void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3); | ||
14 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java new file mode 100644 index 00000000..890dda16 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.dnf.callback; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public interface QueryCallback4 { | ||
13 | void accept(QueryBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4); | ||
14 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java new file mode 100644 index 00000000..1eeb5723 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java | |||
@@ -0,0 +1,78 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.DnfClause; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.util.CycleDetectingMapper; | ||
13 | |||
14 | import java.util.List; | ||
15 | |||
16 | public class DeepDnfEqualityChecker implements DnfEqualityChecker { | ||
17 | private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString, | ||
18 | this::doCheckEqual); | ||
19 | |||
20 | @Override | ||
21 | public boolean dnfEqual(Dnf left, Dnf right) { | ||
22 | return mapper.map(new Pair(left, right)); | ||
23 | } | ||
24 | |||
25 | public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters, | ||
26 | List<? extends List<? extends Literal>> clauses, Dnf other) { | ||
27 | int arity = symbolicParameters.size(); | ||
28 | if (arity != other.arity()) { | ||
29 | return false; | ||
30 | } | ||
31 | for (int i = 0; i < arity; i++) { | ||
32 | if (!symbolicParameters.get(i).getDirection().equals(other.getSymbolicParameters().get(i).getDirection())) { | ||
33 | return false; | ||
34 | } | ||
35 | } | ||
36 | int numClauses = clauses.size(); | ||
37 | if (numClauses != other.getClauses().size()) { | ||
38 | return false; | ||
39 | } | ||
40 | for (int i = 0; i < numClauses; i++) { | ||
41 | var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters, | ||
42 | other.getSymbolicParameters()); | ||
43 | if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) { | ||
44 | return false; | ||
45 | } | ||
46 | } | ||
47 | return true; | ||
48 | } | ||
49 | |||
50 | private boolean equalsWithSubstitutionRaw(LiteralEqualityHelper helper, List<? extends Literal> literals, | ||
51 | DnfClause other) { | ||
52 | int size = literals.size(); | ||
53 | if (size != other.literals().size()) { | ||
54 | return false; | ||
55 | } | ||
56 | for (int i = 0; i < size; i++) { | ||
57 | if (!literals.get(i).equalsWithSubstitution(helper, other.literals().get(i))) { | ||
58 | return false; | ||
59 | } | ||
60 | } | ||
61 | return true; | ||
62 | } | ||
63 | |||
64 | protected boolean doCheckEqual(Pair pair) { | ||
65 | return pair.left.equalsWithSubstitution(this, pair.right); | ||
66 | } | ||
67 | |||
68 | protected List<Pair> getInProgress() { | ||
69 | return mapper.getInProgress(); | ||
70 | } | ||
71 | |||
72 | protected record Pair(Dnf left, Dnf right) { | ||
73 | @Override | ||
74 | public String toString() { | ||
75 | return "(%s, %s)".formatted(left.name(), right.name()); | ||
76 | } | ||
77 | } | ||
78 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java new file mode 100644 index 00000000..4a8bee3b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java | |||
@@ -0,0 +1,13 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public interface DnfEqualityChecker { | ||
12 | boolean dnfEqual(Dnf left, Dnf right); | ||
13 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java new file mode 100644 index 00000000..9315fb30 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java | |||
@@ -0,0 +1,54 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.equality; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.HashMap; | ||
13 | import java.util.List; | ||
14 | import java.util.Map; | ||
15 | |||
16 | public class LiteralEqualityHelper { | ||
17 | private final DnfEqualityChecker dnfEqualityChecker; | ||
18 | private final Map<Variable, Variable> leftToRight; | ||
19 | private final Map<Variable, Variable> rightToLeft; | ||
20 | |||
21 | public LiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, List<SymbolicParameter> leftParameters, | ||
22 | List<SymbolicParameter> rightParameters) { | ||
23 | this.dnfEqualityChecker = dnfEqualityChecker; | ||
24 | var arity = leftParameters.size(); | ||
25 | if (arity != rightParameters.size()) { | ||
26 | throw new IllegalArgumentException("Parameter lists have unequal length"); | ||
27 | } | ||
28 | leftToRight = new HashMap<>(arity); | ||
29 | rightToLeft = new HashMap<>(arity); | ||
30 | for (int i = 0; i < arity; i++) { | ||
31 | if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) { | ||
32 | throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i); | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | |||
37 | public boolean dnfEqual(Dnf left, Dnf right) { | ||
38 | return dnfEqualityChecker.dnfEqual(left, right); | ||
39 | } | ||
40 | |||
41 | public boolean variableEqual(Variable left, Variable right) { | ||
42 | if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) { | ||
43 | leftToRight.put(left, right); | ||
44 | rightToLeft.put(right, left); | ||
45 | return true; | ||
46 | } | ||
47 | return false; | ||
48 | } | ||
49 | |||
50 | private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) { | ||
51 | var currentValue = map.get(key); | ||
52 | return currentValue == null || currentValue.equals(expectedValue); | ||
53 | } | ||
54 | } | ||
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 new file mode 100644 index 00000000..8ef8e8b4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java | |||
@@ -0,0 +1,143 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.*; | ||
15 | |||
16 | public abstract class AbstractCallLiteral implements Literal { | ||
17 | private final Constraint target; | ||
18 | private final List<Variable> arguments; | ||
19 | private final Set<Variable> inArguments; | ||
20 | private final Set<Variable> outArguments; | ||
21 | |||
22 | // Use exhaustive switch over enums. | ||
23 | @SuppressWarnings("squid:S1301") | ||
24 | protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { | ||
25 | int arity = target.arity(); | ||
26 | if (arguments.size() != arity) { | ||
27 | throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), | ||
28 | target.arity(), arguments.size())); | ||
29 | } | ||
30 | this.target = target; | ||
31 | this.arguments = arguments; | ||
32 | var mutableInArguments = new LinkedHashSet<Variable>(); | ||
33 | var mutableOutArguments = new LinkedHashSet<Variable>(); | ||
34 | var parameters = target.getParameters(); | ||
35 | for (int i = 0; i < arity; i++) { | ||
36 | var argument = arguments.get(i); | ||
37 | var parameter = parameters.get(i); | ||
38 | if (!parameter.isAssignable(argument)) { | ||
39 | throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" | ||
40 | .formatted(i, target, parameter)); | ||
41 | } | ||
42 | switch (parameter.getDirection()) { | ||
43 | case IN -> { | ||
44 | if (mutableOutArguments.remove(argument)) { | ||
45 | checkInOutUnifiable(argument); | ||
46 | } | ||
47 | mutableInArguments.add(argument); | ||
48 | } | ||
49 | case OUT -> { | ||
50 | if (mutableInArguments.contains(argument)) { | ||
51 | checkInOutUnifiable(argument); | ||
52 | } else if (!mutableOutArguments.add(argument)) { | ||
53 | checkDuplicateOutUnifiable(argument); | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | inArguments = Collections.unmodifiableSet(mutableInArguments); | ||
59 | outArguments = Collections.unmodifiableSet(mutableOutArguments); | ||
60 | } | ||
61 | |||
62 | private static void checkInOutUnifiable(Variable argument) { | ||
63 | if (!argument.isUnifiable()) { | ||
64 | throw new IllegalArgumentException("Argument %s cannot appear with both %s and %s direction" | ||
65 | .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT)); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | private static void checkDuplicateOutUnifiable(Variable argument) { | ||
70 | if (!argument.isUnifiable()) { | ||
71 | throw new IllegalArgumentException("Argument %s cannot be bound multiple times".formatted(argument)); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | public Constraint getTarget() { | ||
76 | return target; | ||
77 | } | ||
78 | |||
79 | public List<Variable> getArguments() { | ||
80 | return arguments; | ||
81 | } | ||
82 | |||
83 | protected Set<Variable> getArgumentsOfDirection(ParameterDirection direction) { | ||
84 | return switch (direction) { | ||
85 | case IN -> inArguments; | ||
86 | case OUT -> outArguments; | ||
87 | }; | ||
88 | } | ||
89 | |||
90 | @Override | ||
91 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
92 | var inputVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT)); | ||
93 | inputVariables.retainAll(positiveVariablesInClause); | ||
94 | inputVariables.addAll(getArgumentsOfDirection(ParameterDirection.IN)); | ||
95 | return Collections.unmodifiableSet(inputVariables); | ||
96 | } | ||
97 | |||
98 | @Override | ||
99 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
100 | var privateVariables = new LinkedHashSet<>(getArgumentsOfDirection(ParameterDirection.OUT)); | ||
101 | privateVariables.removeAll(positiveVariablesInClause); | ||
102 | return Collections.unmodifiableSet(privateVariables); | ||
103 | } | ||
104 | |||
105 | @Override | ||
106 | public Literal substitute(Substitution substitution) { | ||
107 | var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList(); | ||
108 | return doSubstitute(substitution, substitutedArguments); | ||
109 | } | ||
110 | |||
111 | protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments); | ||
112 | |||
113 | @Override | ||
114 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
115 | if (other == null || getClass() != other.getClass()) { | ||
116 | return false; | ||
117 | } | ||
118 | var otherCallLiteral = (AbstractCallLiteral) other; | ||
119 | var arity = arguments.size(); | ||
120 | if (arity != otherCallLiteral.arguments.size()) { | ||
121 | return false; | ||
122 | } | ||
123 | for (int i = 0; i < arity; i++) { | ||
124 | if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) { | ||
125 | return false; | ||
126 | } | ||
127 | } | ||
128 | return target.equals(helper, otherCallLiteral.target); | ||
129 | } | ||
130 | |||
131 | @Override | ||
132 | public boolean equals(Object o) { | ||
133 | if (this == o) return true; | ||
134 | if (o == null || getClass() != o.getClass()) return false; | ||
135 | AbstractCallLiteral that = (AbstractCallLiteral) o; | ||
136 | return target.equals(that.target) && arguments.equals(that.arguments); | ||
137 | } | ||
138 | |||
139 | @Override | ||
140 | public int hashCode() { | ||
141 | return Objects.hash(getClass(), target, arguments); | ||
142 | } | ||
143 | } | ||
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 new file mode 100644 index 00000000..3a5eb5c7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java | |||
@@ -0,0 +1,138 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | import java.util.Objects; | ||
15 | import java.util.Set; | ||
16 | |||
17 | public class AggregationLiteral<R, T> extends AbstractCallLiteral { | ||
18 | private final DataVariable<R> resultVariable; | ||
19 | private final DataVariable<T> inputVariable; | ||
20 | private final Aggregator<R, T> aggregator; | ||
21 | |||
22 | public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator, | ||
23 | DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { | ||
24 | super(target, arguments); | ||
25 | if (!inputVariable.getType().equals(aggregator.getInputType())) { | ||
26 | throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( | ||
27 | inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); | ||
28 | } | ||
29 | if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { | ||
30 | throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list" | ||
31 | .formatted(inputVariable, ParameterDirection.OUT)); | ||
32 | } | ||
33 | if (!resultVariable.getType().equals(aggregator.getResultType())) { | ||
34 | throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( | ||
35 | resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); | ||
36 | } | ||
37 | if (arguments.contains(resultVariable)) { | ||
38 | throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted( | ||
39 | resultVariable)); | ||
40 | } | ||
41 | this.resultVariable = resultVariable; | ||
42 | this.inputVariable = inputVariable; | ||
43 | this.aggregator = aggregator; | ||
44 | } | ||
45 | |||
46 | public DataVariable<R> getResultVariable() { | ||
47 | return resultVariable; | ||
48 | } | ||
49 | |||
50 | public DataVariable<T> getInputVariable() { | ||
51 | return inputVariable; | ||
52 | } | ||
53 | |||
54 | public Aggregator<R, T> getAggregator() { | ||
55 | return aggregator; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public Set<Variable> getOutputVariables() { | ||
60 | return Set.of(resultVariable); | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
65 | if (positiveVariablesInClause.contains(inputVariable)) { | ||
66 | throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable)); | ||
67 | } | ||
68 | return super.getInputVariables(positiveVariablesInClause); | ||
69 | } | ||
70 | |||
71 | @Override | ||
72 | public Literal reduce() { | ||
73 | var reduction = getTarget().getReduction(); | ||
74 | return switch (reduction) { | ||
75 | case ALWAYS_FALSE -> { | ||
76 | var emptyValue = aggregator.getEmptyResult(); | ||
77 | yield emptyValue == null ? BooleanLiteral.FALSE : | ||
78 | resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); | ||
79 | } | ||
80 | case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set"); | ||
81 | case NOT_REDUCIBLE -> this; | ||
82 | }; | ||
83 | } | ||
84 | |||
85 | @Override | ||
86 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
87 | return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator, | ||
88 | substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments); | ||
89 | } | ||
90 | |||
91 | @Override | ||
92 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
93 | if (!super.equalsWithSubstitution(helper, other)) { | ||
94 | return false; | ||
95 | } | ||
96 | var otherAggregationLiteral = (AggregationLiteral<?, ?>) other; | ||
97 | return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) && | ||
98 | aggregator.equals(otherAggregationLiteral.aggregator) && | ||
99 | helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable); | ||
100 | } | ||
101 | |||
102 | @Override | ||
103 | public boolean equals(Object o) { | ||
104 | if (this == o) return true; | ||
105 | if (o == null || getClass() != o.getClass()) return false; | ||
106 | if (!super.equals(o)) return false; | ||
107 | AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o; | ||
108 | return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) && | ||
109 | aggregator.equals(that.aggregator); | ||
110 | } | ||
111 | |||
112 | @Override | ||
113 | public int hashCode() { | ||
114 | return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator); | ||
115 | } | ||
116 | |||
117 | @Override | ||
118 | public String toString() { | ||
119 | var builder = new StringBuilder(); | ||
120 | builder.append(resultVariable); | ||
121 | builder.append(" is "); | ||
122 | builder.append(getTarget().toReferenceString()); | ||
123 | builder.append("("); | ||
124 | var argumentIterator = getArguments().iterator(); | ||
125 | if (argumentIterator.hasNext()) { | ||
126 | var argument = argumentIterator.next(); | ||
127 | if (inputVariable.equals(argument)) { | ||
128 | builder.append("@Aggregate(\"").append(aggregator).append("\") "); | ||
129 | } | ||
130 | builder.append(argument); | ||
131 | while (argumentIterator.hasNext()) { | ||
132 | builder.append(", ").append(argumentIterator.next()); | ||
133 | } | ||
134 | } | ||
135 | builder.append(")"); | ||
136 | return builder.toString(); | ||
137 | } | ||
138 | } | ||
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 new file mode 100644 index 00000000..dbf999a2 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java | |||
@@ -0,0 +1,79 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.DataVariable; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.Collections; | ||
15 | import java.util.Objects; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal { | ||
19 | public AssignLiteral { | ||
20 | if (!term.getType().equals(variable.getType())) { | ||
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | ||
22 | term, variable.getType().getName(), term.getType().getName())); | ||
23 | } | ||
24 | var inputVariables = term.getInputVariables(); | ||
25 | if (inputVariables.contains(variable)) { | ||
26 | throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( | ||
27 | variable, term)); | ||
28 | } | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Set<Variable> getOutputVariables() { | ||
33 | return Set.of(variable); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
38 | return Collections.unmodifiableSet(term.getInputVariables()); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
43 | return Set.of(); | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public Literal substitute(Substitution substitution) { | ||
48 | return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution)); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
53 | if (other == null || getClass() != other.getClass()) { | ||
54 | return false; | ||
55 | } | ||
56 | var otherLetLiteral = (AssignLiteral<?>) other; | ||
57 | return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper, | ||
58 | otherLetLiteral.term); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public String toString() { | ||
63 | return "%s is (%s)".formatted(variable, term); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public boolean equals(Object obj) { | ||
68 | if (obj == this) return true; | ||
69 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
70 | var that = (AssignLiteral<?>) obj; | ||
71 | return Objects.equals(this.variable, that.variable) && | ||
72 | Objects.equals(this.term, that.term); | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public int hashCode() { | ||
77 | return Objects.hash(getClass(), variable, term); | ||
78 | } | ||
79 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java new file mode 100644 index 00000000..1ca04c77 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java | |||
@@ -0,0 +1,83 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.ConstantTerm; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.Collections; | ||
15 | import java.util.Objects; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public record AssumeLiteral(Term<Boolean> term) implements Literal { | ||
19 | public AssumeLiteral { | ||
20 | if (!term.getType().equals(Boolean.class)) { | ||
21 | throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( | ||
22 | term, Boolean.class.getName(), term.getType().getName())); | ||
23 | } | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public Set<Variable> getOutputVariables() { | ||
28 | return Set.of(); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
33 | return Collections.unmodifiableSet(term.getInputVariables()); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
38 | return Set.of(); | ||
39 | } | ||
40 | |||
41 | |||
42 | @Override | ||
43 | public Literal substitute(Substitution substitution) { | ||
44 | return new AssumeLiteral(term.substitute(substitution)); | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
49 | if (other == null || getClass() != other.getClass()) { | ||
50 | return false; | ||
51 | } | ||
52 | var otherAssumeLiteral = (AssumeLiteral) other; | ||
53 | return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public Literal reduce() { | ||
58 | if (term instanceof ConstantTerm<Boolean> constantTerm) { | ||
59 | // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. | ||
60 | return Boolean.TRUE.equals(constantTerm.getValue()) ? BooleanLiteral.TRUE : | ||
61 | BooleanLiteral.FALSE; | ||
62 | } | ||
63 | return this; | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public String toString() { | ||
68 | return "(%s)".formatted(term); | ||
69 | } | ||
70 | |||
71 | @Override | ||
72 | public boolean equals(Object obj) { | ||
73 | if (obj == this) return true; | ||
74 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
75 | var that = (AssumeLiteral) obj; | ||
76 | return Objects.equals(this.term, that.term); | ||
77 | } | ||
78 | |||
79 | @Override | ||
80 | public int hashCode() { | ||
81 | return Objects.hash(getClass(), term); | ||
82 | } | ||
83 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java new file mode 100644 index 00000000..f312d202 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java | |||
@@ -0,0 +1,63 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Set; | ||
13 | |||
14 | public enum BooleanLiteral implements CanNegate<BooleanLiteral> { | ||
15 | TRUE(true), | ||
16 | FALSE(false); | ||
17 | |||
18 | private final boolean value; | ||
19 | |||
20 | BooleanLiteral(boolean value) { | ||
21 | this.value = value; | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public Set<Variable> getOutputVariables() { | ||
26 | return Set.of(); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
31 | return Set.of(); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
36 | return Set.of(); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public Literal substitute(Substitution substitution) { | ||
41 | // No variables to substitute. | ||
42 | return this; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public BooleanLiteral negate() { | ||
47 | return fromBoolean(!value); | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
52 | return equals(other); | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public String toString() { | ||
57 | return Boolean.toString(value); | ||
58 | } | ||
59 | |||
60 | public static BooleanLiteral fromBoolean(boolean value) { | ||
61 | return value ? TRUE : FALSE; | ||
62 | } | ||
63 | } | ||
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 new file mode 100644 index 00000000..29772aee --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java | |||
@@ -0,0 +1,130 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | |||
14 | import java.util.*; | ||
15 | |||
16 | public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { | ||
17 | private final CallPolarity polarity; | ||
18 | |||
19 | public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) { | ||
20 | super(target, arguments); | ||
21 | var parameters = target.getParameters(); | ||
22 | int arity = target.arity(); | ||
23 | if (polarity.isTransitive()) { | ||
24 | if (arity != 2) { | ||
25 | throw new IllegalArgumentException("Transitive closures can only take binary relations"); | ||
26 | } | ||
27 | if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { | ||
28 | throw new IllegalArgumentException("Transitive closures can only be computed over nodes"); | ||
29 | } | ||
30 | } | ||
31 | this.polarity = polarity; | ||
32 | } | ||
33 | |||
34 | public CallPolarity getPolarity() { | ||
35 | return polarity; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
40 | return new CallLiteral(polarity, getTarget(), substitutedArguments); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public Set<Variable> getOutputVariables() { | ||
45 | if (polarity.isPositive()) { | ||
46 | return getArgumentsOfDirection(ParameterDirection.OUT); | ||
47 | } | ||
48 | return Set.of(); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
53 | if (polarity.isPositive()) { | ||
54 | return getArgumentsOfDirection(ParameterDirection.IN); | ||
55 | } | ||
56 | return super.getInputVariables(positiveVariablesInClause); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
61 | if (polarity.isPositive()) { | ||
62 | return Set.of(); | ||
63 | } | ||
64 | return super.getPrivateVariables(positiveVariablesInClause); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public Literal reduce() { | ||
69 | var reduction = getTarget().getReduction(); | ||
70 | var negatedReduction = polarity.isPositive() ? reduction : reduction.negate(); | ||
71 | return switch (negatedReduction) { | ||
72 | case ALWAYS_TRUE -> BooleanLiteral.TRUE; | ||
73 | case ALWAYS_FALSE -> BooleanLiteral.FALSE; | ||
74 | default -> this; | ||
75 | }; | ||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
80 | if (!super.equalsWithSubstitution(helper, other)) { | ||
81 | return false; | ||
82 | } | ||
83 | var otherCallLiteral = (CallLiteral) other; | ||
84 | return polarity.equals(otherCallLiteral.polarity); | ||
85 | } | ||
86 | |||
87 | @Override | ||
88 | public CallLiteral negate() { | ||
89 | return new CallLiteral(polarity.negate(), getTarget(), getArguments()); | ||
90 | } | ||
91 | |||
92 | @Override | ||
93 | public boolean equals(Object o) { | ||
94 | if (this == o) return true; | ||
95 | if (o == null || getClass() != o.getClass()) return false; | ||
96 | if (!super.equals(o)) return false; | ||
97 | CallLiteral that = (CallLiteral) o; | ||
98 | return polarity == that.polarity; | ||
99 | } | ||
100 | |||
101 | @Override | ||
102 | public int hashCode() { | ||
103 | return Objects.hash(super.hashCode(), polarity); | ||
104 | } | ||
105 | |||
106 | @Override | ||
107 | public String toString() { | ||
108 | var builder = new StringBuilder(); | ||
109 | if (!polarity.isPositive()) { | ||
110 | builder.append("!("); | ||
111 | } | ||
112 | builder.append(getTarget().toReferenceString()); | ||
113 | if (polarity.isTransitive()) { | ||
114 | builder.append("+"); | ||
115 | } | ||
116 | builder.append("("); | ||
117 | var argumentIterator = getArguments().iterator(); | ||
118 | if (argumentIterator.hasNext()) { | ||
119 | builder.append(argumentIterator.next()); | ||
120 | while (argumentIterator.hasNext()) { | ||
121 | builder.append(", ").append(argumentIterator.next()); | ||
122 | } | ||
123 | } | ||
124 | builder.append(")"); | ||
125 | if (!polarity.isPositive()) { | ||
126 | builder.append(")"); | ||
127 | } | ||
128 | return builder.toString(); | ||
129 | } | ||
130 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java index 957e9b7b..ca70b0fd 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallPolarity.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java | |||
@@ -1,4 +1,9 @@ | |||
1 | package tools.refinery.store.query.atom; | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
2 | 7 | ||
3 | public enum CallPolarity { | 8 | public enum CallPolarity { |
4 | POSITIVE(true, false), | 9 | POSITIVE(true, false), |
@@ -22,7 +27,11 @@ public enum CallPolarity { | |||
22 | return transitive; | 27 | return transitive; |
23 | } | 28 | } |
24 | 29 | ||
25 | public static CallPolarity fromBoolean(boolean positive) { | 30 | public CallPolarity negate() { |
26 | return positive ? POSITIVE : NEGATIVE; | 31 | return switch (this) { |
32 | case POSITIVE -> NEGATIVE; | ||
33 | case NEGATIVE -> POSITIVE; | ||
34 | case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated"); | ||
35 | }; | ||
27 | } | 36 | } |
28 | } | 37 | } |
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java new file mode 100644 index 00000000..35dcb3fb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java | |||
@@ -0,0 +1,10 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | public interface CanNegate<T extends CanNegate<T>> extends Literal { | ||
9 | T negate(); | ||
10 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java new file mode 100644 index 00000000..73545620 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | |||
13 | import java.util.Objects; | ||
14 | import java.util.Set; | ||
15 | |||
16 | public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { | ||
17 | @Override | ||
18 | public Set<Variable> getOutputVariables() { | ||
19 | return Set.of(variable); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
24 | return Set.of(); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
29 | return Set.of(); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public ConstantLiteral substitute(Substitution substitution) { | ||
34 | return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId); | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
39 | if (other.getClass() != getClass()) { | ||
40 | return false; | ||
41 | } | ||
42 | var otherConstantLiteral = (ConstantLiteral) other; | ||
43 | return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; | ||
44 | } | ||
45 | |||
46 | |||
47 | @Override | ||
48 | public String toString() { | ||
49 | return "%s === @Constant %d".formatted(variable, nodeId); | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public boolean equals(Object obj) { | ||
54 | if (obj == this) return true; | ||
55 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
56 | var that = (ConstantLiteral) obj; | ||
57 | return Objects.equals(this.variable, that.variable) && | ||
58 | this.nodeId == that.nodeId; | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public int hashCode() { | ||
63 | return Objects.hash(getClass(), variable, nodeId); | ||
64 | } | ||
65 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java new file mode 100644 index 00000000..4d4749c8 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java | |||
@@ -0,0 +1,101 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.term.DataVariable; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.query.term.int_.IntTerms; | ||
14 | |||
15 | import java.util.List; | ||
16 | import java.util.Objects; | ||
17 | import java.util.Set; | ||
18 | |||
19 | public class CountLiteral extends AbstractCallLiteral { | ||
20 | private final DataVariable<Integer> resultVariable; | ||
21 | |||
22 | public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { | ||
23 | super(target, arguments); | ||
24 | if (!resultVariable.getType().equals(Integer.class)) { | ||
25 | throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted( | ||
26 | resultVariable, Integer.class.getName(), resultVariable.getType().getName())); | ||
27 | } | ||
28 | if (arguments.contains(resultVariable)) { | ||
29 | throw new IllegalArgumentException("Count result variable %s must not appear in the argument list" | ||
30 | .formatted(resultVariable)); | ||
31 | } | ||
32 | this.resultVariable = resultVariable; | ||
33 | } | ||
34 | |||
35 | public DataVariable<Integer> getResultVariable() { | ||
36 | return resultVariable; | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public Set<Variable> getOutputVariables() { | ||
41 | return Set.of(resultVariable); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public Literal reduce() { | ||
46 | var reduction = getTarget().getReduction(); | ||
47 | return switch (reduction) { | ||
48 | case ALWAYS_FALSE -> getResultVariable().assign(IntTerms.constant(0)); | ||
49 | // The only way a constant {@code true} predicate can be called in a negative position is to have all of | ||
50 | // its arguments bound as input variables. Thus, there will only be a single match. | ||
51 | case ALWAYS_TRUE -> getResultVariable().assign(IntTerms.constant(1)); | ||
52 | case NOT_REDUCIBLE -> this; | ||
53 | }; | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
58 | return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
63 | if (!super.equalsWithSubstitution(helper, other)) { | ||
64 | return false; | ||
65 | } | ||
66 | var otherCountLiteral = (CountLiteral) other; | ||
67 | return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable); | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public boolean equals(Object o) { | ||
72 | if (this == o) return true; | ||
73 | if (o == null || getClass() != o.getClass()) return false; | ||
74 | if (!super.equals(o)) return false; | ||
75 | CountLiteral that = (CountLiteral) o; | ||
76 | return resultVariable.equals(that.resultVariable); | ||
77 | } | ||
78 | |||
79 | @Override | ||
80 | public int hashCode() { | ||
81 | return Objects.hash(super.hashCode(), resultVariable); | ||
82 | } | ||
83 | |||
84 | @Override | ||
85 | public String toString() { | ||
86 | var builder = new StringBuilder(); | ||
87 | builder.append(resultVariable); | ||
88 | builder.append(" is count "); | ||
89 | builder.append(getTarget().toReferenceString()); | ||
90 | builder.append("("); | ||
91 | var argumentIterator = getArguments().iterator(); | ||
92 | if (argumentIterator.hasNext()) { | ||
93 | builder.append(argumentIterator.next()); | ||
94 | while (argumentIterator.hasNext()) { | ||
95 | builder.append(", ").append(argumentIterator.next()); | ||
96 | } | ||
97 | } | ||
98 | builder.append(")"); | ||
99 | return builder.toString(); | ||
100 | } | ||
101 | } | ||
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 new file mode 100644 index 00000000..28ba7625 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java | |||
@@ -0,0 +1,81 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | |||
13 | import java.util.Objects; | ||
14 | import java.util.Set; | ||
15 | |||
16 | public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) | ||
17 | implements CanNegate<EquivalenceLiteral> { | ||
18 | @Override | ||
19 | public Set<Variable> getOutputVariables() { | ||
20 | return Set.of(left); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
25 | return Set.of(right); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) { | ||
30 | return Set.of(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public EquivalenceLiteral negate() { | ||
35 | return new EquivalenceLiteral(!positive, left, right); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public EquivalenceLiteral substitute(Substitution substitution) { | ||
40 | return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left), | ||
41 | substitution.getTypeSafeSubstitute(right)); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public Literal reduce() { | ||
46 | if (left.equals(right)) { | ||
47 | return positive ? BooleanLiteral.TRUE : BooleanLiteral.FALSE; | ||
48 | } | ||
49 | return this; | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { | ||
54 | if (other.getClass() != getClass()) { | ||
55 | return false; | ||
56 | } | ||
57 | var otherEquivalenceLiteral = (EquivalenceLiteral) other; | ||
58 | return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right, | ||
59 | otherEquivalenceLiteral.right); | ||
60 | } | ||
61 | |||
62 | @Override | ||
63 | public String toString() { | ||
64 | return "%s %s %s".formatted(left, positive ? "===" : "!==", right); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public boolean equals(Object obj) { | ||
69 | if (obj == this) return true; | ||
70 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
71 | var that = (EquivalenceLiteral) obj; | ||
72 | return this.positive == that.positive && | ||
73 | Objects.equals(this.left, that.left) && | ||
74 | Objects.equals(this.right, that.right); | ||
75 | } | ||
76 | |||
77 | @Override | ||
78 | public int hashCode() { | ||
79 | return Objects.hash(getClass(), positive, left, right); | ||
80 | } | ||
81 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java new file mode 100644 index 00000000..ce6c11fe --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java | |||
@@ -0,0 +1,29 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Set; | ||
13 | |||
14 | public interface Literal { | ||
15 | Set<Variable> getOutputVariables(); | ||
16 | |||
17 | Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause); | ||
18 | |||
19 | Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause); | ||
20 | |||
21 | Literal substitute(Substitution substitution); | ||
22 | |||
23 | default Literal reduce() { | ||
24 | return this; | ||
25 | } | ||
26 | |||
27 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") | ||
28 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); | ||
29 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java new file mode 100644 index 00000000..b3a87811 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | |||
10 | public final class Literals { | ||
11 | private Literals() { | ||
12 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
13 | } | ||
14 | |||
15 | public static <T extends CanNegate<T>> T not(CanNegate<T> literal) { | ||
16 | return literal.negate(); | ||
17 | } | ||
18 | |||
19 | public static AssumeLiteral assume(Term<Boolean> term) { | ||
20 | return new AssumeLiteral(term); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java new file mode 100644 index 00000000..ee155a9a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.literal; | ||
7 | |||
8 | public enum Reduction { | ||
9 | /** | ||
10 | * Signifies that a literal should be preserved in the clause. | ||
11 | */ | ||
12 | NOT_REDUCIBLE, | ||
13 | |||
14 | /** | ||
15 | * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty). | ||
16 | */ | ||
17 | ALWAYS_TRUE, | ||
18 | |||
19 | /** | ||
20 | * Signifies that the clause with the literal may be omitted entirely. | ||
21 | */ | ||
22 | ALWAYS_FALSE; | ||
23 | |||
24 | public Reduction negate() { | ||
25 | return switch (this) { | ||
26 | case NOT_REDUCIBLE -> NOT_REDUCIBLE; | ||
27 | case ALWAYS_TRUE -> ALWAYS_FALSE; | ||
28 | case ALWAYS_FALSE -> ALWAYS_TRUE; | ||
29 | }; | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java new file mode 100644 index 00000000..a710c64d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java | |||
@@ -0,0 +1,63 @@ | |||
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.resultset; | ||
7 | |||
8 | import tools.refinery.store.query.ModelQueryAdapter; | ||
9 | import tools.refinery.store.query.dnf.Query; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.ArrayList; | ||
13 | import java.util.List; | ||
14 | |||
15 | public abstract class AbstractResultSet<T> implements ResultSet<T> { | ||
16 | private final ModelQueryAdapter adapter; | ||
17 | private final Query<T> query; | ||
18 | private final List<ResultSetListener<T>> listeners = new ArrayList<>(); | ||
19 | |||
20 | protected AbstractResultSet(ModelQueryAdapter adapter, Query<T> query) { | ||
21 | this.adapter = adapter; | ||
22 | this.query = query; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public ModelQueryAdapter getAdapter() { | ||
27 | return adapter; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public Query<T> getQuery() { | ||
32 | return query; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public void addListener(ResultSetListener<T> listener) { | ||
37 | if (listeners.isEmpty()) { | ||
38 | startListeningForChanges(); | ||
39 | } | ||
40 | listeners.add(listener); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public void removeListener(ResultSetListener<T> listener) { | ||
45 | listeners.remove(listener); | ||
46 | if (listeners.isEmpty()) { | ||
47 | stopListeningForChanges(); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | protected abstract void startListeningForChanges(); | ||
52 | |||
53 | protected abstract void stopListeningForChanges(); | ||
54 | |||
55 | protected void notifyChange(Tuple key, T oldValue, T newValue) { | ||
56 | int listenerCount = listeners.size(); | ||
57 | // Use a for loop instead of a for-each loop to avoid {@code Iterator} allocation overhead. | ||
58 | //noinspection ForLoopReplaceableByForEach | ||
59 | for (int i = 0; i < listenerCount; i++) { | ||
60 | listeners.get(i).put(key, oldValue, newValue); | ||
61 | } | ||
62 | } | ||
63 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java new file mode 100644 index 00000000..02809477 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.query.ModelQueryAdapter; | ||
9 | import tools.refinery.store.query.dnf.AnyQuery; | ||
10 | |||
11 | public sealed interface AnyResultSet permits ResultSet { | ||
12 | ModelQueryAdapter getAdapter(); | ||
13 | |||
14 | AnyQuery getQuery(); | ||
15 | |||
16 | int size(); | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java new file mode 100644 index 00000000..2795a44b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.map.Cursors; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) implements ResultSet<T> { | ||
15 | @Override | ||
16 | public ModelQueryAdapter getAdapter() { | ||
17 | return adapter; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public Query<T> getQuery() { | ||
22 | return query; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public T get(Tuple parameters) { | ||
27 | return query.defaultValue(); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public Cursor<Tuple, T> getAll() { | ||
32 | return Cursors.empty(); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public int size() { | ||
37 | return 0; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public void addListener(ResultSetListener<T> listener) { | ||
42 | // No need to store the listener, because the empty result set will never change. | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public void removeListener(ResultSetListener<T> listener) { | ||
47 | // No need to remove the listener, because we never stored it. | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java new file mode 100644 index 00000000..39006d65 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java | |||
@@ -0,0 +1,80 @@ | |||
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.resultset; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.query.ModelQueryAdapter; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.utils.OrderStatisticTree; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | import java.util.Objects; | ||
15 | |||
16 | public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> { | ||
17 | private final ResultSet<T> resultSet; | ||
18 | private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>(); | ||
19 | private final ResultSetListener<T> listener = (key, fromValue, toValue) -> { | ||
20 | var defaultValue = getQuery().defaultValue(); | ||
21 | if (Objects.equals(defaultValue, toValue)) { | ||
22 | tree.remove(key); | ||
23 | } else { | ||
24 | tree.add(key); | ||
25 | } | ||
26 | }; | ||
27 | |||
28 | public OrderedResultSet(ResultSet<T> resultSet) { | ||
29 | this.resultSet = resultSet; | ||
30 | resultSet.addListener(listener); | ||
31 | var cursor = resultSet.getAll(); | ||
32 | while (cursor.move()) { | ||
33 | tree.add(cursor.getKey()); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public ModelQueryAdapter getAdapter() { | ||
39 | return resultSet.getAdapter(); | ||
40 | } | ||
41 | |||
42 | @Override | ||
43 | public int size() { | ||
44 | return resultSet.size(); | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public Query<T> getQuery() { | ||
49 | return resultSet.getQuery(); | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public T get(Tuple parameters) { | ||
54 | return resultSet.get(parameters); | ||
55 | } | ||
56 | |||
57 | public Tuple getKey(int index) { | ||
58 | return tree.get(index); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public Cursor<Tuple, T> getAll() { | ||
63 | return resultSet.getAll(); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public void addListener(ResultSetListener<T> listener) { | ||
68 | resultSet.addListener(listener); | ||
69 | } | ||
70 | |||
71 | @Override | ||
72 | public void removeListener(ResultSetListener<T> listener) { | ||
73 | resultSet.removeListener(listener); | ||
74 | } | ||
75 | |||
76 | @Override | ||
77 | public void close() { | ||
78 | resultSet.removeListener(listener); | ||
79 | } | ||
80 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java new file mode 100644 index 00000000..33d1ea95 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.query.dnf.Query; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public non-sealed interface ResultSet<T> extends AnyResultSet { | ||
13 | Query<T> getQuery(); | ||
14 | |||
15 | T get(Tuple parameters); | ||
16 | |||
17 | Cursor<Tuple, T> getAll(); | ||
18 | |||
19 | void addListener(ResultSetListener<T> listener); | ||
20 | |||
21 | void removeListener(ResultSetListener<T> listener); | ||
22 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java new file mode 100644 index 00000000..fd8a503e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.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.store.query.resultset; | ||
7 | |||
8 | import tools.refinery.store.tuple.Tuple; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public interface ResultSetListener<T> { | ||
12 | void put(Tuple key, T fromValue, T toValue); | ||
13 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java new file mode 100644 index 00000000..a8201eef --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | import java.util.Map; | ||
11 | |||
12 | public record MapBasedSubstitution(Map<Variable, Variable> map, Substitution fallback) implements Substitution { | ||
13 | @Override | ||
14 | public Variable getSubstitute(Variable variable) { | ||
15 | var value = map.get(variable); | ||
16 | return value == null ? fallback.getSubstitute(variable) : value; | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java new file mode 100644 index 00000000..9b737ceb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java | |||
@@ -0,0 +1,20 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | import java.util.HashMap; | ||
11 | import java.util.Map; | ||
12 | |||
13 | public class RenewingSubstitution implements Substitution { | ||
14 | private final Map<Variable, Variable> alreadyRenewed = new HashMap<>(); | ||
15 | |||
16 | @Override | ||
17 | public Variable getSubstitute(Variable variable) { | ||
18 | return alreadyRenewed.computeIfAbsent(variable, Variable::renew); | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java new file mode 100644 index 00000000..bb3803d3 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java | |||
@@ -0,0 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.Variable; | ||
9 | |||
10 | public enum StatelessSubstitution implements Substitution { | ||
11 | FAILING { | ||
12 | @Override | ||
13 | public Variable getSubstitute(Variable variable) { | ||
14 | throw new IllegalArgumentException("No substitute for " + variable); | ||
15 | } | ||
16 | }, | ||
17 | IDENTITY { | ||
18 | @Override | ||
19 | public Variable getSubstitute(Variable variable) { | ||
20 | return variable; | ||
21 | } | ||
22 | } | ||
23 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java new file mode 100644 index 00000000..834fce12 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java | |||
@@ -0,0 +1,29 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.DataVariable; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | @FunctionalInterface | ||
13 | public interface Substitution { | ||
14 | Variable getSubstitute(Variable variable); | ||
15 | |||
16 | default NodeVariable getTypeSafeSubstitute(NodeVariable variable) { | ||
17 | var substitute = getSubstitute(variable); | ||
18 | return substitute.asNodeVariable(); | ||
19 | } | ||
20 | |||
21 | default <T> DataVariable<T> getTypeSafeSubstitute(DataVariable<T> variable) { | ||
22 | var substitute = getSubstitute(variable); | ||
23 | return substitute.asDataVariable(variable.getType()); | ||
24 | } | ||
25 | |||
26 | static SubstitutionBuilder builder() { | ||
27 | return new SubstitutionBuilder(); | ||
28 | } | ||
29 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java new file mode 100644 index 00000000..37fb6908 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java | |||
@@ -0,0 +1,79 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.substitution; | ||
7 | |||
8 | import tools.refinery.store.query.term.DataVariable; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | |||
12 | import java.util.Collections; | ||
13 | import java.util.HashMap; | ||
14 | import java.util.List; | ||
15 | import java.util.Map; | ||
16 | |||
17 | @SuppressWarnings("UnusedReturnValue") | ||
18 | public class SubstitutionBuilder { | ||
19 | private final Map<Variable, Variable> map = new HashMap<>(); | ||
20 | private Substitution fallback; | ||
21 | |||
22 | SubstitutionBuilder() { | ||
23 | total(); | ||
24 | } | ||
25 | |||
26 | public SubstitutionBuilder put(NodeVariable original, NodeVariable substitute) { | ||
27 | return putChecked(original, substitute); | ||
28 | } | ||
29 | |||
30 | public <T> SubstitutionBuilder put(DataVariable<T> original, DataVariable<T> substitute) { | ||
31 | return putChecked(original, substitute); | ||
32 | } | ||
33 | |||
34 | public SubstitutionBuilder putChecked(Variable original, Variable substitute) { | ||
35 | if (!original.tryGetType().equals(substitute.tryGetType())) { | ||
36 | throw new IllegalArgumentException("Cannot substitute variable %s of sort %s with variable %s of sort %s" | ||
37 | .formatted(original, original.tryGetType().map(Class::getName).orElse("node"), substitute, | ||
38 | substitute.tryGetType().map(Class::getName).orElse("node"))); | ||
39 | } | ||
40 | if (map.containsKey(original)) { | ||
41 | throw new IllegalArgumentException("Already has substitution for variable %s".formatted(original)); | ||
42 | } | ||
43 | map.put(original, substitute); | ||
44 | return this; | ||
45 | } | ||
46 | |||
47 | public SubstitutionBuilder putManyChecked(List<Variable> originals, List<Variable> substitutes) { | ||
48 | int size = originals.size(); | ||
49 | if (size != substitutes.size()) { | ||
50 | throw new IllegalArgumentException("Cannot substitute %d variables %s with %d variables %s" | ||
51 | .formatted(size, originals, substitutes.size(), substitutes)); | ||
52 | } | ||
53 | for (int i = 0; i < size; i++) { | ||
54 | putChecked(originals.get(i), substitutes.get(i)); | ||
55 | } | ||
56 | return this; | ||
57 | } | ||
58 | |||
59 | public SubstitutionBuilder fallback(Substitution newFallback) { | ||
60 | fallback = newFallback; | ||
61 | return this; | ||
62 | } | ||
63 | |||
64 | public SubstitutionBuilder total() { | ||
65 | return fallback(StatelessSubstitution.FAILING); | ||
66 | } | ||
67 | |||
68 | public SubstitutionBuilder partial() { | ||
69 | return fallback(StatelessSubstitution.IDENTITY); | ||
70 | } | ||
71 | |||
72 | public SubstitutionBuilder renewing() { | ||
73 | return fallback(new RenewingSubstitution()); | ||
74 | } | ||
75 | |||
76 | public Substitution build() { | ||
77 | return map.isEmpty() ? fallback : new MapBasedSubstitution(Collections.unmodifiableMap(map), fallback); | ||
78 | } | ||
79 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java new file mode 100644 index 00000000..d0ae3c12 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java | |||
@@ -0,0 +1,41 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | |||
10 | import java.util.Objects; | ||
11 | |||
12 | public abstract class AbstractTerm<T> implements Term<T> { | ||
13 | private final Class<T> type; | ||
14 | |||
15 | protected AbstractTerm(Class<T> type) { | ||
16 | this.type = type; | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | ||
21 | return getClass().equals(other.getClass()) && type.equals(other.getType()); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public Class<T> getType() { | ||
26 | return type; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public boolean equals(Object o) { | ||
31 | if (this == o) return true; | ||
32 | if (o == null || getClass() != o.getClass()) return false; | ||
33 | AbstractTerm<?> that = (AbstractTerm<?>) o; | ||
34 | return type.equals(that.type); | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public int hashCode() { | ||
39 | return Objects.hash(getClass(), type); | ||
40 | } | ||
41 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java new file mode 100644 index 00000000..0684a9d9 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | public interface Aggregator<R, T> { | ||
11 | Class<R> getResultType(); | ||
12 | |||
13 | Class<T> getInputType(); | ||
14 | |||
15 | R aggregateStream(Stream<T> stream); | ||
16 | |||
17 | R getEmptyResult(); | ||
18 | } | ||
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 new file mode 100644 index 00000000..192c39c5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | |||
11 | import java.util.Optional; | ||
12 | import java.util.Set; | ||
13 | |||
14 | public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable { | ||
15 | protected AnyDataVariable(String name) { | ||
16 | super(name); | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | public Optional<Class<?>> tryGetType() { | ||
21 | return Optional.of(getType()); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public NodeVariable asNodeVariable() { | ||
26 | throw new IllegalStateException("%s is a data variable".formatted(this)); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | ||
31 | return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Set<AnyDataVariable> getInputVariables() { | ||
36 | return Set.of(this); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public boolean isUnifiable() { | ||
41 | return false; | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public abstract AnyDataVariable renew(@Nullable String name); | ||
46 | |||
47 | @Override | ||
48 | public abstract AnyDataVariable renew(); | ||
49 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java new file mode 100644 index 00000000..c12c0166 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | |||
11 | import java.util.Set; | ||
12 | |||
13 | public sealed interface AnyTerm permits AnyDataVariable, Term { | ||
14 | Class<?> getType(); | ||
15 | |||
16 | AnyTerm substitute(Substitution substitution); | ||
17 | |||
18 | boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other); | ||
19 | |||
20 | Set<AnyDataVariable> getInputVariables(); | ||
21 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java new file mode 100644 index 00000000..0cf30aa6 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java | |||
@@ -0,0 +1,13 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.literal.Literal; | ||
9 | |||
10 | @FunctionalInterface | ||
11 | public interface AssignedValue<T> { | ||
12 | Literal toLiteral(DataVariable<T> targetVariable); | ||
13 | } | ||
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 new file mode 100644 index 00000000..8ad17839 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java | |||
@@ -0,0 +1,113 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import java.util.Collections; | ||
13 | import java.util.HashSet; | ||
14 | import java.util.Objects; | ||
15 | import java.util.Set; | ||
16 | |||
17 | public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { | ||
18 | private final Class<T1> leftType; | ||
19 | private final Class<T2> rightType; | ||
20 | private final Term<T1> left; | ||
21 | private final Term<T2> right; | ||
22 | |||
23 | protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) { | ||
24 | super(type); | ||
25 | if (!left.getType().equals(leftType)) { | ||
26 | throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted( | ||
27 | left, leftType.getName(), left.getType().getName())); | ||
28 | } | ||
29 | if (!right.getType().equals(rightType)) { | ||
30 | throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted( | ||
31 | right, rightType.getName(), right.getType().getName())); | ||
32 | } | ||
33 | this.leftType = leftType; | ||
34 | this.rightType = rightType; | ||
35 | this.left = left; | ||
36 | this.right = right; | ||
37 | } | ||
38 | |||
39 | public Class<T1> getLeftType() { | ||
40 | return leftType; | ||
41 | } | ||
42 | |||
43 | public Class<T2> getRightType() { | ||
44 | return rightType; | ||
45 | } | ||
46 | |||
47 | public Term<T1> getLeft() { | ||
48 | return left; | ||
49 | } | ||
50 | |||
51 | public Term<T2> getRight() { | ||
52 | return right; | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public R evaluate(Valuation valuation) { | ||
57 | var leftValue = left.evaluate(valuation); | ||
58 | if (leftValue == null) { | ||
59 | return null; | ||
60 | } | ||
61 | var rightValue = right.evaluate(valuation); | ||
62 | if (rightValue == null) { | ||
63 | return null; | ||
64 | } | ||
65 | return doEvaluate(leftValue, rightValue); | ||
66 | } | ||
67 | |||
68 | protected abstract R doEvaluate(T1 leftValue, T2 rightValue); | ||
69 | |||
70 | @Override | ||
71 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | ||
72 | if (!super.equalsWithSubstitution(helper, other)) { | ||
73 | return false; | ||
74 | } | ||
75 | var otherBinaryTerm = (BinaryTerm<?, ?, ?>) other; | ||
76 | return leftType.equals(otherBinaryTerm.leftType) && | ||
77 | rightType.equals(otherBinaryTerm.rightType) && | ||
78 | left.equalsWithSubstitution(helper, otherBinaryTerm.left) && | ||
79 | right.equalsWithSubstitution(helper, otherBinaryTerm.right); | ||
80 | } | ||
81 | |||
82 | @Override | ||
83 | public Term<R> substitute(Substitution substitution) { | ||
84 | return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); | ||
85 | } | ||
86 | |||
87 | public abstract Term<R> doSubstitute(Substitution substitution, Term<T1> substitutedLeft, | ||
88 | Term<T2> substitutedRight); | ||
89 | |||
90 | @Override | ||
91 | public Set<AnyDataVariable> getInputVariables() { | ||
92 | var inputVariables = new HashSet<>(left.getInputVariables()); | ||
93 | inputVariables.addAll(right.getInputVariables()); | ||
94 | return Collections.unmodifiableSet(inputVariables); | ||
95 | } | ||
96 | |||
97 | @Override | ||
98 | public boolean equals(Object o) { | ||
99 | if (this == o) return true; | ||
100 | if (o == null || getClass() != o.getClass()) return false; | ||
101 | if (!super.equals(o)) return false; | ||
102 | BinaryTerm<?, ?, ?> that = (BinaryTerm<?, ?, ?>) o; | ||
103 | return Objects.equals(leftType, that.leftType) && | ||
104 | Objects.equals(rightType, that.rightType) && | ||
105 | Objects.equals(left, that.left) && | ||
106 | Objects.equals(right, that.right); | ||
107 | } | ||
108 | |||
109 | @Override | ||
110 | public int hashCode() { | ||
111 | return Objects.hash(super.hashCode(), leftType, rightType, left, right); | ||
112 | } | ||
113 | } | ||
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 new file mode 100644 index 00000000..2f6c56d1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java | |||
@@ -0,0 +1,71 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public final class ConstantTerm<T> extends AbstractTerm<T> { | ||
16 | private final T value; | ||
17 | |||
18 | public ConstantTerm(Class<T> type, T value) { | ||
19 | super(type); | ||
20 | if (value != null && !type.isInstance(value)) { | ||
21 | throw new IllegalArgumentException("Value %s is not an instance of %s".formatted(value, type.getName())); | ||
22 | } | ||
23 | this.value = value; | ||
24 | } | ||
25 | |||
26 | public T getValue() { | ||
27 | return value; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public T evaluate(Valuation valuation) { | ||
32 | return getValue(); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public Term<T> substitute(Substitution substitution) { | ||
37 | return this; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | ||
42 | if (!super.equalsWithSubstitution(helper, other)) { | ||
43 | return false; | ||
44 | } | ||
45 | var otherConstantTerm = (ConstantTerm<?>) other; | ||
46 | return Objects.equals(value, otherConstantTerm.value); | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public Set<AnyDataVariable> getInputVariables() { | ||
51 | return Set.of(); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public String toString() { | ||
56 | return value.toString(); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public boolean equals(Object o) { | ||
61 | if (this == o) return true; | ||
62 | if (o == null || getClass() != o.getClass()) return false; | ||
63 | ConstantTerm<?> that = (ConstantTerm<?>) o; | ||
64 | return Objects.equals(value, that.value); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public int hashCode() { | ||
69 | return Objects.hash(value); | ||
70 | } | ||
71 | } | ||
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 new file mode 100644 index 00000000..00950360 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java | |||
@@ -0,0 +1,82 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.literal.Literal; | ||
11 | import tools.refinery.store.query.substitution.Substitution; | ||
12 | import tools.refinery.store.query.valuation.Valuation; | ||
13 | |||
14 | import java.util.Objects; | ||
15 | |||
16 | public final class DataVariable<T> extends AnyDataVariable implements Term<T> { | ||
17 | private final Class<T> type; | ||
18 | |||
19 | DataVariable(String name, Class<T> type) { | ||
20 | super(name); | ||
21 | this.type = type; | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public Class<T> getType() { | ||
26 | return type; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public DataVariable<T> renew(@Nullable String name) { | ||
31 | return new DataVariable<>(name, type); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public DataVariable<T> renew() { | ||
36 | return renew(getExplicitName()); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public <U> DataVariable<U> asDataVariable(Class<U> newType) { | ||
41 | if (!getType().equals(newType)) { | ||
42 | throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(), | ||
43 | getType().getName())); | ||
44 | } | ||
45 | @SuppressWarnings("unchecked") | ||
46 | var result = (DataVariable<U>) this; | ||
47 | return result; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public T evaluate(Valuation valuation) { | ||
52 | return valuation.getValue(this); | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public Term<T> substitute(Substitution substitution) { | ||
57 | return substitution.getTypeSafeSubstitute(this); | ||
58 | } | ||
59 | |||
60 | @Override | ||
61 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | ||
62 | return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable); | ||
63 | } | ||
64 | |||
65 | public Literal assign(AssignedValue<T> value) { | ||
66 | return value.toLiteral(this); | ||
67 | } | ||
68 | |||
69 | @Override | ||
70 | public boolean equals(Object o) { | ||
71 | if (this == o) return true; | ||
72 | if (o == null || getClass() != o.getClass()) return false; | ||
73 | if (!super.equals(o)) return false; | ||
74 | DataVariable<?> that = (DataVariable<?>) o; | ||
75 | return type.equals(that.type); | ||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public int hashCode() { | ||
80 | return Objects.hash(super.hashCode(), type); | ||
81 | } | ||
82 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java new file mode 100644 index 00000000..657cb631 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java | |||
@@ -0,0 +1,108 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.Comparator; | ||
9 | import java.util.Objects; | ||
10 | import java.util.SortedMap; | ||
11 | import java.util.TreeMap; | ||
12 | |||
13 | public class ExtremeValueAggregator<T> implements StatefulAggregator<T, T> { | ||
14 | private final Class<T> type; | ||
15 | private final T emptyResult; | ||
16 | private final Comparator<T> comparator; | ||
17 | |||
18 | public ExtremeValueAggregator(Class<T> type, T emptyResult) { | ||
19 | this(type, emptyResult, null); | ||
20 | } | ||
21 | |||
22 | public ExtremeValueAggregator(Class<T> type, T emptyResult, Comparator<T> comparator) { | ||
23 | this.type = type; | ||
24 | this.emptyResult = emptyResult; | ||
25 | this.comparator = comparator; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Class<T> getResultType() { | ||
30 | return getInputType(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public Class<T> getInputType() { | ||
35 | return type; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public StatefulAggregate<T, T> createEmptyAggregate() { | ||
40 | return new Aggregate(); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public T getEmptyResult() { | ||
45 | return emptyResult; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public boolean equals(Object o) { | ||
50 | if (this == o) return true; | ||
51 | if (o == null || getClass() != o.getClass()) return false; | ||
52 | ExtremeValueAggregator<?> that = (ExtremeValueAggregator<?>) o; | ||
53 | return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator, | ||
54 | that.comparator); | ||
55 | } | ||
56 | |||
57 | @Override | ||
58 | public int hashCode() { | ||
59 | return Objects.hash(type, emptyResult, comparator); | ||
60 | } | ||
61 | |||
62 | private class Aggregate implements StatefulAggregate<T, T> { | ||
63 | private final SortedMap<T, Integer> values; | ||
64 | |||
65 | private Aggregate() { | ||
66 | values = new TreeMap<>(comparator); | ||
67 | } | ||
68 | |||
69 | private Aggregate(Aggregate other) { | ||
70 | values = new TreeMap<>(other.values); | ||
71 | } | ||
72 | |||
73 | @Override | ||
74 | public void add(T value) { | ||
75 | values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1); | ||
76 | } | ||
77 | |||
78 | @Override | ||
79 | public void remove(T value) { | ||
80 | values.compute(value, (theValue, currentCount) -> { | ||
81 | if (currentCount == null || currentCount <= 0) { | ||
82 | throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue)); | ||
83 | } | ||
84 | return currentCount.equals(1) ? null : currentCount - 1; | ||
85 | }); | ||
86 | } | ||
87 | |||
88 | @Override | ||
89 | public T getResult() { | ||
90 | return isEmpty() ? emptyResult : values.firstKey(); | ||
91 | } | ||
92 | |||
93 | @Override | ||
94 | public boolean isEmpty() { | ||
95 | return values.isEmpty(); | ||
96 | } | ||
97 | |||
98 | @Override | ||
99 | public StatefulAggregate<T, T> deepCopy() { | ||
100 | return new Aggregate(this); | ||
101 | } | ||
102 | |||
103 | @Override | ||
104 | public boolean contains(T value) { | ||
105 | return StatefulAggregate.super.contains(value); | ||
106 | } | ||
107 | } | ||
108 | } | ||
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 new file mode 100644 index 00000000..a2f3261f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.literal.ConstantLiteral; | ||
10 | import tools.refinery.store.query.literal.EquivalenceLiteral; | ||
11 | |||
12 | import java.util.Optional; | ||
13 | |||
14 | public final class NodeVariable extends Variable { | ||
15 | NodeVariable(@Nullable String name) { | ||
16 | super(name); | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | public Optional<Class<?>> tryGetType() { | ||
21 | return Optional.empty(); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public boolean isUnifiable() { | ||
26 | return true; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public NodeVariable renew(@Nullable String name) { | ||
31 | return Variable.of(name); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public NodeVariable renew() { | ||
36 | return renew(getExplicitName()); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public NodeVariable asNodeVariable() { | ||
41 | return this; | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public <T> DataVariable<T> asDataVariable(Class<T> type) { | ||
46 | throw new IllegalStateException("%s is a node variable".formatted(this)); | ||
47 | } | ||
48 | |||
49 | public ConstantLiteral isConstant(int value) { | ||
50 | return new ConstantLiteral(this, value); | ||
51 | } | ||
52 | |||
53 | public EquivalenceLiteral isEquivalent(NodeVariable other) { | ||
54 | return new EquivalenceLiteral(true, this, other); | ||
55 | } | ||
56 | |||
57 | public EquivalenceLiteral notEquivalent(NodeVariable other) { | ||
58 | return new EquivalenceLiteral(false, this, other); | ||
59 | } | ||
60 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java new file mode 100644 index 00000000..e5a0cdf1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java | |||
@@ -0,0 +1,60 @@ | |||
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.term; | ||
7 | |||
8 | import java.util.Objects; | ||
9 | import java.util.Optional; | ||
10 | |||
11 | public class Parameter { | ||
12 | public static final Parameter NODE_OUT = new Parameter(null, ParameterDirection.OUT); | ||
13 | |||
14 | private final Class<?> dataType; | ||
15 | private final ParameterDirection direction; | ||
16 | |||
17 | public Parameter(Class<?> dataType, ParameterDirection direction) { | ||
18 | this.dataType = dataType; | ||
19 | this.direction = direction; | ||
20 | } | ||
21 | |||
22 | public boolean isNodeVariable() { | ||
23 | return dataType == null; | ||
24 | } | ||
25 | |||
26 | public boolean isDataVariable() { | ||
27 | return !isNodeVariable(); | ||
28 | } | ||
29 | |||
30 | public Optional<Class<?>> tryGetType() { | ||
31 | return Optional.ofNullable(dataType); | ||
32 | } | ||
33 | |||
34 | public ParameterDirection getDirection() { | ||
35 | return direction; | ||
36 | } | ||
37 | |||
38 | public boolean isAssignable(Variable variable) { | ||
39 | if (variable instanceof AnyDataVariable dataVariable) { | ||
40 | return dataVariable.getType().equals(dataType); | ||
41 | } else if (variable instanceof NodeVariable) { | ||
42 | return !isDataVariable(); | ||
43 | } else { | ||
44 | throw new IllegalArgumentException("Unknown variable " + variable); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public boolean equals(Object o) { | ||
50 | if (this == o) return true; | ||
51 | if (o == null || getClass() != o.getClass()) return false; | ||
52 | Parameter parameter = (Parameter) o; | ||
53 | return Objects.equals(dataType, parameter.dataType) && direction == parameter.direction; | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public int hashCode() { | ||
58 | return Objects.hash(dataType, direction); | ||
59 | } | ||
60 | } | ||
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 new file mode 100644 index 00000000..cd0739be --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java | |||
@@ -0,0 +1,22 @@ | |||
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.term; | ||
7 | |||
8 | public enum ParameterDirection { | ||
9 | OUT("@Out"), | ||
10 | IN("@In"); | ||
11 | |||
12 | private final String name; | ||
13 | |||
14 | ParameterDirection(String name) { | ||
15 | this.name = name; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public String toString() { | ||
20 | return name; | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java new file mode 100644 index 00000000..ab310556 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | public interface StatefulAggregate<R, T> { | ||
9 | void add(T value); | ||
10 | |||
11 | void remove(T value); | ||
12 | |||
13 | R getResult(); | ||
14 | |||
15 | boolean isEmpty(); | ||
16 | |||
17 | StatefulAggregate<R, T> deepCopy(); | ||
18 | |||
19 | default boolean contains(T value) { | ||
20 | throw new UnsupportedOperationException(); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java new file mode 100644 index 00000000..df746a90 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java | |||
@@ -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 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | public interface StatefulAggregator<R, T> extends Aggregator<R, T> { | ||
11 | StatefulAggregate<R, T> createEmptyAggregate(); | ||
12 | |||
13 | @Override | ||
14 | default R aggregateStream(Stream<T> stream) { | ||
15 | var accumulator = createEmptyAggregate(); | ||
16 | var iterator = stream.iterator(); | ||
17 | while (iterator.hasNext()) { | ||
18 | var value = iterator.next(); | ||
19 | accumulator.add(value); | ||
20 | } | ||
21 | return accumulator.getResult(); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | default R getEmptyResult() { | ||
26 | return createEmptyAggregate().getResult(); | ||
27 | } | ||
28 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java new file mode 100644 index 00000000..a094919e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java | |||
@@ -0,0 +1,25 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import java.util.stream.Stream; | ||
9 | |||
10 | public interface StatelessAggregator<R, T> extends Aggregator<R, T> { | ||
11 | R add(R current, T value); | ||
12 | |||
13 | R remove(R current, T value); | ||
14 | |||
15 | @Override | ||
16 | default R aggregateStream(Stream<T> stream) { | ||
17 | var accumulator = getEmptyResult(); | ||
18 | var iterator = stream.iterator(); | ||
19 | while (iterator.hasNext()) { | ||
20 | var value = iterator.next(); | ||
21 | accumulator = add(accumulator, value); | ||
22 | } | ||
23 | return accumulator; | ||
24 | } | ||
25 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java new file mode 100644 index 00000000..e6818b88 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java | |||
@@ -0,0 +1,26 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.literal.AssignLiteral; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.substitution.Substitution; | ||
11 | import tools.refinery.store.query.valuation.Valuation; | ||
12 | |||
13 | public non-sealed interface Term<T> extends AnyTerm, AssignedValue<T> { | ||
14 | @Override | ||
15 | Class<T> getType(); | ||
16 | |||
17 | T evaluate(Valuation valuation); | ||
18 | |||
19 | @Override | ||
20 | Term<T> substitute(Substitution substitution); | ||
21 | |||
22 | @Override | ||
23 | default Literal toLiteral(DataVariable<T> targetVariable) { | ||
24 | return new AssignLiteral<>(targetVariable, this); | ||
25 | } | ||
26 | } | ||
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 new file mode 100644 index 00000000..a46ebe31 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java | |||
@@ -0,0 +1,79 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { | ||
16 | private final Class<T> bodyType; | ||
17 | private final Term<T> body; | ||
18 | |||
19 | protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) { | ||
20 | super(type); | ||
21 | if (!body.getType().equals(bodyType)) { | ||
22 | throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body, | ||
23 | bodyType.getName(), body.getType().getName())); | ||
24 | } | ||
25 | this.bodyType = bodyType; | ||
26 | this.body = body; | ||
27 | } | ||
28 | |||
29 | public Class<T> getBodyType() { | ||
30 | return bodyType; | ||
31 | } | ||
32 | |||
33 | public Term<T> getBody() { | ||
34 | return body; | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public R evaluate(Valuation valuation) { | ||
39 | var bodyValue = body.evaluate(valuation); | ||
40 | return bodyValue == null ? null : doEvaluate(bodyValue); | ||
41 | } | ||
42 | |||
43 | protected abstract R doEvaluate(T bodyValue); | ||
44 | |||
45 | @Override | ||
46 | public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { | ||
47 | if (!super.equalsWithSubstitution(helper, other)) { | ||
48 | return false; | ||
49 | } | ||
50 | var otherUnaryTerm = (UnaryTerm<?, ?>) other; | ||
51 | return bodyType.equals(otherUnaryTerm.bodyType) && body.equalsWithSubstitution(helper, otherUnaryTerm.body); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public Term<R> substitute(Substitution substitution) { | ||
56 | return doSubstitute(substitution, body.substitute(substitution)); | ||
57 | } | ||
58 | |||
59 | protected abstract Term<R> doSubstitute(Substitution substitution, Term<T> substitutedBody); | ||
60 | |||
61 | @Override | ||
62 | public Set<AnyDataVariable> getInputVariables() { | ||
63 | return body.getInputVariables(); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public boolean equals(Object o) { | ||
68 | if (this == o) return true; | ||
69 | if (o == null || getClass() != o.getClass()) return false; | ||
70 | if (!super.equals(o)) return false; | ||
71 | UnaryTerm<?, ?> unaryTerm = (UnaryTerm<?, ?>) o; | ||
72 | return Objects.equals(bodyType, unaryTerm.bodyType) && Objects.equals(body, unaryTerm.body); | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public int hashCode() { | ||
77 | return Objects.hash(super.hashCode(), bodyType, body); | ||
78 | } | ||
79 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java new file mode 100644 index 00000000..a0268c8e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.dnf.DnfUtils; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | import java.util.Optional; | ||
13 | |||
14 | public abstract sealed class Variable permits AnyDataVariable, NodeVariable { | ||
15 | private final String explicitName; | ||
16 | private final String uniqueName; | ||
17 | |||
18 | protected Variable(String name) { | ||
19 | this.explicitName = name; | ||
20 | uniqueName = DnfUtils.generateUniqueName(name); | ||
21 | } | ||
22 | |||
23 | public abstract Optional<Class<?>> tryGetType(); | ||
24 | |||
25 | public String getName() { | ||
26 | return explicitName == null ? uniqueName : explicitName; | ||
27 | } | ||
28 | |||
29 | protected String getExplicitName() { | ||
30 | return explicitName; | ||
31 | } | ||
32 | |||
33 | public boolean isExplicitlyNamed() { | ||
34 | return explicitName != null; | ||
35 | } | ||
36 | |||
37 | public String getUniqueName() { | ||
38 | return uniqueName; | ||
39 | } | ||
40 | |||
41 | public abstract boolean isUnifiable(); | ||
42 | |||
43 | public abstract Variable renew(@Nullable String name); | ||
44 | |||
45 | public abstract Variable renew(); | ||
46 | |||
47 | public abstract NodeVariable asNodeVariable(); | ||
48 | |||
49 | public abstract <T> DataVariable<T> asDataVariable(Class<T> type); | ||
50 | |||
51 | @Override | ||
52 | public String toString() { | ||
53 | return getName(); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public boolean equals(Object o) { | ||
58 | if (this == o) return true; | ||
59 | if (o == null || getClass() != o.getClass()) return false; | ||
60 | Variable variable = (Variable) o; | ||
61 | return Objects.equals(uniqueName, variable.uniqueName); | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public int hashCode() { | ||
66 | return Objects.hash(uniqueName); | ||
67 | } | ||
68 | |||
69 | public static NodeVariable of(@Nullable String name) { | ||
70 | return new NodeVariable(name); | ||
71 | } | ||
72 | |||
73 | public static NodeVariable of() { | ||
74 | return of((String) null); | ||
75 | } | ||
76 | |||
77 | public static <T> DataVariable<T> of(@Nullable String name, Class<T> type) { | ||
78 | return new DataVariable<>(name, type); | ||
79 | } | ||
80 | |||
81 | public static <T> DataVariable<T> of(Class<T> type) { | ||
82 | return of(null, type); | ||
83 | } | ||
84 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java new file mode 100644 index 00000000..f9e1c06f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class BoolAndTerm extends BoolBinaryTerm { | ||
12 | public BoolAndTerm(Term<Boolean> left, Term<Boolean> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft, | ||
18 | Term<Boolean> substitutedRight) { | ||
19 | return new BoolAndTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { | ||
24 | return leftValue && rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s && %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java new file mode 100644 index 00000000..a85aa63a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public abstract class BoolBinaryTerm extends BinaryTerm<Boolean, Boolean, Boolean> { | ||
12 | protected BoolBinaryTerm(Term<Boolean> left, Term<Boolean> right) { | ||
13 | super(Boolean.class, Boolean.class, Boolean.class, left, right); | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java new file mode 100644 index 00000000..8d3382b3 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.query.term.UnaryTerm; | ||
11 | |||
12 | public class BoolNotTerm extends UnaryTerm<Boolean, Boolean> { | ||
13 | protected BoolNotTerm(Term<Boolean> body) { | ||
14 | super(Boolean.class, Boolean.class, body); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedBody) { | ||
19 | return new BoolNotTerm(substitutedBody); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Boolean doEvaluate(Boolean bodyValue) { | ||
24 | return !bodyValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(!%s)".formatted(getBody()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java new file mode 100644 index 00000000..b5195d52 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class BoolOrTerm extends BoolBinaryTerm { | ||
12 | public BoolOrTerm(Term<Boolean> left, Term<Boolean> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft, | ||
18 | Term<Boolean> substitutedRight) { | ||
19 | return new BoolOrTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { | ||
24 | return leftValue || rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s || %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java new file mode 100644 index 00000000..fa54f686 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.term.ConstantTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public final class BoolTerms { | ||
12 | private BoolTerms() { | ||
13 | throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); | ||
14 | } | ||
15 | |||
16 | public static Term<Boolean> constant(Boolean value) { | ||
17 | return new ConstantTerm<>(Boolean.class, value); | ||
18 | } | ||
19 | |||
20 | public static Term<Boolean> not(Term<Boolean> body) { | ||
21 | return new BoolNotTerm(body); | ||
22 | } | ||
23 | |||
24 | public static Term<Boolean> and(Term<Boolean> left, Term<Boolean> right) { | ||
25 | return new BoolAndTerm(left, right); | ||
26 | } | ||
27 | |||
28 | public static Term<Boolean> or(Term<Boolean> left, Term<Boolean> right) { | ||
29 | return new BoolOrTerm(left, right); | ||
30 | } | ||
31 | |||
32 | public static Term<Boolean> xor(Term<Boolean> left, Term<Boolean> right) { | ||
33 | return new BoolXorTerm(left, right); | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java new file mode 100644 index 00000000..7478b8a5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class BoolXorTerm extends BoolBinaryTerm { | ||
12 | public BoolXorTerm(Term<Boolean> left, Term<Boolean> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft, | ||
18 | Term<Boolean> substitutedRight) { | ||
19 | return new BoolXorTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { | ||
24 | return leftValue ^ rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s ^^ %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java new file mode 100644 index 00000000..5ca5a0a1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java | |||
@@ -0,0 +1,19 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public abstract class ComparisonTerm<T> extends BinaryTerm<Boolean, T, T> { | ||
12 | protected ComparisonTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(Boolean.class, argumentType, argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | public Class<T> getArgumentType() { | ||
17 | return getLeftType(); | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java new file mode 100644 index 00000000..b8cf36f8 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class EqTerm<T> extends ComparisonTerm<T> { | ||
12 | public EqTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Boolean doEvaluate(T leftValue, T rightValue) { | ||
18 | return leftValue.equals(rightValue); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) { | ||
23 | return new EqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(%s == %s)".formatted(getLeft(), getRight()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java new file mode 100644 index 00000000..b109eb1a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class GreaterEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> { | ||
12 | public GreaterEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Boolean doEvaluate(T leftValue, T rightValue) { | ||
18 | return leftValue.compareTo(rightValue) >= 0; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) { | ||
23 | return new GreaterEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(%s >= %s)".formatted(getLeft(), getRight()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java new file mode 100644 index 00000000..1b67f8b5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class GreaterTerm<T extends Comparable<T>> extends ComparisonTerm<T> { | ||
12 | public GreaterTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Boolean doEvaluate(T leftValue, T rightValue) { | ||
18 | return leftValue.compareTo(rightValue) > 0; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) { | ||
23 | return new GreaterTerm<>(getArgumentType(), substitutedLeft, substitutedRight); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(%s > %s)".formatted(getLeft(), getRight()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java new file mode 100644 index 00000000..1b34535f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class LessEqTerm<T extends Comparable<T>> extends ComparisonTerm<T> { | ||
12 | public LessEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Boolean doEvaluate(T leftValue, T rightValue) { | ||
18 | return leftValue.compareTo(rightValue) <= 0; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) { | ||
23 | return new LessEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(%s <= %s)".formatted(getLeft(), getRight()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java new file mode 100644 index 00000000..44e70902 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class LessTerm<T extends Comparable<T>> extends ComparisonTerm<T> { | ||
12 | public LessTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Boolean doEvaluate(T leftValue, T rightValue) { | ||
18 | return leftValue.compareTo(rightValue) < 0; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) { | ||
23 | return new LessTerm<>(getArgumentType(), substitutedLeft, substitutedRight); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(%s < %s)".formatted(getLeft(), getRight()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java new file mode 100644 index 00000000..1f9734c4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.comparable; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class NotEqTerm<T> extends ComparisonTerm<T> { | ||
12 | public NotEqTerm(Class<T> argumentType, Term<T> left, Term<T> right) { | ||
13 | super(argumentType, left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Boolean doEvaluate(T leftValue, T rightValue) { | ||
18 | return !leftValue.equals(rightValue); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Term<Boolean> doSubstitute(Substitution substitution, Term<T> substitutedLeft, Term<T> substitutedRight) { | ||
23 | return new NotEqTerm<>(getArgumentType(), substitutedLeft, substitutedRight); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(%s != %s)".formatted(getLeft(), getRight()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java new file mode 100644 index 00000000..dbea3efc --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntAddTerm extends IntBinaryTerm { | ||
12 | public IntAddTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntAddTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return leftValue + rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s + %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java new file mode 100644 index 00000000..27ced4e4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public abstract class IntBinaryTerm extends BinaryTerm<Integer, Integer, Integer> { | ||
12 | protected IntBinaryTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(Integer.class, Integer.class, Integer.class, left, right); | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java new file mode 100644 index 00000000..2a35058c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntDivTerm extends IntBinaryTerm { | ||
12 | public IntDivTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntDivTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return rightValue == 0 ? null : leftValue / rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s / %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java new file mode 100644 index 00000000..f81fc509 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntMaxTerm extends IntBinaryTerm { | ||
12 | public IntMaxTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntMaxTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return Math.max(rightValue, leftValue); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "max(%s, %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java new file mode 100644 index 00000000..89182e26 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntMinTerm extends IntBinaryTerm { | ||
12 | public IntMinTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntMinTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return Math.min(rightValue, leftValue); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "min(%s, %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java new file mode 100644 index 00000000..709aa5ba --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntMinusTerm extends IntUnaryTerm { | ||
12 | public IntMinusTerm(Term<Integer> body) { | ||
13 | super(body); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) { | ||
18 | return new IntMinusTerm(substitutedBody); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | protected Integer doEvaluate(Integer bodyValue) { | ||
23 | return -bodyValue; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(-%s)".formatted(getBody()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java new file mode 100644 index 00000000..89d4c5f4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntMulTerm extends IntBinaryTerm { | ||
12 | public IntMulTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntMulTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return leftValue * rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s * %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java new file mode 100644 index 00000000..aef83bb4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntPlusTerm extends IntUnaryTerm { | ||
12 | public IntPlusTerm(Term<Integer> body) { | ||
13 | super(body); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) { | ||
18 | return new IntPlusTerm(substitutedBody); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | protected Integer doEvaluate(Integer bodyValue) { | ||
23 | return bodyValue; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(+%s)".formatted(getBody()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java new file mode 100644 index 00000000..d5af97a1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java | |||
@@ -0,0 +1,43 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntPowTerm extends IntBinaryTerm { | ||
12 | public IntPowTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntPowTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return rightValue < 0 ? null : power(leftValue, rightValue); | ||
25 | } | ||
26 | |||
27 | private static int power(int base, int exponent) { | ||
28 | int accum = 1; | ||
29 | while (exponent > 0) { | ||
30 | if (exponent % 2 == 1) { | ||
31 | accum = accum * base; | ||
32 | } | ||
33 | base = base * base; | ||
34 | exponent = exponent / 2; | ||
35 | } | ||
36 | return accum; | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public String toString() { | ||
41 | return "(%s ** %s)".formatted(getLeft(), getRight()); | ||
42 | } | ||
43 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java new file mode 100644 index 00000000..2c27afb1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class IntSubTerm extends IntBinaryTerm { | ||
12 | public IntSubTerm(Term<Integer> left, Term<Integer> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft, | ||
18 | Term<Integer> substitutedRight) { | ||
19 | return new IntSubTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Integer doEvaluate(Integer leftValue, Integer rightValue) { | ||
24 | return leftValue - rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s - %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java new file mode 100644 index 00000000..cd718c53 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.StatelessAggregator; | ||
9 | |||
10 | public final class IntSumAggregator implements StatelessAggregator<Integer, Integer> { | ||
11 | public static final IntSumAggregator INSTANCE = new IntSumAggregator(); | ||
12 | |||
13 | private IntSumAggregator() { | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Class<Integer> getResultType() { | ||
18 | return Integer.class; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Class<Integer> getInputType() { | ||
23 | return Integer.class; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public Integer getEmptyResult() { | ||
28 | return 0; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Integer add(Integer current, Integer value) { | ||
33 | return current + value; | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public Integer remove(Integer current, Integer value) { | ||
38 | return current - value; | ||
39 | } | ||
40 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java new file mode 100644 index 00000000..acb98b94 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java | |||
@@ -0,0 +1,94 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.Aggregator; | ||
9 | import tools.refinery.store.query.term.ConstantTerm; | ||
10 | import tools.refinery.store.query.term.ExtremeValueAggregator; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.comparable.*; | ||
13 | |||
14 | import java.util.Comparator; | ||
15 | |||
16 | public final class IntTerms { | ||
17 | public static final Aggregator<Integer, Integer> INT_SUM = IntSumAggregator.INSTANCE; | ||
18 | public static final Aggregator<Integer, Integer> INT_MIN = new ExtremeValueAggregator<>(Integer.class, | ||
19 | Integer.MAX_VALUE); | ||
20 | public static final Aggregator<Integer, Integer> INT_MAX = new ExtremeValueAggregator<>(Integer.class, | ||
21 | Integer.MIN_VALUE, Comparator.reverseOrder()); | ||
22 | |||
23 | private IntTerms() { | ||
24 | throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); | ||
25 | } | ||
26 | |||
27 | public static Term<Integer> constant(Integer value) { | ||
28 | return new ConstantTerm<>(Integer.class, value); | ||
29 | } | ||
30 | |||
31 | public static Term<Integer> plus(Term<Integer> body) { | ||
32 | return new IntPlusTerm(body); | ||
33 | } | ||
34 | |||
35 | public static Term<Integer> minus(Term<Integer> body) { | ||
36 | return new IntMinusTerm(body); | ||
37 | } | ||
38 | |||
39 | public static Term<Integer> add(Term<Integer> left, Term<Integer> right) { | ||
40 | return new IntAddTerm(left, right); | ||
41 | } | ||
42 | |||
43 | public static Term<Integer> sub(Term<Integer> left, Term<Integer> right) { | ||
44 | return new IntSubTerm(left, right); | ||
45 | } | ||
46 | |||
47 | public static Term<Integer> mul(Term<Integer> left, Term<Integer> right) { | ||
48 | return new IntMulTerm(left, right); | ||
49 | } | ||
50 | |||
51 | public static Term<Integer> div(Term<Integer> left, Term<Integer> right) { | ||
52 | return new IntDivTerm(left, right); | ||
53 | } | ||
54 | |||
55 | public static Term<Integer> pow(Term<Integer> left, Term<Integer> right) { | ||
56 | return new IntPowTerm(left, right); | ||
57 | } | ||
58 | |||
59 | public static Term<Integer> min(Term<Integer> left, Term<Integer> right) { | ||
60 | return new IntMinTerm(left, right); | ||
61 | } | ||
62 | |||
63 | public static Term<Integer> max(Term<Integer> left, Term<Integer> right) { | ||
64 | return new IntMaxTerm(left, right); | ||
65 | } | ||
66 | |||
67 | public static Term<Boolean> eq(Term<Integer> left, Term<Integer> right) { | ||
68 | return new EqTerm<>(Integer.class, left, right); | ||
69 | } | ||
70 | |||
71 | public static Term<Boolean> notEq(Term<Integer> left, Term<Integer> right) { | ||
72 | return new NotEqTerm<>(Integer.class, left, right); | ||
73 | } | ||
74 | |||
75 | public static Term<Boolean> less(Term<Integer> left, Term<Integer> right) { | ||
76 | return new LessTerm<>(Integer.class, left, right); | ||
77 | } | ||
78 | |||
79 | public static Term<Boolean> lessEq(Term<Integer> left, Term<Integer> right) { | ||
80 | return new LessEqTerm<>(Integer.class, left, right); | ||
81 | } | ||
82 | |||
83 | public static Term<Boolean> greater(Term<Integer> left, Term<Integer> right) { | ||
84 | return new GreaterTerm<>(Integer.class, left, right); | ||
85 | } | ||
86 | |||
87 | public static Term<Boolean> greaterEq(Term<Integer> left, Term<Integer> right) { | ||
88 | return new GreaterEqTerm<>(Integer.class, left, right); | ||
89 | } | ||
90 | |||
91 | public static Term<Integer> asInt(Term<Double> body) { | ||
92 | return new RealToIntTerm(body); | ||
93 | } | ||
94 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java new file mode 100644 index 00000000..49b4c647 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | import tools.refinery.store.query.term.UnaryTerm; | ||
10 | |||
11 | public abstract class IntUnaryTerm extends UnaryTerm<Integer, Integer> { | ||
12 | protected IntUnaryTerm(Term<Integer> body) { | ||
13 | super(Integer.class, Integer.class, body); | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java new file mode 100644 index 00000000..7d383562 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.query.term.UnaryTerm; | ||
11 | |||
12 | public class RealToIntTerm extends UnaryTerm<Integer, Double> { | ||
13 | protected RealToIntTerm(Term<Double> body) { | ||
14 | super(Integer.class, Double.class, body); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected Integer doEvaluate(Double bodyValue) { | ||
19 | return bodyValue.isNaN() ? null : bodyValue.intValue(); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Term<Integer> doSubstitute(Substitution substitution, Term<Double> substitutedBody) { | ||
24 | return new RealToIntTerm(substitutedBody); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s as int)".formatted(getBody()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java new file mode 100644 index 00000000..2f53117a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.query.term.UnaryTerm; | ||
11 | |||
12 | public class IntToRealTerm extends UnaryTerm<Double, Integer> { | ||
13 | protected IntToRealTerm(Term<Integer> body) { | ||
14 | super(Double.class, Integer.class, body); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected Term<Double> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) { | ||
19 | return new IntToRealTerm(substitutedBody); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Integer bodyValue) { | ||
24 | return bodyValue.doubleValue(); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s as real)".formatted(getBody()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java new file mode 100644 index 00000000..33fc9e41 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealAddTerm extends RealBinaryTerm { | ||
12 | public RealAddTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealAddTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return leftValue + rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s + %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java new file mode 100644 index 00000000..000f3623 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public abstract class RealBinaryTerm extends BinaryTerm<Double, Double, Double> { | ||
12 | protected RealBinaryTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(Double.class, Double.class, Double.class, left, right); | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java new file mode 100644 index 00000000..1e55bf42 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealDivTerm extends RealBinaryTerm { | ||
12 | public RealDivTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealDivTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return leftValue / rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s / %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java new file mode 100644 index 00000000..2a249496 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealMaxTerm extends RealBinaryTerm { | ||
12 | public RealMaxTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealMaxTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return Math.max(leftValue, rightValue); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "max(%s, %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java new file mode 100644 index 00000000..2eb4cc1e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealMinTerm extends RealBinaryTerm { | ||
12 | public RealMinTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealMinTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return Math.min(leftValue, rightValue); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "min(%s, %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java new file mode 100644 index 00000000..4afec7a1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealMinusTerm extends RealUnaryTerm { | ||
12 | public RealMinusTerm(Term<Double> body) { | ||
13 | super(body); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) { | ||
18 | return new RealMinusTerm(substitutedBody); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | protected Double doEvaluate(Double bodyValue) { | ||
23 | return -bodyValue; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(-%s)".formatted(getBody()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java new file mode 100644 index 00000000..ec95ac6f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealMulTerm extends RealBinaryTerm { | ||
12 | public RealMulTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealMulTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return leftValue * rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s * %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java new file mode 100644 index 00000000..64dd2e70 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealPlusTerm extends RealUnaryTerm { | ||
12 | public RealPlusTerm(Term<Double> body) { | ||
13 | super(body); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) { | ||
18 | return new RealPlusTerm(substitutedBody); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | protected Double doEvaluate(Double bodyValue) { | ||
23 | return bodyValue; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public String toString() { | ||
28 | return "(+%s)".formatted(getBody()); | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java new file mode 100644 index 00000000..11c952ea --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealPowTerm extends RealBinaryTerm { | ||
12 | public RealPowTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealPowTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return Math.pow(leftValue, rightValue); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s ** %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java new file mode 100644 index 00000000..8cc701ed --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | |||
11 | public class RealSubTerm extends RealBinaryTerm { | ||
12 | public RealSubTerm(Term<Double> left, Term<Double> right) { | ||
13 | super(left, right); | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft, | ||
18 | Term<Double> substitutedRight) { | ||
19 | return new RealSubTerm(substitutedLeft, substitutedRight); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected Double doEvaluate(Double leftValue, Double rightValue) { | ||
24 | return leftValue - rightValue; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s - %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java new file mode 100644 index 00000000..d21048e9 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java | |||
@@ -0,0 +1,90 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.StatefulAggregate; | ||
9 | import tools.refinery.store.query.term.StatefulAggregator; | ||
10 | |||
11 | import java.util.Map; | ||
12 | import java.util.TreeMap; | ||
13 | |||
14 | public final class RealSumAggregator implements StatefulAggregator<Double, Double> { | ||
15 | public static final RealSumAggregator INSTANCE = new RealSumAggregator(); | ||
16 | |||
17 | private RealSumAggregator() { | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public Class<Double> getResultType() { | ||
22 | return Double.class; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public Class<Double> getInputType() { | ||
27 | return Double.class; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public StatefulAggregate<Double, Double> createEmptyAggregate() { | ||
32 | return new Aggregate(); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public Double getEmptyResult() { | ||
37 | return 0d; | ||
38 | } | ||
39 | |||
40 | private static class Aggregate implements StatefulAggregate<Double, Double> { | ||
41 | private final Map<Double, Integer> values; | ||
42 | |||
43 | public Aggregate() { | ||
44 | values = new TreeMap<>(); | ||
45 | } | ||
46 | |||
47 | private Aggregate(Aggregate other) { | ||
48 | values = new TreeMap<>(other.values); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public void add(Double value) { | ||
53 | values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public void remove(Double value) { | ||
58 | values.compute(value, (theValue, currentCount) -> { | ||
59 | if (currentCount == null || currentCount <= 0) { | ||
60 | throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue)); | ||
61 | } | ||
62 | return currentCount.equals(1) ? null : currentCount - 1; | ||
63 | }); | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public Double getResult() { | ||
68 | return values.entrySet() | ||
69 | .stream() | ||
70 | .mapToDouble(entry -> entry.getKey() * entry.getValue()) | ||
71 | .reduce(Double::sum) | ||
72 | .orElse(0d); | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public boolean isEmpty() { | ||
77 | return values.isEmpty(); | ||
78 | } | ||
79 | |||
80 | @Override | ||
81 | public StatefulAggregate<Double, Double> deepCopy() { | ||
82 | return new Aggregate(this); | ||
83 | } | ||
84 | |||
85 | @Override | ||
86 | public boolean contains(Double value) { | ||
87 | return values.containsKey(value); | ||
88 | } | ||
89 | } | ||
90 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java new file mode 100644 index 00000000..79220358 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java | |||
@@ -0,0 +1,94 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.Aggregator; | ||
9 | import tools.refinery.store.query.term.ConstantTerm; | ||
10 | import tools.refinery.store.query.term.ExtremeValueAggregator; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.comparable.*; | ||
13 | |||
14 | import java.util.Comparator; | ||
15 | |||
16 | public final class RealTerms { | ||
17 | public static final Aggregator<Double, Double> REAL_SUM = RealSumAggregator.INSTANCE; | ||
18 | public static final Aggregator<Double, Double> REAL_MIN = new ExtremeValueAggregator<>(Double.class, | ||
19 | Double.POSITIVE_INFINITY); | ||
20 | public static final Aggregator<Double, Double> REAL_MAX = new ExtremeValueAggregator<>(Double.class, | ||
21 | Double.NEGATIVE_INFINITY, Comparator.reverseOrder()); | ||
22 | |||
23 | private RealTerms() { | ||
24 | throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); | ||
25 | } | ||
26 | |||
27 | public static Term<Double> constant(Double value) { | ||
28 | return new ConstantTerm<>(Double.class, value); | ||
29 | } | ||
30 | |||
31 | public static Term<Double> plus(Term<Double> body) { | ||
32 | return new RealPlusTerm(body); | ||
33 | } | ||
34 | |||
35 | public static Term<Double> minus(Term<Double> body) { | ||
36 | return new RealMinusTerm(body); | ||
37 | } | ||
38 | |||
39 | public static Term<Double> add(Term<Double> left, Term<Double> right) { | ||
40 | return new RealAddTerm(left, right); | ||
41 | } | ||
42 | |||
43 | public static Term<Double> sub(Term<Double> left, Term<Double> right) { | ||
44 | return new RealSubTerm(left, right); | ||
45 | } | ||
46 | |||
47 | public static Term<Double> mul(Term<Double> left, Term<Double> right) { | ||
48 | return new RealMulTerm(left, right); | ||
49 | } | ||
50 | |||
51 | public static Term<Double> div(Term<Double> left, Term<Double> right) { | ||
52 | return new RealDivTerm(left, right); | ||
53 | } | ||
54 | |||
55 | public static Term<Double> pow(Term<Double> left, Term<Double> right) { | ||
56 | return new RealPowTerm(left, right); | ||
57 | } | ||
58 | |||
59 | public static Term<Double> min(Term<Double> left, Term<Double> right) { | ||
60 | return new RealMinTerm(left, right); | ||
61 | } | ||
62 | |||
63 | public static Term<Double> max(Term<Double> left, Term<Double> right) { | ||
64 | return new RealMaxTerm(left, right); | ||
65 | } | ||
66 | |||
67 | public static Term<Boolean> eq(Term<Double> left, Term<Double> right) { | ||
68 | return new EqTerm<>(Double.class, left, right); | ||
69 | } | ||
70 | |||
71 | public static Term<Boolean> notEq(Term<Double> left, Term<Double> right) { | ||
72 | return new NotEqTerm<>(Double.class, left, right); | ||
73 | } | ||
74 | |||
75 | public static Term<Boolean> less(Term<Double> left, Term<Double> right) { | ||
76 | return new LessTerm<>(Double.class, left, right); | ||
77 | } | ||
78 | |||
79 | public static Term<Boolean> lessEq(Term<Double> left, Term<Double> right) { | ||
80 | return new LessEqTerm<>(Double.class, left, right); | ||
81 | } | ||
82 | |||
83 | public static Term<Boolean> greater(Term<Double> left, Term<Double> right) { | ||
84 | return new GreaterTerm<>(Double.class, left, right); | ||
85 | } | ||
86 | |||
87 | public static Term<Boolean> greaterEq(Term<Double> left, Term<Double> right) { | ||
88 | return new GreaterEqTerm<>(Double.class, left, right); | ||
89 | } | ||
90 | |||
91 | public static Term<Double> asReal(Term<Integer> body) { | ||
92 | return new IntToRealTerm(body); | ||
93 | } | ||
94 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java new file mode 100644 index 00000000..d41c4ed9 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import tools.refinery.store.query.term.Term; | ||
9 | import tools.refinery.store.query.term.UnaryTerm; | ||
10 | |||
11 | public abstract class RealUnaryTerm extends UnaryTerm<Double, Double> { | ||
12 | protected RealUnaryTerm(Term<Double> body) { | ||
13 | super(Double.class, Double.class, body); | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java new file mode 100644 index 00000000..68905f51 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public class UpperCardinalityAddTerm extends UpperCardinalityBinaryTerm { | ||
13 | protected UpperCardinalityAddTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
14 | super(left, right); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { | ||
19 | return leftValue.add(rightValue); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) { | ||
24 | return new UpperCardinalityAddTerm(substitutedLeft, substitutedRight); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s + %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java new file mode 100644 index 00000000..0cf8fe44 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.term.BinaryTerm; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public abstract class UpperCardinalityBinaryTerm extends BinaryTerm<UpperCardinality, UpperCardinality, | ||
13 | UpperCardinality> { | ||
14 | protected UpperCardinalityBinaryTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
15 | super(UpperCardinality.class, UpperCardinality.class, UpperCardinality.class, left, right); | ||
16 | } | ||
17 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java new file mode 100644 index 00000000..ff75f64e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public class UpperCardinalityMaxTerm extends UpperCardinalityBinaryTerm { | ||
13 | protected UpperCardinalityMaxTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
14 | super(left, right); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { | ||
19 | return leftValue.max(rightValue); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) { | ||
24 | return new UpperCardinalityMaxTerm(substitutedLeft, substitutedRight); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "max(%s, %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java new file mode 100644 index 00000000..1e89e9f4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public class UpperCardinalityMinTerm extends UpperCardinalityBinaryTerm { | ||
13 | protected UpperCardinalityMinTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
14 | super(left, right); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { | ||
19 | return leftValue.min(rightValue); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) { | ||
24 | return new UpperCardinalityMinTerm(substitutedLeft, substitutedRight); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "min(%s, %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java new file mode 100644 index 00000000..3b4970f4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.Term; | ||
10 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
11 | |||
12 | public class UpperCardinalityMulTerm extends UpperCardinalityBinaryTerm { | ||
13 | protected UpperCardinalityMulTerm(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
14 | super(left, right); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected UpperCardinality doEvaluate(UpperCardinality leftValue, UpperCardinality rightValue) { | ||
19 | return leftValue.multiply(rightValue); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Term<UpperCardinality> doSubstitute(Substitution substitution, Term<UpperCardinality> substitutedLeft, Term<UpperCardinality> substitutedRight) { | ||
24 | return new UpperCardinalityMulTerm(substitutedLeft, substitutedRight); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString() { | ||
29 | return "(%s * %s)".formatted(getLeft(), getRight()); | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java new file mode 100644 index 00000000..5bbd3081 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java | |||
@@ -0,0 +1,86 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.term.StatefulAggregate; | ||
9 | import tools.refinery.store.query.term.StatefulAggregator; | ||
10 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | ||
11 | import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
13 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
14 | |||
15 | public class UpperCardinalitySumAggregator implements StatefulAggregator<UpperCardinality, UpperCardinality> { | ||
16 | public static final UpperCardinalitySumAggregator INSTANCE = new UpperCardinalitySumAggregator(); | ||
17 | |||
18 | private UpperCardinalitySumAggregator() { | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Class<UpperCardinality> getResultType() { | ||
23 | return UpperCardinality.class; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public Class<UpperCardinality> getInputType() { | ||
28 | return UpperCardinality.class; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public StatefulAggregate<UpperCardinality, UpperCardinality> createEmptyAggregate() { | ||
33 | return new Aggregate(); | ||
34 | } | ||
35 | |||
36 | private static class Aggregate implements StatefulAggregate<UpperCardinality, UpperCardinality> { | ||
37 | private int sumFiniteUpperBounds; | ||
38 | private int countUnbounded; | ||
39 | |||
40 | public Aggregate() { | ||
41 | this(0, 0); | ||
42 | } | ||
43 | |||
44 | private Aggregate(int sumFiniteUpperBounds, int countUnbounded) { | ||
45 | this.sumFiniteUpperBounds = sumFiniteUpperBounds; | ||
46 | this.countUnbounded = countUnbounded; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public void add(UpperCardinality value) { | ||
51 | if (value instanceof FiniteUpperCardinality finiteUpperCardinality) { | ||
52 | sumFiniteUpperBounds += finiteUpperCardinality.finiteUpperBound(); | ||
53 | } else if (value instanceof UnboundedUpperCardinality) { | ||
54 | countUnbounded += 1; | ||
55 | } else { | ||
56 | throw new IllegalArgumentException("Unknown UpperCardinality: " + value); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | @Override | ||
61 | public void remove(UpperCardinality value) { | ||
62 | if (value instanceof FiniteUpperCardinality finiteUpperCardinality) { | ||
63 | sumFiniteUpperBounds -= finiteUpperCardinality.finiteUpperBound(); | ||
64 | } else if (value instanceof UnboundedUpperCardinality) { | ||
65 | countUnbounded -= 1; | ||
66 | } else { | ||
67 | throw new IllegalArgumentException("Unknown UpperCardinality: " + value); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | @Override | ||
72 | public UpperCardinality getResult() { | ||
73 | return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.valueOf(sumFiniteUpperBounds); | ||
74 | } | ||
75 | |||
76 | @Override | ||
77 | public boolean isEmpty() { | ||
78 | return sumFiniteUpperBounds == 0 && countUnbounded == 0; | ||
79 | } | ||
80 | |||
81 | @Override | ||
82 | public StatefulAggregate<UpperCardinality, UpperCardinality> deepCopy() { | ||
83 | return new Aggregate(sumFiniteUpperBounds, countUnbounded); | ||
84 | } | ||
85 | } | ||
86 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java new file mode 100644 index 00000000..13914f2d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java | |||
@@ -0,0 +1,73 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import tools.refinery.store.query.term.Aggregator; | ||
9 | import tools.refinery.store.query.term.ConstantTerm; | ||
10 | import tools.refinery.store.query.term.ExtremeValueAggregator; | ||
11 | import tools.refinery.store.query.term.Term; | ||
12 | import tools.refinery.store.query.term.comparable.*; | ||
13 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
14 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
15 | |||
16 | import java.util.Comparator; | ||
17 | |||
18 | public final class UpperCardinalityTerms { | ||
19 | public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_SUM = | ||
20 | UpperCardinalitySumAggregator.INSTANCE; | ||
21 | public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MIN = | ||
22 | new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.UNBOUNDED); | ||
23 | public static final Aggregator<UpperCardinality, UpperCardinality> UPPER_CARDINALITY_MAX = | ||
24 | new ExtremeValueAggregator<>(UpperCardinality.class, UpperCardinalities.ZERO, Comparator.reverseOrder()); | ||
25 | |||
26 | private UpperCardinalityTerms() { | ||
27 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
28 | } | ||
29 | |||
30 | public static Term<UpperCardinality> constant(UpperCardinality value) { | ||
31 | return new ConstantTerm<>(UpperCardinality.class, value); | ||
32 | } | ||
33 | |||
34 | public static Term<UpperCardinality> add(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
35 | return new UpperCardinalityAddTerm(left, right); | ||
36 | } | ||
37 | |||
38 | public static Term<UpperCardinality> mul(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
39 | return new UpperCardinalityMulTerm(left, right); | ||
40 | } | ||
41 | |||
42 | public static Term<UpperCardinality> min(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
43 | return new UpperCardinalityMinTerm(left, right); | ||
44 | } | ||
45 | |||
46 | public static Term<UpperCardinality> max(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
47 | return new UpperCardinalityMaxTerm(left, right); | ||
48 | } | ||
49 | |||
50 | public static Term<Boolean> eq(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
51 | return new EqTerm<>(UpperCardinality.class, left, right); | ||
52 | } | ||
53 | |||
54 | public static Term<Boolean> notEq(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
55 | return new NotEqTerm<>(UpperCardinality.class, left, right); | ||
56 | } | ||
57 | |||
58 | public static Term<Boolean> less(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
59 | return new LessTerm<>(UpperCardinality.class, left, right); | ||
60 | } | ||
61 | |||
62 | public static Term<Boolean> lessEq(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
63 | return new LessEqTerm<>(UpperCardinality.class, left, right); | ||
64 | } | ||
65 | |||
66 | public static Term<Boolean> greater(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
67 | return new GreaterTerm<>(UpperCardinality.class, left, right); | ||
68 | } | ||
69 | |||
70 | public static Term<Boolean> greaterEq(Term<UpperCardinality> left, Term<UpperCardinality> right) { | ||
71 | return new GreaterEqTerm<>(UpperCardinality.class, left, right); | ||
72 | } | ||
73 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java new file mode 100644 index 00000000..b568b99d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java | |||
@@ -0,0 +1,754 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2021 Rodion Efremov | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | */ | ||
7 | package tools.refinery.store.query.utils; | ||
8 | |||
9 | import java.util.*; | ||
10 | |||
11 | /** | ||
12 | * This class implements an order statistic tree which is based on AVL-trees. | ||
13 | * <p> | ||
14 | * This class was copied into <i>Refinery</i> from | ||
15 | * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a> | ||
16 | * and is available under the | ||
17 | * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>. | ||
18 | * We also incorporated changes by <a href="https://github.com/coderodde/OrderStatisticTree/issues/3">Eugene Schava</a> | ||
19 | * and cleaned up some linter warnings. | ||
20 | * | ||
21 | * @param <T> the actual element type. | ||
22 | * @author Rodion "rodde" Efremov | ||
23 | * @version based on 1.6 (Feb 11, 2016) | ||
24 | */ | ||
25 | public class OrderStatisticTree<T extends Comparable<? super T>> implements Set<T> { | ||
26 | |||
27 | @Override | ||
28 | public Iterator<T> iterator() { | ||
29 | return new TreeIterator(); | ||
30 | } | ||
31 | |||
32 | private final class TreeIterator implements Iterator<T> { | ||
33 | |||
34 | private Node<T> previousNode; | ||
35 | private Node<T> nextNode; | ||
36 | private int expectedModCount = modCount; | ||
37 | |||
38 | TreeIterator() { | ||
39 | if (root == null) { | ||
40 | nextNode = null; | ||
41 | } else { | ||
42 | nextNode = minimumNode(root); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public boolean hasNext() { | ||
48 | return nextNode != null; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public T next() { | ||
53 | if (nextNode == null) { | ||
54 | throw new NoSuchElementException("Iteration exceeded."); | ||
55 | } | ||
56 | |||
57 | checkConcurrentModification(); | ||
58 | T datum = nextNode.key; | ||
59 | previousNode = nextNode; | ||
60 | nextNode = successorOf(nextNode); | ||
61 | return datum; | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public void remove() { | ||
66 | if (previousNode == null) { | ||
67 | throw new IllegalStateException( | ||
68 | nextNode == null ? | ||
69 | "Not a single call to next(); nothing to remove." : | ||
70 | "Removing the same element twice." | ||
71 | ); | ||
72 | } | ||
73 | |||
74 | checkConcurrentModification(); | ||
75 | |||
76 | Node<T> x = deleteNode(previousNode); | ||
77 | fixAfterModification(x, false); | ||
78 | |||
79 | if (x == nextNode) { | ||
80 | nextNode = previousNode; | ||
81 | } | ||
82 | |||
83 | expectedModCount = ++modCount; | ||
84 | size--; | ||
85 | previousNode = null; | ||
86 | } | ||
87 | |||
88 | private void checkConcurrentModification() { | ||
89 | if (expectedModCount != modCount) { | ||
90 | throw new ConcurrentModificationException( | ||
91 | "The set was modified while iterating."); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | private Node<T> successorOf(Node<T> node) { | ||
96 | if (node.right != null) { | ||
97 | node = node.right; | ||
98 | |||
99 | while (node.left != null) { | ||
100 | node = node.left; | ||
101 | } | ||
102 | |||
103 | return node; | ||
104 | } | ||
105 | |||
106 | Node<T> parent = node.parent; | ||
107 | |||
108 | while (parent != null && parent.right == node) { | ||
109 | node = parent; | ||
110 | parent = parent.parent; | ||
111 | } | ||
112 | |||
113 | return parent; | ||
114 | } | ||
115 | } | ||
116 | |||
117 | @Override | ||
118 | public Object[] toArray() { | ||
119 | Object[] array = new Object[size]; | ||
120 | Iterator<T> iterator = iterator(); | ||
121 | int index = 0; | ||
122 | |||
123 | while (iterator.hasNext()) { | ||
124 | array[index++] = iterator.next(); | ||
125 | } | ||
126 | |||
127 | return array; | ||
128 | } | ||
129 | |||
130 | @Override | ||
131 | public <U> U[] toArray(U[] a) { | ||
132 | Iterator<T> iterator = iterator(); | ||
133 | |||
134 | if (size > a.length) { | ||
135 | a = Arrays.copyOf(a, size); | ||
136 | } | ||
137 | |||
138 | int index = 0; | ||
139 | |||
140 | for (; index < size; ++index) { | ||
141 | @SuppressWarnings("unchecked") | ||
142 | var convertedValue = (U) iterator.next(); | ||
143 | a[index] = convertedValue; | ||
144 | } | ||
145 | |||
146 | if (index < a.length) { | ||
147 | a[index] = null; | ||
148 | } | ||
149 | |||
150 | return a; | ||
151 | } | ||
152 | |||
153 | @Override | ||
154 | public boolean containsAll(Collection<?> c) { | ||
155 | for (Object element : c) { | ||
156 | if (!contains(element)) { | ||
157 | return false; | ||
158 | } | ||
159 | } | ||
160 | |||
161 | return true; | ||
162 | } | ||
163 | |||
164 | @Override | ||
165 | public boolean addAll(Collection<? extends T> c) { | ||
166 | boolean modified = false; | ||
167 | |||
168 | for (T element : c) { | ||
169 | if (add(element)) { | ||
170 | modified = true; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | return modified; | ||
175 | } | ||
176 | |||
177 | @Override | ||
178 | public boolean retainAll(Collection<?> c) { | ||
179 | if (!c.getClass().equals(HashSet.class)) { | ||
180 | c = new HashSet<>(c); | ||
181 | } | ||
182 | |||
183 | Iterator<T> iterator = iterator(); | ||
184 | boolean modified = false; | ||
185 | |||
186 | while (iterator.hasNext()) { | ||
187 | T element = iterator.next(); | ||
188 | |||
189 | if (!c.contains(element)) { | ||
190 | iterator.remove(); | ||
191 | modified = true; | ||
192 | } | ||
193 | } | ||
194 | |||
195 | return modified; | ||
196 | } | ||
197 | |||
198 | @Override | ||
199 | public boolean removeAll(Collection<?> c) { | ||
200 | boolean modified = false; | ||
201 | |||
202 | for (Object element : c) { | ||
203 | if (remove(element)) { | ||
204 | modified = true; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | return modified; | ||
209 | } | ||
210 | |||
211 | private static final class Node<T> { | ||
212 | T key; | ||
213 | |||
214 | Node<T> parent; | ||
215 | Node<T> left; | ||
216 | Node<T> right; | ||
217 | |||
218 | int height; | ||
219 | int count; | ||
220 | |||
221 | Node(T key) { | ||
222 | this.key = key; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | private Node<T> root; | ||
227 | private int size; | ||
228 | private int modCount; | ||
229 | |||
230 | @Override | ||
231 | public boolean add(T element) { | ||
232 | Objects.requireNonNull(element, "The input element is null."); | ||
233 | |||
234 | if (root == null) { | ||
235 | root = new Node<>(element); | ||
236 | size = 1; | ||
237 | modCount++; | ||
238 | return true; | ||
239 | } | ||
240 | |||
241 | Node<T> parent = null; | ||
242 | Node<T> node = root; | ||
243 | int cmp; | ||
244 | |||
245 | while (node != null) { | ||
246 | cmp = element.compareTo(node.key); | ||
247 | |||
248 | if (cmp == 0) { | ||
249 | // The element is already in this tree. | ||
250 | return false; | ||
251 | } | ||
252 | |||
253 | parent = node; | ||
254 | |||
255 | if (cmp < 0) { | ||
256 | node = node.left; | ||
257 | } else { | ||
258 | node = node.right; | ||
259 | } | ||
260 | } | ||
261 | |||
262 | Node<T> newnode = new Node<>(element); | ||
263 | |||
264 | if (element.compareTo(parent.key) < 0) { | ||
265 | parent.left = newnode; | ||
266 | } else { | ||
267 | parent.right = newnode; | ||
268 | } | ||
269 | |||
270 | newnode.parent = parent; | ||
271 | size++; | ||
272 | modCount++; | ||
273 | Node<T> hi = parent; | ||
274 | Node<T> lo = newnode; | ||
275 | |||
276 | while (hi != null) { | ||
277 | if (hi.left == lo) { | ||
278 | hi.count++; | ||
279 | } | ||
280 | |||
281 | lo = hi; | ||
282 | hi = hi.parent; | ||
283 | } | ||
284 | |||
285 | fixAfterModification(newnode, true); | ||
286 | return true; | ||
287 | } | ||
288 | |||
289 | @Override | ||
290 | public boolean contains(Object o) { | ||
291 | @SuppressWarnings("unchecked") | ||
292 | T element = (T) o; | ||
293 | Node<T> x = root; | ||
294 | int cmp; | ||
295 | |||
296 | while (x != null && (cmp = element.compareTo(x.key)) != 0) { | ||
297 | if (cmp < 0) { | ||
298 | x = x.left; | ||
299 | } else { | ||
300 | x = x.right; | ||
301 | } | ||
302 | } | ||
303 | |||
304 | return x != null; | ||
305 | } | ||
306 | |||
307 | @Override | ||
308 | public boolean remove(Object o) { | ||
309 | @SuppressWarnings("unchecked") | ||
310 | T element = (T) o; | ||
311 | Node<T> x = root; | ||
312 | int cmp; | ||
313 | |||
314 | while (x != null && (cmp = element.compareTo(x.key)) != 0) { | ||
315 | if (cmp < 0) { | ||
316 | x = x.left; | ||
317 | } else { | ||
318 | x = x.right; | ||
319 | } | ||
320 | } | ||
321 | |||
322 | if (x == null) { | ||
323 | return false; | ||
324 | } | ||
325 | |||
326 | x = deleteNode(x); | ||
327 | fixAfterModification(x, false); | ||
328 | size--; | ||
329 | modCount++; | ||
330 | return true; | ||
331 | } | ||
332 | |||
333 | public T get(int index) { | ||
334 | checkIndex(index); | ||
335 | Node<T> node = root; | ||
336 | |||
337 | while (true) { | ||
338 | if (index > node.count) { | ||
339 | index -= node.count + 1; | ||
340 | node = node.right; | ||
341 | } else if (index < node.count) { | ||
342 | node = node.left; | ||
343 | } else { | ||
344 | return node.key; | ||
345 | } | ||
346 | } | ||
347 | } | ||
348 | |||
349 | public int indexOf(T element) { | ||
350 | Node<T> node = root; | ||
351 | |||
352 | if (root == null) { | ||
353 | return -1; | ||
354 | } | ||
355 | |||
356 | int rank = root.count; | ||
357 | int cmp; | ||
358 | |||
359 | while (true) { | ||
360 | if ((cmp = element.compareTo(node.key)) < 0) { | ||
361 | if (node.left == null) { | ||
362 | return -1; | ||
363 | } | ||
364 | |||
365 | rank -= (node.count - node.left.count); | ||
366 | node = node.left; | ||
367 | } else if (cmp > 0) { | ||
368 | if (node.right == null) { | ||
369 | return -1; | ||
370 | } | ||
371 | |||
372 | rank += 1 + node.right.count; | ||
373 | node = node.right; | ||
374 | } else { | ||
375 | return rank; | ||
376 | } | ||
377 | } | ||
378 | } | ||
379 | |||
380 | @Override | ||
381 | public int size() { | ||
382 | return size; | ||
383 | } | ||
384 | |||
385 | @Override | ||
386 | public boolean isEmpty() { | ||
387 | return size == 0; | ||
388 | } | ||
389 | |||
390 | @Override | ||
391 | public void clear() { | ||
392 | modCount += size; | ||
393 | root = null; | ||
394 | size = 0; | ||
395 | } | ||
396 | |||
397 | |||
398 | private void checkIndex(int index) { | ||
399 | if (index < 0) { | ||
400 | throw new IndexOutOfBoundsException( | ||
401 | "The input index is negative: " + index); | ||
402 | } | ||
403 | |||
404 | if (index >= size) { | ||
405 | throw new IndexOutOfBoundsException( | ||
406 | "The input index is too large: " + index + | ||
407 | ", the size of this tree is " + size); | ||
408 | } | ||
409 | } | ||
410 | |||
411 | @SuppressWarnings("squid:S3776") | ||
412 | private Node<T> deleteNode(Node<T> node) { | ||
413 | if (node.left == null && node.right == null) { | ||
414 | // 'node' has no children. | ||
415 | Node<T> parent = node.parent; | ||
416 | |||
417 | if (parent == null) { | ||
418 | // 'node' is the root node of this tree. | ||
419 | root = null; | ||
420 | ++modCount; | ||
421 | return node; | ||
422 | } | ||
423 | |||
424 | Node<T> lo = node; | ||
425 | Node<T> hi = parent; | ||
426 | |||
427 | while (hi != null) { | ||
428 | if (hi.left == lo) { | ||
429 | hi.count--; | ||
430 | } | ||
431 | |||
432 | lo = hi; | ||
433 | hi = hi.parent; | ||
434 | } | ||
435 | |||
436 | if (node == parent.left) { | ||
437 | parent.left = null; | ||
438 | } else { | ||
439 | parent.right = null; | ||
440 | } | ||
441 | |||
442 | return node; | ||
443 | } | ||
444 | |||
445 | if (node.left != null && node.right != null) { | ||
446 | // 'node' has both children. | ||
447 | Node<T> successor = minimumNode(node.right); | ||
448 | node.key = successor.key; | ||
449 | Node<T> child = successor.right; | ||
450 | Node<T> parent = successor.parent; | ||
451 | |||
452 | if (parent.left == successor) { | ||
453 | parent.left = child; | ||
454 | } else { | ||
455 | parent.right = child; | ||
456 | } | ||
457 | |||
458 | if (child != null) { | ||
459 | child.parent = parent; | ||
460 | } | ||
461 | |||
462 | Node<T> lo = child; | ||
463 | Node<T> hi = parent; | ||
464 | |||
465 | while (hi != null) { | ||
466 | if (hi.left == lo) { | ||
467 | hi.count--; | ||
468 | } | ||
469 | |||
470 | lo = hi; | ||
471 | hi = hi.parent; | ||
472 | } | ||
473 | |||
474 | return successor; | ||
475 | } | ||
476 | |||
477 | Node<T> child; | ||
478 | |||
479 | // 'node' has only one child. | ||
480 | if (node.left != null) { | ||
481 | child = node.left; | ||
482 | } else { | ||
483 | child = node.right; | ||
484 | } | ||
485 | |||
486 | Node<T> parent = node.parent; | ||
487 | child.parent = parent; | ||
488 | |||
489 | if (parent == null) { | ||
490 | root = child; | ||
491 | return node; | ||
492 | } | ||
493 | |||
494 | if (node == parent.left) { | ||
495 | parent.left = child; | ||
496 | } else { | ||
497 | parent.right = child; | ||
498 | } | ||
499 | |||
500 | Node<T> hi = parent; | ||
501 | Node<T> lo = child; | ||
502 | |||
503 | while (hi != null) { | ||
504 | if (hi.left == lo) { | ||
505 | hi.count--; | ||
506 | } | ||
507 | |||
508 | lo = hi; | ||
509 | hi = hi.parent; | ||
510 | } | ||
511 | |||
512 | return node; | ||
513 | |||
514 | } | ||
515 | |||
516 | private Node<T> minimumNode(Node<T> node) { | ||
517 | while (node.left != null) { | ||
518 | node = node.left; | ||
519 | } | ||
520 | |||
521 | return node; | ||
522 | } | ||
523 | |||
524 | private int height(Node<T> node) { | ||
525 | return node == null ? -1 : node.height; | ||
526 | } | ||
527 | |||
528 | private Node<T> leftRotate(Node<T> node1) { | ||
529 | Node<T> node2 = node1.right; | ||
530 | node2.parent = node1.parent; | ||
531 | node1.parent = node2; | ||
532 | node1.right = node2.left; | ||
533 | node2.left = node1; | ||
534 | |||
535 | if (node1.right != null) { | ||
536 | node1.right.parent = node1; | ||
537 | } | ||
538 | |||
539 | node1.height = Math.max(height(node1.left), height(node1.right)) + 1; | ||
540 | node2.height = Math.max(height(node2.left), height(node2.right)) + 1; | ||
541 | node2.count += node1.count + 1; | ||
542 | return node2; | ||
543 | } | ||
544 | |||
545 | private Node<T> rightRotate(Node<T> node1) { | ||
546 | Node<T> node2 = node1.left; | ||
547 | node2.parent = node1.parent; | ||
548 | node1.parent = node2; | ||
549 | node1.left = node2.right; | ||
550 | node2.right = node1; | ||
551 | |||
552 | if (node1.left != null) { | ||
553 | node1.left.parent = node1; | ||
554 | } | ||
555 | |||
556 | node1.height = Math.max(height(node1.left), height(node1.right)) + 1; | ||
557 | node2.height = Math.max(height(node2.left), height(node2.right)) + 1; | ||
558 | node1.count -= node2.count + 1; | ||
559 | return node2; | ||
560 | } | ||
561 | |||
562 | private Node<T> rightLeftRotate(Node<T> node1) { | ||
563 | Node<T> node2 = node1.right; | ||
564 | node1.right = rightRotate(node2); | ||
565 | return leftRotate(node1); | ||
566 | } | ||
567 | |||
568 | private Node<T> leftRightRotate(Node<T> node1) { | ||
569 | Node<T> node2 = node1.left; | ||
570 | node1.left = leftRotate(node2); | ||
571 | return rightRotate(node1); | ||
572 | } | ||
573 | |||
574 | // Fixing an insertion: use insertionMode = true. | ||
575 | // Fixing a deletion: use insertionMode = false. | ||
576 | @SuppressWarnings("squid:S3776") | ||
577 | private void fixAfterModification(Node<T> node, boolean insertionMode) { | ||
578 | Node<T> parent = node.parent; | ||
579 | Node<T> grandParent; | ||
580 | Node<T> subTree; | ||
581 | |||
582 | while (parent != null) { | ||
583 | if (height(parent.left) == height(parent.right) + 2) { | ||
584 | grandParent = parent.parent; | ||
585 | |||
586 | if (height(parent.left.left) >= height(parent.left.right)) { | ||
587 | subTree = rightRotate(parent); | ||
588 | } else { | ||
589 | subTree = leftRightRotate(parent); | ||
590 | } | ||
591 | |||
592 | if (grandParent == null) { | ||
593 | root = subTree; | ||
594 | } else if (grandParent.left == parent) { | ||
595 | grandParent.left = subTree; | ||
596 | } else { | ||
597 | grandParent.right = subTree; | ||
598 | } | ||
599 | |||
600 | if (grandParent != null) { | ||
601 | grandParent.height = Math.max( | ||
602 | height(grandParent.left), | ||
603 | height(grandParent.right)) + 1; | ||
604 | } | ||
605 | |||
606 | if (insertionMode) { | ||
607 | // Whenever fixing after insertion, at most one rotation is | ||
608 | // required in order to maintain the balance. | ||
609 | return; | ||
610 | } | ||
611 | } else if (height(parent.right) == height(parent.left) + 2) { | ||
612 | grandParent = parent.parent; | ||
613 | |||
614 | if (height(parent.right.right) >= height(parent.right.left)) { | ||
615 | subTree = leftRotate(parent); | ||
616 | } else { | ||
617 | subTree = rightLeftRotate(parent); | ||
618 | } | ||
619 | |||
620 | if (grandParent == null) { | ||
621 | root = subTree; | ||
622 | } else if (grandParent.left == parent) { | ||
623 | grandParent.left = subTree; | ||
624 | } else { | ||
625 | grandParent.right = subTree; | ||
626 | } | ||
627 | |||
628 | if (grandParent != null) { | ||
629 | grandParent.height = | ||
630 | Math.max(height(grandParent.left), | ||
631 | height(grandParent.right)) + 1; | ||
632 | } | ||
633 | |||
634 | if (insertionMode) { | ||
635 | return; | ||
636 | } | ||
637 | } | ||
638 | |||
639 | parent.height = Math.max(height(parent.left), | ||
640 | height(parent.right)) + 1; | ||
641 | parent = parent.parent; | ||
642 | } | ||
643 | } | ||
644 | |||
645 | boolean isHealthy() { | ||
646 | if (root == null) { | ||
647 | return true; | ||
648 | } | ||
649 | |||
650 | return !containsCycles() | ||
651 | && heightsAreCorrect() | ||
652 | && isBalanced() | ||
653 | && isWellIndexed(); | ||
654 | } | ||
655 | |||
656 | private boolean containsCycles() { | ||
657 | Set<Node<T>> visitedNodes = new HashSet<>(); | ||
658 | return containsCycles(root, visitedNodes); | ||
659 | } | ||
660 | |||
661 | private boolean containsCycles(Node<T> current, Set<Node<T>> visitedNodes) { | ||
662 | if (current == null) { | ||
663 | return false; | ||
664 | } | ||
665 | |||
666 | if (visitedNodes.contains(current)) { | ||
667 | return true; | ||
668 | } | ||
669 | |||
670 | visitedNodes.add(current); | ||
671 | |||
672 | return containsCycles(current.left, visitedNodes) | ||
673 | || containsCycles(current.right, visitedNodes); | ||
674 | } | ||
675 | |||
676 | private boolean heightsAreCorrect() { | ||
677 | return getHeight(root) == root.height; | ||
678 | } | ||
679 | |||
680 | private int getHeight(Node<T> node) { | ||
681 | if (node == null) { | ||
682 | return -1; | ||
683 | } | ||
684 | |||
685 | int leftTreeHeight = getHeight(node.left); | ||
686 | |||
687 | if (leftTreeHeight == Integer.MIN_VALUE) { | ||
688 | return Integer.MIN_VALUE; | ||
689 | } | ||
690 | |||
691 | int rightTreeHeight = getHeight(node.right); | ||
692 | |||
693 | if (rightTreeHeight == Integer.MIN_VALUE) { | ||
694 | return Integer.MIN_VALUE; | ||
695 | } | ||
696 | |||
697 | if (node.height == Math.max(leftTreeHeight, rightTreeHeight) + 1) { | ||
698 | return node.height; | ||
699 | } | ||
700 | |||
701 | return Integer.MIN_VALUE; | ||
702 | } | ||
703 | |||
704 | private boolean isBalanced() { | ||
705 | return isBalanced(root); | ||
706 | } | ||
707 | |||
708 | private boolean isBalanced(Node<T> node) { | ||
709 | if (node == null) { | ||
710 | return true; | ||
711 | } | ||
712 | |||
713 | if (!isBalanced(node.left)) { | ||
714 | return false; | ||
715 | } | ||
716 | |||
717 | if (!isBalanced(node.right)) { | ||
718 | return false; | ||
719 | } | ||
720 | |||
721 | int leftHeight = height(node.left); | ||
722 | int rightHeight = height(node.right); | ||
723 | |||
724 | return Math.abs(leftHeight - rightHeight) < 2; | ||
725 | } | ||
726 | |||
727 | private boolean isWellIndexed() { | ||
728 | return size == count(root); | ||
729 | } | ||
730 | |||
731 | private int count(Node<T> node) { | ||
732 | if (node == null) { | ||
733 | return 0; | ||
734 | } | ||
735 | |||
736 | int leftTreeSize = count(node.left); | ||
737 | |||
738 | if (leftTreeSize == Integer.MIN_VALUE) { | ||
739 | return Integer.MIN_VALUE; | ||
740 | } | ||
741 | |||
742 | if (node.count != leftTreeSize) { | ||
743 | return Integer.MIN_VALUE; | ||
744 | } | ||
745 | |||
746 | int rightTreeSize = count(node.right); | ||
747 | |||
748 | if (rightTreeSize == Integer.MIN_VALUE) { | ||
749 | return Integer.MIN_VALUE; | ||
750 | } | ||
751 | |||
752 | return leftTreeSize + 1 + rightTreeSize; | ||
753 | } | ||
754 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java new file mode 100644 index 00000000..261ceaa5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.term.AnyDataVariable; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Map; | ||
12 | |||
13 | record MapBasedValuation(Map<AnyDataVariable, Object> values) implements Valuation { | ||
14 | @Override | ||
15 | public <T> T getValue(DataVariable<T> variable) { | ||
16 | if (!values.containsKey(variable)) { | ||
17 | throw new IllegalArgumentException("No value for variable %s".formatted(variable)); | ||
18 | } | ||
19 | var value = values.get(variable); | ||
20 | return variable.getType().cast(value); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java new file mode 100644 index 00000000..fc8406aa --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.term.AnyDataVariable; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Set; | ||
12 | |||
13 | public record RestrictedValuation(Valuation valuation, Set<AnyDataVariable> allowedVariables) implements Valuation { | ||
14 | @Override | ||
15 | public <T> T getValue(DataVariable<T> variable) { | ||
16 | if (!allowedVariables.contains(variable)) { | ||
17 | throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable)); | ||
18 | } | ||
19 | return valuation.getValue(variable); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java new file mode 100644 index 00000000..1c14112c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation { | ||
12 | @Override | ||
13 | public <T> T getValue(DataVariable<T> variable) { | ||
14 | return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable)); | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java new file mode 100644 index 00000000..1588e957 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.substitution.Substitution; | ||
10 | import tools.refinery.store.query.term.AnyDataVariable; | ||
11 | import tools.refinery.store.query.term.DataVariable; | ||
12 | |||
13 | import java.util.Map; | ||
14 | import java.util.Set; | ||
15 | |||
16 | public interface Valuation { | ||
17 | <T> T getValue(DataVariable<T> variable); | ||
18 | |||
19 | default Valuation substitute(@Nullable Substitution substitution) { | ||
20 | if (substitution == null) { | ||
21 | return this; | ||
22 | } | ||
23 | return new SubstitutedValuation(this, substitution); | ||
24 | } | ||
25 | |||
26 | default Valuation restrict(Set<? extends AnyDataVariable> allowedVariables) { | ||
27 | return new RestrictedValuation(this, Set.copyOf(allowedVariables)); | ||
28 | } | ||
29 | |||
30 | static ValuationBuilder builder() { | ||
31 | return new ValuationBuilder(); | ||
32 | } | ||
33 | |||
34 | static Valuation empty() { | ||
35 | return new MapBasedValuation(Map.of()); | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java new file mode 100644 index 00000000..7337dbc3 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.valuation; | ||
7 | |||
8 | import tools.refinery.store.query.term.AnyDataVariable; | ||
9 | import tools.refinery.store.query.term.DataVariable; | ||
10 | |||
11 | import java.util.Collections; | ||
12 | import java.util.HashMap; | ||
13 | import java.util.Map; | ||
14 | |||
15 | public class ValuationBuilder { | ||
16 | private final Map<AnyDataVariable, Object> values = new HashMap<>(); | ||
17 | |||
18 | ValuationBuilder() { | ||
19 | } | ||
20 | |||
21 | public <T> ValuationBuilder put(DataVariable<T> variable, T value) { | ||
22 | return putChecked(variable, value); | ||
23 | } | ||
24 | |||
25 | public ValuationBuilder putChecked(AnyDataVariable variable, Object value) { | ||
26 | if (value != null && !variable.getType().isInstance(value)) { | ||
27 | throw new IllegalArgumentException("Value %s is not an instance of %s" | ||
28 | .formatted(value, variable.getType().getName())); | ||
29 | } | ||
30 | if (values.containsKey(variable)) { | ||
31 | throw new IllegalArgumentException("Already has value for variable %s".formatted(variable)); | ||
32 | } | ||
33 | values.put(variable, value); | ||
34 | return this; | ||
35 | } | ||
36 | |||
37 | public Valuation build() { | ||
38 | return new MapBasedValuation(Collections.unmodifiableMap(values)); | ||
39 | } | ||
40 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java new file mode 100644 index 00000000..fd37604e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java | |||
@@ -0,0 +1,110 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.dnf.FunctionalDependency; | ||
10 | import tools.refinery.store.query.term.Parameter; | ||
11 | import tools.refinery.store.representation.Symbol; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | import tools.refinery.store.tuple.Tuple1; | ||
14 | |||
15 | import java.util.Arrays; | ||
16 | import java.util.List; | ||
17 | import java.util.Objects; | ||
18 | import java.util.Set; | ||
19 | import java.util.stream.Collectors; | ||
20 | import java.util.stream.IntStream; | ||
21 | |||
22 | public abstract class AbstractFunctionView<T> extends SymbolView<T> { | ||
23 | private final List<Parameter> parameters; | ||
24 | |||
25 | protected AbstractFunctionView(Symbol<T> symbol, String name, Parameter outParameter) { | ||
26 | super(symbol, name); | ||
27 | parameters = createParameters(symbol.arity(), outParameter); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public Set<FunctionalDependency<Integer>> getFunctionalDependencies() { | ||
32 | var arity = getSymbol().arity(); | ||
33 | var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet()); | ||
34 | var unique = Set.of(arity); | ||
35 | return Set.of(new FunctionalDependency<>(forEach, unique)); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public Set<ViewImplication> getImpliedRelationViews() { | ||
40 | var symbol = getSymbol(); | ||
41 | var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList(); | ||
42 | var keysView = new KeyOnlyView<>(symbol); | ||
43 | return Set.of(new ViewImplication(this, keysView, impliedIndices)); | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | protected boolean doFilter(Tuple key, T value) { | ||
48 | return true; | ||
49 | } | ||
50 | |||
51 | protected Object forwardMapValue(T value) { | ||
52 | return value; | ||
53 | } | ||
54 | |||
55 | protected boolean valueEquals(T value, Object otherForwardMappedValue) { | ||
56 | return Objects.equals(otherForwardMappedValue, forwardMapValue(value)); | ||
57 | } | ||
58 | |||
59 | @Override | ||
60 | public Object[] forwardMap(Tuple key, T value) { | ||
61 | int size = key.getSize(); | ||
62 | Object[] result = new Object[size + 1]; | ||
63 | for (int i = 0; i < size; i++) { | ||
64 | result[i] = Tuple.of(key.get(i)); | ||
65 | } | ||
66 | result[key.getSize()] = forwardMapValue(value); | ||
67 | return result; | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public boolean get(Model model, Object[] tuple) { | ||
72 | int[] content = new int[tuple.length - 1]; | ||
73 | for (int i = 0; i < tuple.length - 1; i++) { | ||
74 | if (!(tuple[i] instanceof Tuple1 wrapper)) { | ||
75 | return false; | ||
76 | } | ||
77 | content[i] = wrapper.value0(); | ||
78 | } | ||
79 | Tuple key = Tuple.of(content); | ||
80 | var valueInTuple = tuple[tuple.length - 1]; | ||
81 | T valueInMap = model.getInterpretation(getSymbol()).get(key); | ||
82 | return valueEquals(valueInMap, valueInTuple); | ||
83 | } | ||
84 | |||
85 | @Override | ||
86 | public List<Parameter> getParameters() { | ||
87 | return parameters; | ||
88 | } | ||
89 | |||
90 | @Override | ||
91 | public boolean equals(Object o) { | ||
92 | if (this == o) return true; | ||
93 | if (o == null || getClass() != o.getClass()) return false; | ||
94 | if (!super.equals(o)) return false; | ||
95 | AbstractFunctionView<?> that = (AbstractFunctionView<?>) o; | ||
96 | return Objects.equals(parameters, that.parameters); | ||
97 | } | ||
98 | |||
99 | @Override | ||
100 | public int hashCode() { | ||
101 | return Objects.hash(super.hashCode(), parameters); | ||
102 | } | ||
103 | |||
104 | private static List<Parameter> createParameters(int symbolArity, Parameter outParameter) { | ||
105 | var parameters = new Parameter[symbolArity + 1]; | ||
106 | Arrays.fill(parameters, Parameter.NODE_OUT); | ||
107 | parameters[symbolArity] = outParameter; | ||
108 | return List.of(parameters); | ||
109 | } | ||
110 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java new file mode 100644 index 00000000..90b27ebb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.dnf.FunctionalDependency; | ||
10 | import tools.refinery.store.representation.AnySymbol; | ||
11 | import tools.refinery.store.query.Constraint; | ||
12 | |||
13 | import java.util.Set; | ||
14 | |||
15 | public sealed interface AnySymbolView extends Constraint permits SymbolView { | ||
16 | AnySymbol getSymbol(); | ||
17 | |||
18 | String getViewName(); | ||
19 | |||
20 | default Set<FunctionalDependency<Integer>> getFunctionalDependencies() { | ||
21 | return Set.of(); | ||
22 | } | ||
23 | |||
24 | default Set<ViewImplication> getImpliedRelationViews() { | ||
25 | return Set.of(); | ||
26 | } | ||
27 | |||
28 | boolean get(Model model, Object[] tuple); | ||
29 | |||
30 | Iterable<Object[]> getAll(Model model); | ||
31 | } | ||
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 new file mode 100644 index 00000000..abae6e5c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java | |||
@@ -0,0 +1,73 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.tuple.Tuple; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | import java.util.function.BiPredicate; | ||
13 | import java.util.function.Predicate; | ||
14 | |||
15 | public class FilteredView<T> extends TuplePreservingView<T> { | ||
16 | private final BiPredicate<Tuple, T> predicate; | ||
17 | |||
18 | public FilteredView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) { | ||
19 | super(symbol, name); | ||
20 | this.predicate = predicate; | ||
21 | } | ||
22 | |||
23 | public FilteredView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) { | ||
24 | super(symbol); | ||
25 | this.predicate = predicate; | ||
26 | } | ||
27 | |||
28 | public FilteredView(Symbol<T> symbol, String name, Predicate<T> predicate) { | ||
29 | this(symbol, name, (k, v) -> predicate.test(v)); | ||
30 | validateDefaultValue(predicate); | ||
31 | } | ||
32 | |||
33 | public FilteredView(Symbol<T> symbol, Predicate<T> predicate) { | ||
34 | this(symbol, (k, v) -> predicate.test(v)); | ||
35 | validateDefaultValue(predicate); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | protected boolean doFilter(Tuple key, T value) { | ||
40 | return this.predicate.test(key, value); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public boolean equals(Object o) { | ||
45 | if (this == o) return true; | ||
46 | if (o == null || getClass() != o.getClass()) return false; | ||
47 | if (!super.equals(o)) return false; | ||
48 | FilteredView<?> that = (FilteredView<?>) o; | ||
49 | return Objects.equals(predicate, that.predicate); | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public int hashCode() { | ||
54 | return Objects.hash(super.hashCode(), predicate); | ||
55 | } | ||
56 | |||
57 | private void validateDefaultValue(Predicate<T> predicate) { | ||
58 | var defaultValue = getSymbol().defaultValue(); | ||
59 | boolean matchesDefaultValue = false; | ||
60 | try { | ||
61 | matchesDefaultValue = predicate.test(defaultValue); | ||
62 | } catch (NullPointerException e) { | ||
63 | if (defaultValue != null) { | ||
64 | throw e; | ||
65 | } | ||
66 | // The predicate doesn't need to handle the default value if it is null. | ||
67 | } | ||
68 | if (matchesDefaultValue) { | ||
69 | throw new IllegalArgumentException("Tuples with default value %s cannot be enumerated in %s" | ||
70 | .formatted(defaultValue, getSymbol())); | ||
71 | } | ||
72 | } | ||
73 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java new file mode 100644 index 00000000..c312330e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public class ForbiddenView extends TuplePreservingView<TruthValue> { | ||
13 | public ForbiddenView(Symbol<TruthValue> symbol) { | ||
14 | super(symbol, "forbidden"); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, TruthValue value) { | ||
19 | return !value.may(); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java new file mode 100644 index 00000000..74a5be07 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.query.term.*; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | |||
11 | import java.util.ArrayList; | ||
12 | import java.util.List; | ||
13 | |||
14 | public final class FunctionView<T> extends AbstractFunctionView<T> { | ||
15 | public FunctionView(Symbol<T> symbol, String name) { | ||
16 | super(symbol, name, new Parameter(symbol.valueType(), ParameterDirection.OUT)); | ||
17 | } | ||
18 | |||
19 | public FunctionView(Symbol<T> symbol) { | ||
20 | this(symbol, "function"); | ||
21 | } | ||
22 | |||
23 | public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) { | ||
24 | return targetVariable -> { | ||
25 | var placeholderVariable = Variable.of(getSymbol().valueType()); | ||
26 | var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1); | ||
27 | argumentsWithPlaceholder.addAll(arguments); | ||
28 | argumentsWithPlaceholder.add(placeholderVariable); | ||
29 | return aggregateBy(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable); | ||
30 | }; | ||
31 | } | ||
32 | |||
33 | public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) { | ||
34 | return aggregate(aggregator, List.of(arguments)); | ||
35 | } | ||
36 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java index e1b2e45b..f0e4a61e 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.query.view; | 6 | package tools.refinery.store.query.view; |
2 | 7 | ||
3 | import tools.refinery.store.representation.Symbol; | 8 | import tools.refinery.store.representation.Symbol; |
@@ -5,19 +10,19 @@ import tools.refinery.store.tuple.Tuple; | |||
5 | 10 | ||
6 | import java.util.Objects; | 11 | import java.util.Objects; |
7 | 12 | ||
8 | public final class KeyOnlyRelationView<T> extends TuplePreservingRelationView<T> { | 13 | public final class KeyOnlyView<T> extends TuplePreservingView<T> { |
9 | public static final String VIEW_NAME = "key"; | 14 | public static final String VIEW_NAME = "key"; |
10 | 15 | ||
11 | private final T defaultValue; | 16 | private final T defaultValue; |
12 | 17 | ||
13 | public KeyOnlyRelationView(Symbol<T> symbol) { | 18 | public KeyOnlyView(Symbol<T> symbol) { |
14 | super(symbol, VIEW_NAME); | 19 | super(symbol, VIEW_NAME); |
15 | defaultValue = symbol.defaultValue(); | 20 | defaultValue = symbol.defaultValue(); |
16 | } | 21 | } |
17 | 22 | ||
18 | @Override | 23 | @Override |
19 | public boolean filter(Tuple key, T value) { | 24 | protected boolean doFilter(Tuple key, T value) { |
20 | return !Objects.equals(value, defaultValue); | 25 | return true; |
21 | } | 26 | } |
22 | 27 | ||
23 | @Override | 28 | @Override |
@@ -25,7 +30,7 @@ public final class KeyOnlyRelationView<T> extends TuplePreservingRelationView<T> | |||
25 | if (this == o) return true; | 30 | if (this == o) return true; |
26 | if (o == null || getClass() != o.getClass()) return false; | 31 | if (o == null || getClass() != o.getClass()) return false; |
27 | if (!super.equals(o)) return false; | 32 | if (!super.equals(o)) return false; |
28 | KeyOnlyRelationView<?> that = (KeyOnlyRelationView<?>) o; | 33 | KeyOnlyView<?> that = (KeyOnlyView<?>) o; |
29 | return Objects.equals(defaultValue, that.defaultValue); | 34 | return Objects.equals(defaultValue, that.defaultValue); |
30 | } | 35 | } |
31 | 36 | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java new file mode 100644 index 00000000..c322e220 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public class MayView extends TuplePreservingView<TruthValue> { | ||
13 | public MayView(Symbol<TruthValue> symbol) { | ||
14 | super(symbol, "may"); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, TruthValue value) { | ||
19 | return value.may(); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java new file mode 100644 index 00000000..65bb4e4c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public class MustView extends TuplePreservingView<TruthValue> { | ||
13 | public MustView(Symbol<TruthValue> symbol) { | ||
14 | super(symbol, "must"); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, TruthValue value) { | ||
19 | return value.must(); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java new file mode 100644 index 00000000..fcf11506 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java | |||
@@ -0,0 +1,20 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.query.term.Parameter; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple1; | ||
11 | |||
12 | public final class NodeFunctionView extends AbstractFunctionView<Tuple1> { | ||
13 | public NodeFunctionView(Symbol<Tuple1> symbol, String name) { | ||
14 | super(symbol, name, Parameter.NODE_OUT); | ||
15 | } | ||
16 | |||
17 | public NodeFunctionView(Symbol<Tuple1> symbol) { | ||
18 | this(symbol, "function"); | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java new file mode 100644 index 00000000..cd8bd56b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java | |||
@@ -0,0 +1,85 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.map.CursorAsIterator; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | |||
13 | import java.util.Objects; | ||
14 | import java.util.UUID; | ||
15 | |||
16 | /** | ||
17 | * Represents a view of a {@link Symbol} that can be queried. | ||
18 | * | ||
19 | * @param <T> | ||
20 | * @author Oszkar Semerath | ||
21 | */ | ||
22 | public abstract non-sealed class SymbolView<T> implements AnySymbolView { | ||
23 | private final Symbol<T> symbol; | ||
24 | private final String viewName; | ||
25 | |||
26 | protected SymbolView(Symbol<T> symbol, String viewName) { | ||
27 | this.symbol = symbol; | ||
28 | this.viewName = viewName; | ||
29 | } | ||
30 | |||
31 | protected SymbolView(Symbol<T> representation) { | ||
32 | this(representation, UUID.randomUUID().toString()); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public Symbol<T> getSymbol() { | ||
37 | return symbol; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public String getViewName() { | ||
42 | return viewName; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public String name() { | ||
47 | return symbol.name() + "#" + viewName; | ||
48 | } | ||
49 | |||
50 | public final boolean filter(Tuple key, T value) { | ||
51 | return !Objects.equals(symbol.defaultValue(), value) && doFilter(key, value); | ||
52 | } | ||
53 | |||
54 | protected abstract boolean doFilter(Tuple key, T value); | ||
55 | |||
56 | public abstract Object[] forwardMap(Tuple key, T value); | ||
57 | |||
58 | @Override | ||
59 | public Iterable<Object[]> getAll(Model model) { | ||
60 | return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter)); | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public String toString() { | ||
65 | return name(); | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public String toReferenceString() { | ||
70 | return "@RelationView(\"%s\") %s".formatted(viewName, symbol.name()); | ||
71 | } | ||
72 | |||
73 | @Override | ||
74 | public boolean equals(Object o) { | ||
75 | if (this == o) return true; | ||
76 | if (o == null || getClass() != o.getClass()) return false; | ||
77 | SymbolView<?> that = (SymbolView<?>) o; | ||
78 | return Objects.equals(symbol, that.symbol) && Objects.equals(viewName, that.viewName); | ||
79 | } | ||
80 | |||
81 | @Override | ||
82 | public int hashCode() { | ||
83 | return Objects.hash(getClass(), symbol, viewName); | ||
84 | } | ||
85 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java new file mode 100644 index 00000000..6bc5a708 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java | |||
@@ -0,0 +1,82 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.term.Parameter; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | import tools.refinery.store.tuple.Tuple1; | ||
13 | |||
14 | import java.util.Arrays; | ||
15 | import java.util.List; | ||
16 | import java.util.Objects; | ||
17 | |||
18 | public abstract class TuplePreservingView<T> extends SymbolView<T> { | ||
19 | private final List<Parameter> parameters; | ||
20 | |||
21 | protected TuplePreservingView(Symbol<T> symbol, String name) { | ||
22 | super(symbol, name); | ||
23 | this.parameters = createParameters(symbol.arity()); | ||
24 | } | ||
25 | |||
26 | protected TuplePreservingView(Symbol<T> symbol) { | ||
27 | super(symbol); | ||
28 | this.parameters = createParameters(symbol.arity()); | ||
29 | } | ||
30 | |||
31 | public Object[] forwardMap(Tuple key) { | ||
32 | Object[] result = new Object[key.getSize()]; | ||
33 | for (int i = 0; i < key.getSize(); i++) { | ||
34 | result[i] = Tuple.of(key.get(i)); | ||
35 | } | ||
36 | return result; | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public Object[] forwardMap(Tuple key, T value) { | ||
41 | return forwardMap(key); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public boolean get(Model model, Object[] tuple) { | ||
46 | int[] content = new int[tuple.length]; | ||
47 | for (int i = 0; i < tuple.length; i++) { | ||
48 | if (!(tuple[i] instanceof Tuple1 wrapper)) { | ||
49 | return false; | ||
50 | } | ||
51 | content[i] = wrapper.value0(); | ||
52 | } | ||
53 | Tuple key = Tuple.of(content); | ||
54 | T value = model.getInterpretation(getSymbol()).get(key); | ||
55 | return filter(key, value); | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public List<Parameter> getParameters() { | ||
60 | return parameters; | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public boolean equals(Object o) { | ||
65 | if (this == o) return true; | ||
66 | if (o == null || getClass() != o.getClass()) return false; | ||
67 | if (!super.equals(o)) return false; | ||
68 | TuplePreservingView<?> that = (TuplePreservingView<?>) o; | ||
69 | return Objects.equals(parameters, that.parameters); | ||
70 | } | ||
71 | |||
72 | @Override | ||
73 | public int hashCode() { | ||
74 | return Objects.hash(super.hashCode(), parameters); | ||
75 | } | ||
76 | |||
77 | private static List<Parameter> createParameters(int arity) { | ||
78 | var parameters = new Parameter[arity]; | ||
79 | Arrays.fill(parameters, Parameter.NODE_OUT); | ||
80 | return List.of(parameters); | ||
81 | } | ||
82 | } | ||
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java new file mode 100644 index 00000000..fc2db9f2 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java | |||
@@ -0,0 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.view; | ||
7 | |||
8 | import java.util.List; | ||
9 | |||
10 | public record ViewImplication(AnySymbolView implyingView, AnySymbolView impliedView, List<Integer> impliedIndices) { | ||
11 | public ViewImplication { | ||
12 | if (impliedIndices.size() != impliedView.arity()) { | ||
13 | throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided" | ||
14 | .formatted(impliedView.arity(), impliedView, impliedIndices.size())); | ||
15 | } | ||
16 | for (var index : impliedIndices) { | ||
17 | if (impliedView.invalidIndex(index)) { | ||
18 | throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index, | ||
19 | implyingView)); | ||
20 | } | ||
21 | } | ||
22 | } | ||
23 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java new file mode 100644 index 00000000..e17496e3 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java | |||
@@ -0,0 +1,210 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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 org.junit.jupiter.api.Test; | ||
9 | import org.junit.jupiter.params.ParameterizedTest; | ||
10 | import org.junit.jupiter.params.provider.CsvSource; | ||
11 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
12 | import tools.refinery.store.query.term.NodeVariable; | ||
13 | import tools.refinery.store.query.term.ParameterDirection; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | import tools.refinery.store.query.term.bool.BoolTerms; | ||
16 | import tools.refinery.store.query.view.KeyOnlyView; | ||
17 | import tools.refinery.store.query.view.SymbolView; | ||
18 | import tools.refinery.store.representation.Symbol; | ||
19 | |||
20 | import java.util.List; | ||
21 | |||
22 | import static org.hamcrest.MatcherAssert.assertThat; | ||
23 | import static tools.refinery.store.query.literal.Literals.assume; | ||
24 | import static tools.refinery.store.query.literal.Literals.not; | ||
25 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
26 | |||
27 | class DnfBuilderLiteralEliminationTest { | ||
28 | private final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
29 | private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); | ||
30 | private final NodeVariable p = Variable.of("p"); | ||
31 | private final NodeVariable q = Variable.of("q"); | ||
32 | private final Dnf trueDnf = Dnf.builder().parameter(p, ParameterDirection.IN).clause().build(); | ||
33 | private final Dnf falseDnf = Dnf.builder().parameter(p).build(); | ||
34 | |||
35 | @Test | ||
36 | void eliminateTrueTest() { | ||
37 | var actual = Dnf.builder() | ||
38 | .parameters(p, q) | ||
39 | .clause(BooleanLiteral.TRUE, friendView.call(p, q)) | ||
40 | .build(); | ||
41 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
42 | |||
43 | assertThat(actual, structurallyEqualTo(expected)); | ||
44 | } | ||
45 | |||
46 | @Test | ||
47 | void eliminateTrueAssumptionTest() { | ||
48 | var actual = Dnf.builder() | ||
49 | .parameters(p, q) | ||
50 | .clause(assume(BoolTerms.constant(true)), friendView.call(p, q)) | ||
51 | .build(); | ||
52 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
53 | |||
54 | assertThat(actual, structurallyEqualTo(expected)); | ||
55 | } | ||
56 | |||
57 | @Test | ||
58 | void eliminateFalseTest() { | ||
59 | var actual = Dnf.builder() | ||
60 | .parameters(p, q) | ||
61 | .clause(friendView.call(p, q)) | ||
62 | .clause(friendView.call(q, p), BooleanLiteral.FALSE) | ||
63 | .build(); | ||
64 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
65 | |||
66 | assertThat(actual, structurallyEqualTo(expected)); | ||
67 | } | ||
68 | |||
69 | @ParameterizedTest | ||
70 | @CsvSource(value = { | ||
71 | "false", | ||
72 | "null" | ||
73 | }, nullValues = "null") | ||
74 | void eliminateFalseAssumptionTest(Boolean value) { | ||
75 | var actual = Dnf.builder() | ||
76 | .parameters(p, q) | ||
77 | .clause(friendView.call(p, q)) | ||
78 | .clause(friendView.call(q, p), assume(BoolTerms.constant(value))) | ||
79 | .build(); | ||
80 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
81 | |||
82 | assertThat(actual, structurallyEqualTo(expected)); | ||
83 | } | ||
84 | |||
85 | @Test | ||
86 | void alwaysTrueTest() { | ||
87 | var actual = Dnf.builder() | ||
88 | .parameters(List.of(p, q), ParameterDirection.IN) | ||
89 | .clause(friendView.call(p, q)) | ||
90 | .clause(BooleanLiteral.TRUE) | ||
91 | .build(); | ||
92 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); | ||
93 | |||
94 | assertThat(actual, structurallyEqualTo(expected)); | ||
95 | } | ||
96 | |||
97 | @Test | ||
98 | void alwaysFalseTest() { | ||
99 | var actual = Dnf.builder() | ||
100 | .parameters(p, q) | ||
101 | .clause(friendView.call(p, q), BooleanLiteral.FALSE) | ||
102 | .build(); | ||
103 | var expected = Dnf.builder().parameters(p, q).build(); | ||
104 | |||
105 | assertThat(actual, structurallyEqualTo(expected)); | ||
106 | } | ||
107 | |||
108 | @Test | ||
109 | void eliminateTrueDnfTest() { | ||
110 | var actual = Dnf.builder() | ||
111 | .parameters(p, q) | ||
112 | .clause(trueDnf.call(q), friendView.call(p, q)) | ||
113 | .build(); | ||
114 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
115 | |||
116 | assertThat(actual, structurallyEqualTo(expected)); | ||
117 | } | ||
118 | |||
119 | @Test | ||
120 | void eliminateFalseDnfTest() { | ||
121 | var actual = Dnf.builder() | ||
122 | .parameters(p, q) | ||
123 | .clause(friendView.call(p, q)) | ||
124 | .clause(friendView.call(q, p), falseDnf.call(q)) | ||
125 | .build(); | ||
126 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
127 | |||
128 | assertThat(actual, structurallyEqualTo(expected)); | ||
129 | } | ||
130 | |||
131 | @Test | ||
132 | void alwaysTrueDnfTest() { | ||
133 | var actual = Dnf.builder() | ||
134 | .parameters(List.of(p, q), ParameterDirection.IN) | ||
135 | .clause(friendView.call(p, q)) | ||
136 | .clause(trueDnf.call(q)) | ||
137 | .build(); | ||
138 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); | ||
139 | |||
140 | assertThat(actual, structurallyEqualTo(expected)); | ||
141 | } | ||
142 | |||
143 | @Test | ||
144 | void alwaysFalseDnfTest() { | ||
145 | var actual = Dnf.builder() | ||
146 | .parameters(p, q) | ||
147 | .clause(friendView.call(p, q), falseDnf.call(q)) | ||
148 | .build(); | ||
149 | var expected = Dnf.builder().parameters(p, q).build(); | ||
150 | |||
151 | assertThat(actual, structurallyEqualTo(expected)); | ||
152 | } | ||
153 | |||
154 | @Test | ||
155 | void eliminateNotFalseDnfTest() { | ||
156 | var actual = Dnf.builder() | ||
157 | .parameters(p, q) | ||
158 | .clause(not(falseDnf.call(q)), friendView.call(p, q)) | ||
159 | .build(); | ||
160 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
161 | |||
162 | assertThat(actual, structurallyEqualTo(expected)); | ||
163 | } | ||
164 | |||
165 | @Test | ||
166 | void eliminateNotTrueDnfTest() { | ||
167 | var actual = Dnf.builder() | ||
168 | .parameters(p, q) | ||
169 | .clause(friendView.call(p, q)) | ||
170 | .clause(friendView.call(q, p), not(trueDnf.call(q))) | ||
171 | .build(); | ||
172 | var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); | ||
173 | |||
174 | assertThat(actual, structurallyEqualTo(expected)); | ||
175 | } | ||
176 | |||
177 | @Test | ||
178 | void alwaysNotFalseDnfTest() { | ||
179 | var actual = Dnf.builder() | ||
180 | .parameters(List.of(p, q), ParameterDirection.IN) | ||
181 | .clause(friendView.call(p, q)) | ||
182 | .clause(not(falseDnf.call(q))) | ||
183 | .build(); | ||
184 | var expected = Dnf.builder().parameters(List.of(p, q), ParameterDirection.IN).clause().build(); | ||
185 | |||
186 | assertThat(actual, structurallyEqualTo(expected)); | ||
187 | } | ||
188 | |||
189 | @Test | ||
190 | void alwaysNotTrueDnfTest() { | ||
191 | var actual = Dnf.builder() | ||
192 | .parameters(p, q) | ||
193 | .clause(friendView.call(p, q), not(trueDnf.call(q))) | ||
194 | .build(); | ||
195 | var expected = Dnf.builder().parameters(p, q).build(); | ||
196 | |||
197 | assertThat(actual, structurallyEqualTo(expected)); | ||
198 | } | ||
199 | |||
200 | @Test | ||
201 | void removeDuplicateTest() { | ||
202 | var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( | ||
203 | friendView.call(p, q), | ||
204 | friendView.call(p, q) | ||
205 | ))); | ||
206 | var expected = Dnf.of(builder -> builder.clause((p, q) -> List.of(friendView.call(p, q)))); | ||
207 | |||
208 | assertThat(actual, structurallyEqualTo(expected)); | ||
209 | } | ||
210 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java new file mode 100644 index 00000000..fc40c7b3 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java | |||
@@ -0,0 +1,325 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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 org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.ParameterDirection; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | import tools.refinery.store.query.view.KeyOnlyView; | ||
12 | import tools.refinery.store.query.view.SymbolView; | ||
13 | import tools.refinery.store.representation.Symbol; | ||
14 | |||
15 | import java.util.List; | ||
16 | |||
17 | import static org.hamcrest.MatcherAssert.assertThat; | ||
18 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
19 | |||
20 | class DnfBuilderVariableUnificationTest { | ||
21 | private final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
22 | private final Symbol<Boolean> children = Symbol.of("children", 2); | ||
23 | private final SymbolView<Boolean> friendView = new KeyOnlyView<>(friend); | ||
24 | private final SymbolView<Boolean> childrenView = new KeyOnlyView<>(children); | ||
25 | |||
26 | @Test | ||
27 | void equalToParameterTest() { | ||
28 | var actual = Dnf.of(builder -> { | ||
29 | var p = builder.parameter("p"); | ||
30 | builder.clause(q -> List.of( | ||
31 | friendView.call(p, q), | ||
32 | p.isEquivalent(q) | ||
33 | )); | ||
34 | }); | ||
35 | |||
36 | var expectedP = Variable.of("p"); | ||
37 | assertThat(actual, structurallyEqualTo( | ||
38 | List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)), | ||
39 | List.of( | ||
40 | List.of(friendView.call(expectedP, expectedP)) | ||
41 | ) | ||
42 | )); | ||
43 | } | ||
44 | |||
45 | @Test | ||
46 | void equalToParameterReverseTest() { | ||
47 | var actual = Dnf.of(builder -> { | ||
48 | var p = builder.parameter("p"); | ||
49 | builder.clause(q -> List.of( | ||
50 | friendView.call(p, q), | ||
51 | q.isEquivalent(p) | ||
52 | )); | ||
53 | }); | ||
54 | |||
55 | var expectedP = Variable.of("p"); | ||
56 | assertThat(actual, structurallyEqualTo( | ||
57 | List.of(new SymbolicParameter(expectedP, ParameterDirection.OUT)), | ||
58 | List.of( | ||
59 | List.of(friendView.call(expectedP, expectedP)) | ||
60 | ) | ||
61 | )); | ||
62 | } | ||
63 | |||
64 | @Test | ||
65 | void equalQuantifiedTest() { | ||
66 | var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of( | ||
67 | friendView.call(p, q), | ||
68 | p.isEquivalent(q) | ||
69 | ))); | ||
70 | |||
71 | var expectedP = Variable.of("p"); | ||
72 | assertThat(actual, structurallyEqualTo( | ||
73 | List.of(), | ||
74 | List.of( | ||
75 | List.of(friendView.call(expectedP, expectedP)) | ||
76 | ) | ||
77 | )); | ||
78 | } | ||
79 | |||
80 | @Test | ||
81 | void equalQuantifiedTransitiveTest() { | ||
82 | var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of( | ||
83 | friendView.call(p, q), | ||
84 | p.isEquivalent(q), | ||
85 | childrenView.call(p, r), | ||
86 | q.isEquivalent(r) | ||
87 | ))); | ||
88 | |||
89 | var expectedP = Variable.of("p"); | ||
90 | assertThat(actual, structurallyEqualTo( | ||
91 | List.of(), | ||
92 | List.of( | ||
93 | List.of(friendView.call(expectedP, expectedP), childrenView.call(expectedP, expectedP)) | ||
94 | ) | ||
95 | )); | ||
96 | } | ||
97 | |||
98 | @Test | ||
99 | void equalQuantifiedTransitiveRemoveDuplicateTest() { | ||
100 | var actual = Dnf.of(builder -> builder.clause((p, q, r) -> List.of( | ||
101 | friendView.call(p, q), | ||
102 | p.isEquivalent(q), | ||
103 | friendView.call(p, r), | ||
104 | q.isEquivalent(r) | ||
105 | ))); | ||
106 | |||
107 | var expectedP = Variable.of("p"); | ||
108 | assertThat(actual, structurallyEqualTo( | ||
109 | List.of(), | ||
110 | List.of( | ||
111 | List.of(friendView.call(expectedP, expectedP)) | ||
112 | ) | ||
113 | )); | ||
114 | } | ||
115 | |||
116 | @Test | ||
117 | void parametersEqualTest() { | ||
118 | var actual = Dnf.of(builder -> { | ||
119 | var p = builder.parameter("p"); | ||
120 | var q = builder.parameter("q"); | ||
121 | builder.clause( | ||
122 | friendView.call(p, q), | ||
123 | p.isEquivalent(q) | ||
124 | ); | ||
125 | }); | ||
126 | |||
127 | var expectedP = Variable.of("p"); | ||
128 | var expectedQ = Variable.of("q"); | ||
129 | assertThat(actual, structurallyEqualTo( | ||
130 | List.of( | ||
131 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
132 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
133 | ), | ||
134 | List.of( | ||
135 | List.of(friendView.call(expectedP, expectedP), expectedQ.isEquivalent(expectedP)) | ||
136 | ) | ||
137 | )); | ||
138 | } | ||
139 | |||
140 | @Test | ||
141 | void parametersEqualTransitiveTest() { | ||
142 | var actual = Dnf.of(builder -> { | ||
143 | var p = builder.parameter("p"); | ||
144 | var q = builder.parameter("q"); | ||
145 | var r = builder.parameter("r"); | ||
146 | builder.clause( | ||
147 | friendView.call(p, q), | ||
148 | childrenView.call(p, r), | ||
149 | p.isEquivalent(q), | ||
150 | r.isEquivalent(q) | ||
151 | ); | ||
152 | }); | ||
153 | |||
154 | var expectedP = Variable.of("p"); | ||
155 | var expectedQ = Variable.of("q"); | ||
156 | var expectedR = Variable.of("r"); | ||
157 | assertThat(actual, structurallyEqualTo( | ||
158 | List.of( | ||
159 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
160 | new SymbolicParameter(expectedQ, ParameterDirection.OUT), | ||
161 | new SymbolicParameter(expectedR, ParameterDirection.OUT) | ||
162 | ), | ||
163 | List.of( | ||
164 | List.of( | ||
165 | friendView.call(expectedP, expectedP), | ||
166 | expectedQ.isEquivalent(expectedP), | ||
167 | expectedR.isEquivalent(expectedP), | ||
168 | childrenView.call(expectedP, expectedP) | ||
169 | ) | ||
170 | ) | ||
171 | )); | ||
172 | } | ||
173 | |||
174 | @Test | ||
175 | void parameterAndQuantifiedEqualsTest() { | ||
176 | var actual = Dnf.of(builder -> { | ||
177 | var p = builder.parameter("p"); | ||
178 | var q = builder.parameter("q"); | ||
179 | builder.clause((r) -> List.of( | ||
180 | friendView.call(p, r), | ||
181 | p.isEquivalent(r), | ||
182 | childrenView.call(q, r), | ||
183 | q.isEquivalent(r) | ||
184 | )); | ||
185 | }); | ||
186 | |||
187 | |||
188 | var expectedP = Variable.of("p"); | ||
189 | var expectedQ = Variable.of("q"); | ||
190 | assertThat(actual, structurallyEqualTo( | ||
191 | List.of( | ||
192 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
193 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
194 | ), | ||
195 | List.of( | ||
196 | List.of( | ||
197 | friendView.call(expectedP, expectedP), | ||
198 | expectedQ.isEquivalent(expectedP), | ||
199 | childrenView.call(expectedP, expectedP) | ||
200 | ) | ||
201 | ) | ||
202 | )); | ||
203 | } | ||
204 | |||
205 | @Test | ||
206 | void parameterAndQuantifiedEqualsReverseFirstTest() { | ||
207 | var actual = Dnf.of(builder -> { | ||
208 | var p = builder.parameter("p"); | ||
209 | var q = builder.parameter("q"); | ||
210 | builder.clause((r) -> List.of( | ||
211 | friendView.call(p, r), | ||
212 | r.isEquivalent(p), | ||
213 | childrenView.call(q, r), | ||
214 | q.isEquivalent(r) | ||
215 | )); | ||
216 | }); | ||
217 | |||
218 | var expectedP = Variable.of("p"); | ||
219 | var expectedQ = Variable.of("q"); | ||
220 | assertThat(actual, structurallyEqualTo( | ||
221 | List.of( | ||
222 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
223 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
224 | ), | ||
225 | List.of( | ||
226 | List.of( | ||
227 | friendView.call(expectedP, expectedP), | ||
228 | expectedQ.isEquivalent(expectedP), | ||
229 | childrenView.call(expectedP, expectedP) | ||
230 | ) | ||
231 | ) | ||
232 | )); | ||
233 | } | ||
234 | |||
235 | @Test | ||
236 | void parameterAndQuantifiedEqualsReverseSecondTest() { | ||
237 | var actual = Dnf.of(builder -> { | ||
238 | var p = builder.parameter("p"); | ||
239 | var q = builder.parameter("q"); | ||
240 | builder.clause((r) -> List.of( | ||
241 | friendView.call(p, r), | ||
242 | p.isEquivalent(r), | ||
243 | childrenView.call(q, r), | ||
244 | r.isEquivalent(q) | ||
245 | )); | ||
246 | }); | ||
247 | |||
248 | var expectedP = Variable.of("p"); | ||
249 | var expectedQ = Variable.of("q"); | ||
250 | assertThat(actual, structurallyEqualTo( | ||
251 | List.of( | ||
252 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
253 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
254 | ), | ||
255 | List.of( | ||
256 | List.of( | ||
257 | friendView.call(expectedP, expectedP), | ||
258 | expectedQ.isEquivalent(expectedP), | ||
259 | childrenView.call(expectedP, expectedP) | ||
260 | ) | ||
261 | ) | ||
262 | )); | ||
263 | } | ||
264 | |||
265 | @Test | ||
266 | void parameterAndQuantifiedEqualsReverseBoth() { | ||
267 | var actual = Dnf.of(builder -> { | ||
268 | var p = builder.parameter("p"); | ||
269 | var q = builder.parameter("q"); | ||
270 | builder.clause((r) -> List.of( | ||
271 | friendView.call(p, r), | ||
272 | p.isEquivalent(r), | ||
273 | childrenView.call(q, r), | ||
274 | r.isEquivalent(q) | ||
275 | )); | ||
276 | }); | ||
277 | |||
278 | var expectedP = Variable.of("p"); | ||
279 | var expectedQ = Variable.of("q"); | ||
280 | assertThat(actual, structurallyEqualTo( | ||
281 | List.of( | ||
282 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
283 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
284 | ), | ||
285 | List.of( | ||
286 | List.of( | ||
287 | friendView.call(expectedP, expectedP), | ||
288 | expectedQ.isEquivalent(expectedP), | ||
289 | childrenView.call(expectedP, expectedP) | ||
290 | ) | ||
291 | ) | ||
292 | )); | ||
293 | } | ||
294 | |||
295 | @Test | ||
296 | void parameterAndTwoQuantifiedEqualsTest() { | ||
297 | var actual = Dnf.of(builder -> { | ||
298 | var p = builder.parameter("p"); | ||
299 | var q = builder.parameter("q"); | ||
300 | builder.clause((r, s) -> List.of( | ||
301 | r.isEquivalent(s), | ||
302 | friendView.call(p, r), | ||
303 | p.isEquivalent(r), | ||
304 | childrenView.call(q, s), | ||
305 | q.isEquivalent(s) | ||
306 | )); | ||
307 | }); | ||
308 | |||
309 | var expectedP = Variable.of("p"); | ||
310 | var expectedQ = Variable.of("q"); | ||
311 | assertThat(actual, structurallyEqualTo( | ||
312 | List.of( | ||
313 | new SymbolicParameter(expectedP, ParameterDirection.OUT), | ||
314 | new SymbolicParameter(expectedQ, ParameterDirection.OUT) | ||
315 | ), | ||
316 | List.of( | ||
317 | List.of( | ||
318 | friendView.call(expectedP, expectedP), | ||
319 | expectedQ.isEquivalent(expectedP), | ||
320 | childrenView.call(expectedP, expectedP) | ||
321 | ) | ||
322 | ) | ||
323 | )); | ||
324 | } | ||
325 | } | ||
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 new file mode 100644 index 00000000..d75d7f17 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java | |||
@@ -0,0 +1,157 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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 org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.view.AnySymbolView; | ||
13 | import tools.refinery.store.query.view.KeyOnlyView; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.is; | ||
18 | import static tools.refinery.store.query.literal.Literals.not; | ||
19 | |||
20 | class DnfToDefinitionStringTest { | ||
21 | private static final Symbol<Boolean> person = Symbol.of("person", 1); | ||
22 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
23 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
24 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
25 | private static final NodeVariable p = Variable.of("p"); | ||
26 | private static final NodeVariable q = Variable.of("q"); | ||
27 | |||
28 | @Test | ||
29 | void noClausesTest() { | ||
30 | var dnf = Dnf.builder("Example").parameter(p).build(); | ||
31 | |||
32 | assertThat(dnf.toDefinitionString(), is(""" | ||
33 | pred Example(p) <-> | ||
34 | <no clauses>. | ||
35 | """)); | ||
36 | } | ||
37 | |||
38 | @Test | ||
39 | void noParametersTest() { | ||
40 | var dnf = Dnf.builder("Example").build(); | ||
41 | |||
42 | assertThat(dnf.toDefinitionString(), is(""" | ||
43 | pred Example() <-> | ||
44 | <no clauses>. | ||
45 | """)); | ||
46 | } | ||
47 | |||
48 | @Test | ||
49 | void emptyClauseTest() { | ||
50 | var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build(); | ||
51 | |||
52 | assertThat(dnf.toDefinitionString(), is(""" | ||
53 | pred Example(@In p) <-> | ||
54 | <empty>. | ||
55 | """)); | ||
56 | } | ||
57 | |||
58 | @Test | ||
59 | void relationViewPositiveTest() { | ||
60 | var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build(); | ||
61 | |||
62 | assertThat(dnf.toDefinitionString(), is(""" | ||
63 | pred Example(p) <-> | ||
64 | @RelationView("key") friend(p, q). | ||
65 | """)); | ||
66 | } | ||
67 | |||
68 | @Test | ||
69 | void relationViewNegativeTest() { | ||
70 | var dnf = Dnf.builder("Example") | ||
71 | .parameter(p, ParameterDirection.IN) | ||
72 | .clause(not(friendView.call(p, q))) | ||
73 | .build(); | ||
74 | |||
75 | assertThat(dnf.toDefinitionString(), is(""" | ||
76 | pred Example(@In p) <-> | ||
77 | !(@RelationView("key") friend(p, q)). | ||
78 | """)); | ||
79 | } | ||
80 | |||
81 | @Test | ||
82 | void relationViewTransitiveTest() { | ||
83 | var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build(); | ||
84 | |||
85 | assertThat(dnf.toDefinitionString(), is(""" | ||
86 | pred Example(p) <-> | ||
87 | @RelationView("key") friend+(p, q). | ||
88 | """)); | ||
89 | } | ||
90 | |||
91 | @Test | ||
92 | void multipleParametersTest() { | ||
93 | var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build(); | ||
94 | |||
95 | assertThat(dnf.toDefinitionString(), is(""" | ||
96 | pred Example(p, q) <-> | ||
97 | @RelationView("key") friend(p, q). | ||
98 | """)); | ||
99 | } | ||
100 | |||
101 | @Test | ||
102 | void multipleLiteralsTest() { | ||
103 | var dnf = Dnf.builder("Example") | ||
104 | .parameter(p) | ||
105 | .clause( | ||
106 | personView.call(p), | ||
107 | personView.call(q), | ||
108 | friendView.call(p, q) | ||
109 | ) | ||
110 | .build(); | ||
111 | |||
112 | assertThat(dnf.toDefinitionString(), is(""" | ||
113 | pred Example(p) <-> | ||
114 | @RelationView("key") person(p), | ||
115 | @RelationView("key") person(q), | ||
116 | @RelationView("key") friend(p, q). | ||
117 | """)); | ||
118 | } | ||
119 | |||
120 | @Test | ||
121 | void multipleClausesTest() { | ||
122 | var dnf = Dnf.builder("Example") | ||
123 | .parameter(p) | ||
124 | .clause(friendView.call(p, q)) | ||
125 | .clause(friendView.call(q, p)) | ||
126 | .build(); | ||
127 | |||
128 | assertThat(dnf.toDefinitionString(), is(""" | ||
129 | pred Example(p) <-> | ||
130 | @RelationView("key") friend(p, q) | ||
131 | ; | ||
132 | @RelationView("key") friend(q, p). | ||
133 | """)); | ||
134 | } | ||
135 | |||
136 | @Test | ||
137 | void dnfTest() { | ||
138 | var r = Variable.of("r"); | ||
139 | var s = Variable.of("s"); | ||
140 | var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build(); | ||
141 | var dnf = Dnf.builder("Example") | ||
142 | .parameter(p) | ||
143 | .clause( | ||
144 | personView.call(p), | ||
145 | personView.call(q), | ||
146 | not(called.call(p, q)) | ||
147 | ) | ||
148 | .build(); | ||
149 | |||
150 | assertThat(dnf.toDefinitionString(), is(""" | ||
151 | pred Example(p) <-> | ||
152 | @RelationView("key") person(p), | ||
153 | @RelationView("key") person(q), | ||
154 | !(@Dnf Called(p, q)). | ||
155 | """)); | ||
156 | } | ||
157 | } | ||
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 new file mode 100644 index 00000000..e22dbb21 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.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 org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.term.NodeVariable; | ||
10 | import tools.refinery.store.query.term.ParameterDirection; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.view.AnySymbolView; | ||
13 | import tools.refinery.store.query.view.KeyOnlyView; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
20 | import static tools.refinery.store.query.literal.Literals.not; | ||
21 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
22 | |||
23 | class TopologicalSortTest { | ||
24 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
25 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
26 | private static final Dnf example = Dnf.of("example", builder -> { | ||
27 | var a = builder.parameter("a", ParameterDirection.IN); | ||
28 | var b = builder.parameter("b", ParameterDirection.IN); | ||
29 | var c = builder.parameter("c", ParameterDirection.OUT); | ||
30 | var d = builder.parameter("d", ParameterDirection.OUT); | ||
31 | builder.clause( | ||
32 | friendView.call(a, b), | ||
33 | friendView.call(b, c), | ||
34 | friendView.call(c, d) | ||
35 | ); | ||
36 | }); | ||
37 | private static final NodeVariable p = Variable.of("p"); | ||
38 | private static final NodeVariable q = Variable.of("q"); | ||
39 | private static final NodeVariable r = Variable.of("r"); | ||
40 | private static final NodeVariable s = Variable.of("s"); | ||
41 | private static final NodeVariable t = Variable.of("t"); | ||
42 | |||
43 | @Test | ||
44 | void topologicalSortTest() { | ||
45 | var actual = Dnf.builder("Actual") | ||
46 | .parameter(p, ParameterDirection.IN) | ||
47 | .parameter(q, ParameterDirection.OUT) | ||
48 | .clause( | ||
49 | not(friendView.call(p, q)), | ||
50 | example.call(p, q, r, s), | ||
51 | example.call(r, t, q, s), | ||
52 | friendView.call(r, t) | ||
53 | ) | ||
54 | .build(); | ||
55 | |||
56 | assertThat(actual, structurallyEqualTo( | ||
57 | List.of( | ||
58 | new SymbolicParameter(p, ParameterDirection.IN), | ||
59 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
60 | ), | ||
61 | List.of( | ||
62 | List.of( | ||
63 | friendView.call(r, t), | ||
64 | example.call(r, t, q, s), | ||
65 | not(friendView.call(p, q)), | ||
66 | example.call(p, q, r, s) | ||
67 | ) | ||
68 | ) | ||
69 | )); | ||
70 | } | ||
71 | |||
72 | @Test | ||
73 | void missingInputTest() { | ||
74 | var builder = Dnf.builder("Actual") | ||
75 | .parameter(p, ParameterDirection.OUT) | ||
76 | .parameter(q, ParameterDirection.OUT) | ||
77 | .clause( | ||
78 | not(friendView.call(p, q)), | ||
79 | example.call(p, q, r, s), | ||
80 | example.call(r, t, q, s), | ||
81 | friendView.call(r, t) | ||
82 | ); | ||
83 | assertThrows(IllegalArgumentException.class, builder::build); | ||
84 | } | ||
85 | |||
86 | @Test | ||
87 | void missingVariableTest() { | ||
88 | var builder = Dnf.builder("Actual") | ||
89 | .parameter(p, ParameterDirection.IN) | ||
90 | .parameter(q, ParameterDirection.OUT) | ||
91 | .clause( | ||
92 | not(friendView.call(p, q)), | ||
93 | example.call(p, q, r, s), | ||
94 | example.call(r, t, q, s) | ||
95 | ); | ||
96 | assertThrows(IllegalArgumentException.class, builder::build); | ||
97 | } | ||
98 | |||
99 | @Test | ||
100 | void circularDependencyTest() { | ||
101 | var builder = Dnf.builder("Actual") | ||
102 | .parameter(p, ParameterDirection.IN) | ||
103 | .parameter(q, ParameterDirection.OUT) | ||
104 | .clause( | ||
105 | not(friendView.call(p, q)), | ||
106 | example.call(p, q, r, s), | ||
107 | example.call(r, t, q, s), | ||
108 | example.call(p, q, r, t) | ||
109 | ); | ||
110 | assertThrows(IllegalArgumentException.class, builder::build); | ||
111 | } | ||
112 | } | ||
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 new file mode 100644 index 00000000..c52d26b2 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java | |||
@@ -0,0 +1,428 @@ | |||
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 org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.MethodSource; | ||
11 | import tools.refinery.store.query.literal.BooleanLiteral; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.NodeVariable; | ||
15 | import tools.refinery.store.query.term.ParameterDirection; | ||
16 | import tools.refinery.store.query.term.Variable; | ||
17 | import tools.refinery.store.query.view.AnySymbolView; | ||
18 | import tools.refinery.store.query.view.FunctionView; | ||
19 | import tools.refinery.store.query.view.KeyOnlyView; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
21 | |||
22 | import java.util.ArrayList; | ||
23 | import java.util.List; | ||
24 | import java.util.stream.Stream; | ||
25 | |||
26 | import static org.hamcrest.MatcherAssert.assertThat; | ||
27 | import static org.hamcrest.Matchers.hasItem; | ||
28 | import static org.hamcrest.Matchers.not; | ||
29 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
30 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
31 | import static tools.refinery.store.query.literal.Literals.assume; | ||
32 | import static tools.refinery.store.query.literal.Literals.not; | ||
33 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
34 | |||
35 | class VariableDirectionTest { | ||
36 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
37 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
38 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
39 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
40 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
41 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
42 | private static final NodeVariable p = Variable.of("p"); | ||
43 | private static final NodeVariable q = Variable.of("q"); | ||
44 | private static final DataVariable<Integer> x = Variable.of("x", Integer.class); | ||
45 | private static final DataVariable<Integer> y = Variable.of("y", Integer.class); | ||
46 | private static final DataVariable<Integer> z = Variable.of("z", Integer.class); | ||
47 | |||
48 | @ParameterizedTest | ||
49 | @MethodSource("clausesWithVariableInput") | ||
50 | void unboundOutVariableTest(List<? extends Literal> clause) { | ||
51 | var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause); | ||
52 | assertThrows(IllegalArgumentException.class, builder::build); | ||
53 | } | ||
54 | |||
55 | @ParameterizedTest | ||
56 | @MethodSource("clausesWithVariableInput") | ||
57 | void unboundInVariableTest(List<? extends Literal> clause) { | ||
58 | var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(clause); | ||
59 | var dnf = assertDoesNotThrow(builder::build); | ||
60 | var clauses = dnf.getClauses(); | ||
61 | if (clauses.size() > 0) { | ||
62 | assertThat(clauses.get(0).positiveVariables(), hasItem(p)); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | @ParameterizedTest | ||
67 | @MethodSource("clausesWithVariableInput") | ||
68 | void boundPrivateVariableTest(List<? extends Literal> clause) { | ||
69 | var clauseWithBinding = new ArrayList<Literal>(clause); | ||
70 | clauseWithBinding.add(personView.call(p)); | ||
71 | var builder = Dnf.builder().clause(clauseWithBinding); | ||
72 | var dnf = assertDoesNotThrow(builder::build); | ||
73 | var clauses = dnf.getClauses(); | ||
74 | if (clauses.size() > 0) { | ||
75 | assertThat(clauses.get(0).positiveVariables(), hasItem(p)); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | static Stream<Arguments> clausesWithVariableInput() { | ||
80 | return Stream.concat( | ||
81 | clausesNotBindingVariable(), | ||
82 | literalToClauseArgumentStream(literalsWithRequiredVariableInput()) | ||
83 | ); | ||
84 | } | ||
85 | |||
86 | @ParameterizedTest | ||
87 | @MethodSource("clausesNotBindingVariable") | ||
88 | void unboundPrivateVariableTest(List<? extends Literal> clause) { | ||
89 | var builder = Dnf.builder().clause(clause); | ||
90 | var dnf = assertDoesNotThrow(builder::build); | ||
91 | var clauses = dnf.getClauses(); | ||
92 | if (clauses.size() > 0) { | ||
93 | assertThat(clauses.get(0).positiveVariables(), not(hasItem(p))); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | @ParameterizedTest | ||
98 | @MethodSource("clausesNotBindingVariable") | ||
99 | void unboundByEquivalencePrivateVariableTest(List<? extends Literal> clause) { | ||
100 | var r = Variable.of("r"); | ||
101 | var clauseWithEquivalence = new ArrayList<Literal>(clause); | ||
102 | clauseWithEquivalence.add(r.isEquivalent(p)); | ||
103 | var builder = Dnf.builder().clause(clauseWithEquivalence); | ||
104 | assertThrows(IllegalArgumentException.class, builder::build); | ||
105 | } | ||
106 | |||
107 | static Stream<Arguments> clausesNotBindingVariable() { | ||
108 | return Stream.concat( | ||
109 | Stream.of( | ||
110 | Arguments.of(List.of()), | ||
111 | Arguments.of(List.of(BooleanLiteral.TRUE)), | ||
112 | Arguments.of(List.of(BooleanLiteral.FALSE)) | ||
113 | ), | ||
114 | literalToClauseArgumentStream(literalsWithPrivateVariable()) | ||
115 | ); | ||
116 | } | ||
117 | |||
118 | @ParameterizedTest | ||
119 | @MethodSource("literalsWithPrivateVariable") | ||
120 | void unboundTwicePrivateVariableTest(Literal literal) { | ||
121 | var builder = Dnf.builder().clause(not(personView.call(p)), literal); | ||
122 | assertThrows(IllegalArgumentException.class, builder::build); | ||
123 | } | ||
124 | |||
125 | @ParameterizedTest | ||
126 | @MethodSource("literalsWithPrivateVariable") | ||
127 | void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) { | ||
128 | var r = Variable.of("r"); | ||
129 | var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal); | ||
130 | assertThrows(IllegalArgumentException.class, builder::build); | ||
131 | } | ||
132 | |||
133 | static Stream<Arguments> literalsWithPrivateVariable() { | ||
134 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
135 | .parameter(p, ParameterDirection.OUT) | ||
136 | .parameter(q, ParameterDirection.OUT) | ||
137 | .clause(friendView.call(p, q)) | ||
138 | .build(); | ||
139 | var dnfWithOutputToAggregate = Dnf.builder("WithOutputToAggregate") | ||
140 | .parameter(p, ParameterDirection.OUT) | ||
141 | .parameter(q, ParameterDirection.OUT) | ||
142 | .parameter(x, ParameterDirection.OUT) | ||
143 | .clause( | ||
144 | friendView.call(p, q), | ||
145 | ageView.call(q, x) | ||
146 | ) | ||
147 | .build(); | ||
148 | |||
149 | return Stream.of( | ||
150 | Arguments.of(not(friendView.call(p, q))), | ||
151 | Arguments.of(y.assign(friendView.count(p, q))), | ||
152 | Arguments.of(y.assign(ageView.aggregate(INT_SUM, p))), | ||
153 | Arguments.of(not(dnfWithOutput.call(p, q))), | ||
154 | Arguments.of(y.assign(dnfWithOutput.count(p, q))), | ||
155 | Arguments.of(y.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, p, q, z))) | ||
156 | ); | ||
157 | } | ||
158 | |||
159 | @ParameterizedTest | ||
160 | @MethodSource("literalsWithRequiredVariableInput") | ||
161 | void unboundPrivateVariableTest(Literal literal) { | ||
162 | var builder = Dnf.builder().clause(literal); | ||
163 | assertThrows(IllegalArgumentException.class, builder::build); | ||
164 | } | ||
165 | |||
166 | @ParameterizedTest | ||
167 | @MethodSource("literalsWithRequiredVariableInput") | ||
168 | void boundPrivateVariableInputTest(Literal literal) { | ||
169 | var builder = Dnf.builder().clause(personView.call(p), literal); | ||
170 | var dnf = assertDoesNotThrow(builder::build); | ||
171 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
172 | } | ||
173 | |||
174 | static Stream<Arguments> literalsWithRequiredVariableInput() { | ||
175 | var dnfWithInput = Dnf.builder("WithInput") | ||
176 | .parameter(p, ParameterDirection.IN) | ||
177 | .parameter(q, ParameterDirection.OUT) | ||
178 | .clause(friendView.call(p, q)).build(); | ||
179 | var dnfWithInputToAggregate = Dnf.builder("WithInputToAggregate") | ||
180 | .parameter(p, ParameterDirection.IN) | ||
181 | .parameter(q, ParameterDirection.OUT) | ||
182 | .parameter(x, ParameterDirection.OUT) | ||
183 | .clause( | ||
184 | friendView.call(p, q), | ||
185 | ageView.call(q, x) | ||
186 | ).build(); | ||
187 | |||
188 | return Stream.of( | ||
189 | Arguments.of(dnfWithInput.call(p, q)), | ||
190 | Arguments.of(dnfWithInput.call(p, p)), | ||
191 | Arguments.of(not(dnfWithInput.call(p, q))), | ||
192 | Arguments.of(not(dnfWithInput.call(p, p))), | ||
193 | Arguments.of(y.assign(dnfWithInput.count(p, q))), | ||
194 | Arguments.of(y.assign(dnfWithInput.count(p, p))), | ||
195 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, q, z))), | ||
196 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, p, p, z))) | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | @ParameterizedTest | ||
201 | @MethodSource("literalsWithVariableOutput") | ||
202 | void boundParameterTest(Literal literal) { | ||
203 | var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(literal); | ||
204 | var dnf = assertDoesNotThrow(builder::build); | ||
205 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
206 | } | ||
207 | |||
208 | @ParameterizedTest | ||
209 | @MethodSource("literalsWithVariableOutput") | ||
210 | void boundTwiceParameterTest(Literal literal) { | ||
211 | var builder = Dnf.builder().parameter(p, ParameterDirection.IN).clause(literal); | ||
212 | var dnf = assertDoesNotThrow(builder::build); | ||
213 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
214 | } | ||
215 | |||
216 | @ParameterizedTest | ||
217 | @MethodSource("literalsWithVariableOutput") | ||
218 | void boundPrivateVariableOutputTest(Literal literal) { | ||
219 | var dnfWithInput = Dnf.builder("WithInput") | ||
220 | .parameter(p, ParameterDirection.IN) | ||
221 | .clause(personView.call(p)) | ||
222 | .build(); | ||
223 | var builder = Dnf.builder().clause(dnfWithInput.call(p), literal); | ||
224 | var dnf = assertDoesNotThrow(builder::build); | ||
225 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
226 | } | ||
227 | |||
228 | @ParameterizedTest | ||
229 | @MethodSource("literalsWithVariableOutput") | ||
230 | void boundTwicePrivateVariableOutputTest(Literal literal) { | ||
231 | var builder = Dnf.builder().clause(personView.call(p), literal); | ||
232 | var dnf = assertDoesNotThrow(builder::build); | ||
233 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(p)); | ||
234 | } | ||
235 | |||
236 | static Stream<Arguments> literalsWithVariableOutput() { | ||
237 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
238 | .parameter(p, ParameterDirection.OUT) | ||
239 | .parameter(q, ParameterDirection.OUT) | ||
240 | .clause(friendView.call(p, q)) | ||
241 | .build(); | ||
242 | |||
243 | return Stream.of( | ||
244 | Arguments.of(friendView.call(p, q)), | ||
245 | Arguments.of(dnfWithOutput.call(p, q)) | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | @ParameterizedTest | ||
250 | @MethodSource("clausesWithDataVariableInput") | ||
251 | void unboundOutDataVariableTest(List<? extends Literal> clause) { | ||
252 | var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(clause); | ||
253 | assertThrows(IllegalArgumentException.class, builder::build); | ||
254 | } | ||
255 | |||
256 | @ParameterizedTest | ||
257 | @MethodSource("clausesWithDataVariableInput") | ||
258 | void unboundInDataVariableTest(List<? extends Literal> clause) { | ||
259 | var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(clause); | ||
260 | var dnf = assertDoesNotThrow(builder::build); | ||
261 | var clauses = dnf.getClauses(); | ||
262 | if (clauses.size() > 0) { | ||
263 | assertThat(clauses.get(0).positiveVariables(), hasItem(x)); | ||
264 | } | ||
265 | } | ||
266 | |||
267 | @ParameterizedTest | ||
268 | @MethodSource("clausesWithDataVariableInput") | ||
269 | void boundPrivateDataVariableTest(List<? extends Literal> clause) { | ||
270 | var clauseWithBinding = new ArrayList<Literal>(clause); | ||
271 | clauseWithBinding.add(x.assign(constant(27))); | ||
272 | var builder = Dnf.builder().clause(clauseWithBinding); | ||
273 | var dnf = assertDoesNotThrow(builder::build); | ||
274 | var clauses = dnf.getClauses(); | ||
275 | if (clauses.size() > 0) { | ||
276 | assertThat(clauses.get(0).positiveVariables(), hasItem(x)); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | static Stream<Arguments> clausesWithDataVariableInput() { | ||
281 | return Stream.concat( | ||
282 | clausesNotBindingDataVariable(), | ||
283 | literalToClauseArgumentStream(literalsWithRequiredDataVariableInput()) | ||
284 | ); | ||
285 | } | ||
286 | |||
287 | @ParameterizedTest | ||
288 | @MethodSource("clausesNotBindingDataVariable") | ||
289 | void unboundPrivateDataVariableTest(List<? extends Literal> clause) { | ||
290 | var builder = Dnf.builder().clause(clause); | ||
291 | var dnf = assertDoesNotThrow(builder::build); | ||
292 | var clauses = dnf.getClauses(); | ||
293 | if (clauses.size() > 0) { | ||
294 | assertThat(clauses.get(0).positiveVariables(), not(hasItem(x))); | ||
295 | } | ||
296 | } | ||
297 | |||
298 | static Stream<Arguments> clausesNotBindingDataVariable() { | ||
299 | return Stream.concat( | ||
300 | Stream.of( | ||
301 | Arguments.of(List.of()), | ||
302 | Arguments.of(List.of(BooleanLiteral.TRUE)), | ||
303 | Arguments.of(List.of(BooleanLiteral.FALSE)) | ||
304 | ), | ||
305 | literalToClauseArgumentStream(literalsWithPrivateDataVariable()) | ||
306 | ); | ||
307 | } | ||
308 | |||
309 | @ParameterizedTest | ||
310 | @MethodSource("literalsWithPrivateDataVariable") | ||
311 | void unboundTwicePrivateDataVariableTest(Literal literal) { | ||
312 | var builder = Dnf.builder().clause(not(ageView.call(p, x)), literal); | ||
313 | assertThrows(IllegalArgumentException.class, builder::build); | ||
314 | } | ||
315 | |||
316 | static Stream<Arguments> literalsWithPrivateDataVariable() { | ||
317 | var dnfWithOutput = Dnf.builder("WithDataOutput") | ||
318 | .parameter(y, ParameterDirection.OUT) | ||
319 | .parameter(q, ParameterDirection.OUT) | ||
320 | .clause(ageView.call(q, y)) | ||
321 | .build(); | ||
322 | |||
323 | return Stream.of( | ||
324 | Arguments.of(not(ageView.call(q, x))), | ||
325 | Arguments.of(y.assign(ageView.count(q, x))), | ||
326 | Arguments.of(not(dnfWithOutput.call(x, q))) | ||
327 | ); | ||
328 | } | ||
329 | |||
330 | @ParameterizedTest | ||
331 | @MethodSource("literalsWithRequiredDataVariableInput") | ||
332 | void unboundPrivateDataVariableTest(Literal literal) { | ||
333 | var builder = Dnf.builder().clause(literal); | ||
334 | assertThrows(IllegalArgumentException.class, builder::build); | ||
335 | } | ||
336 | |||
337 | static Stream<Arguments> literalsWithRequiredDataVariableInput() { | ||
338 | var dnfWithInput = Dnf.builder("WithDataInput") | ||
339 | .parameter(y, ParameterDirection.IN) | ||
340 | .parameter(q, ParameterDirection.OUT) | ||
341 | .clause(ageView.call(q, x)) | ||
342 | .build(); | ||
343 | // We are passing {@code y} to the parameter named {@code right} of {@code greaterEq}. | ||
344 | @SuppressWarnings("SuspiciousNameCombination") | ||
345 | var dnfWithInputToAggregate = Dnf.builder("WithDataInputToAggregate") | ||
346 | .parameter(y, ParameterDirection.IN) | ||
347 | .parameter(q, ParameterDirection.OUT) | ||
348 | .parameter(x, ParameterDirection.OUT) | ||
349 | .clause( | ||
350 | friendView.call(p, q), | ||
351 | ageView.call(q, x), | ||
352 | assume(greaterEq(x, y)) | ||
353 | ) | ||
354 | .build(); | ||
355 | |||
356 | return Stream.of( | ||
357 | Arguments.of(dnfWithInput.call(x, q)), | ||
358 | Arguments.of(not(dnfWithInput.call(x, q))), | ||
359 | Arguments.of(y.assign(dnfWithInput.count(x, q))), | ||
360 | Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, x, q, z))) | ||
361 | ); | ||
362 | } | ||
363 | |||
364 | @ParameterizedTest | ||
365 | @MethodSource("literalsWithDataVariableOutput") | ||
366 | void boundDataParameterTest(Literal literal) { | ||
367 | var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(literal); | ||
368 | var dnf = assertDoesNotThrow(builder::build); | ||
369 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x)); | ||
370 | } | ||
371 | |||
372 | @ParameterizedTest | ||
373 | @MethodSource("literalsWithDataVariableOutput") | ||
374 | void boundTwiceDataParameterTest(Literal literal) { | ||
375 | var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(literal); | ||
376 | assertThrows(IllegalArgumentException.class, builder::build); | ||
377 | } | ||
378 | |||
379 | @ParameterizedTest | ||
380 | @MethodSource("literalsWithDataVariableOutput") | ||
381 | void boundPrivateDataVariableOutputTest(Literal literal) { | ||
382 | var dnfWithInput = Dnf.builder("WithInput") | ||
383 | .parameter(x, ParameterDirection.IN) | ||
384 | .clause(assume(greaterEq(x, constant(24)))) | ||
385 | .build(); | ||
386 | var builder = Dnf.builder().clause(dnfWithInput.call(x), literal); | ||
387 | var dnf = assertDoesNotThrow(builder::build); | ||
388 | assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x)); | ||
389 | } | ||
390 | |||
391 | @ParameterizedTest | ||
392 | @MethodSource("literalsWithDataVariableOutput") | ||
393 | void boundTwicePrivateDataVariableOutputTest(Literal literal) { | ||
394 | var builder = Dnf.builder().clause(x.assign(constant(27)), literal); | ||
395 | assertThrows(IllegalArgumentException.class, builder::build); | ||
396 | } | ||
397 | |||
398 | static Stream<Arguments> literalsWithDataVariableOutput() { | ||
399 | var dnfWithOutput = Dnf.builder("WithOutput") | ||
400 | .parameter(q, ParameterDirection.OUT) | ||
401 | .clause(personView.call(q)) | ||
402 | .build(); | ||
403 | var dnfWithDataOutput = Dnf.builder("WithDataOutput") | ||
404 | .parameter(y, ParameterDirection.OUT) | ||
405 | .parameter(q, ParameterDirection.OUT) | ||
406 | .clause(ageView.call(q, y)) | ||
407 | .build(); | ||
408 | var dnfWithOutputToAggregate = Dnf.builder("WithDataOutputToAggregate") | ||
409 | .parameter(q, ParameterDirection.OUT) | ||
410 | .parameter(y, ParameterDirection.OUT) | ||
411 | .clause(ageView.call(q, y)) | ||
412 | .build(); | ||
413 | |||
414 | return Stream.of( | ||
415 | Arguments.of(x.assign(constant(24))), | ||
416 | Arguments.of(ageView.call(q, x)), | ||
417 | Arguments.of(x.assign(personView.count(q))), | ||
418 | Arguments.of(x.assign(ageView.aggregate(INT_SUM, q))), | ||
419 | Arguments.of(dnfWithDataOutput.call(x, q)), | ||
420 | Arguments.of(x.assign(dnfWithOutput.count(q))), | ||
421 | Arguments.of(x.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, q, z))) | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | private static Stream<Arguments> literalToClauseArgumentStream(Stream<Arguments> literalArgumentsStream) { | ||
426 | return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0]))); | ||
427 | } | ||
428 | } | ||
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 new file mode 100644 index 00000000..35910e08 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java | |||
@@ -0,0 +1,88 @@ | |||
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.literal; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.Constraint; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.term.*; | ||
12 | |||
13 | import java.util.List; | ||
14 | import java.util.Set; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
18 | import static org.hamcrest.Matchers.empty; | ||
19 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
20 | 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.term.int_.IntTerms.INT_SUM; | ||
23 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
24 | |||
25 | class AggregationLiteralTest { | ||
26 | private static final NodeVariable p = Variable.of("p"); | ||
27 | private static final DataVariable<Integer> x = Variable.of("x", Integer.class); | ||
28 | private static final DataVariable<Integer> y = Variable.of("y", Integer.class); | ||
29 | private static final DataVariable<Integer> z = Variable.of("z", Integer.class); | ||
30 | private static final Constraint fakeConstraint = new Constraint() { | ||
31 | @Override | ||
32 | public String name() { | ||
33 | return getClass().getName(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public List<Parameter> getParameters() { | ||
38 | return List.of( | ||
39 | new Parameter(null, ParameterDirection.OUT), | ||
40 | new Parameter(Integer.class, ParameterDirection.OUT) | ||
41 | ); | ||
42 | } | ||
43 | }; | ||
44 | |||
45 | @Test | ||
46 | void parameterDirectionTest() { | ||
47 | var literal = x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)); | ||
48 | assertAll( | ||
49 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(x)), | ||
50 | () -> assertThat(literal.getInputVariables(Set.of()), empty()), | ||
51 | () -> assertThat(literal.getInputVariables(Set.of(p)), containsInAnyOrder(p)), | ||
52 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(p, y)), | ||
53 | () -> assertThat(literal.getPrivateVariables(Set.of(p)), containsInAnyOrder(y)) | ||
54 | ); | ||
55 | } | ||
56 | |||
57 | @Test | ||
58 | void missingAggregationVariableTest() { | ||
59 | var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z); | ||
60 | assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); | ||
61 | } | ||
62 | |||
63 | @Test | ||
64 | void circularAggregationVariableTest() { | ||
65 | var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x); | ||
66 | assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); | ||
67 | } | ||
68 | |||
69 | @Test | ||
70 | void unboundTwiceVariableTest() { | ||
71 | var builder = Dnf.builder() | ||
72 | .clause( | ||
73 | not(fakeConstraint.call(p, y)), | ||
74 | x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) | ||
75 | ); | ||
76 | assertThrows(IllegalArgumentException.class, builder::build); | ||
77 | } | ||
78 | |||
79 | @Test | ||
80 | void unboundBoundVariableTest() { | ||
81 | var builder = Dnf.builder() | ||
82 | .clause( | ||
83 | y.assign(constant(27)), | ||
84 | x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) | ||
85 | ); | ||
86 | assertThrows(IllegalArgumentException.class, builder::build); | ||
87 | } | ||
88 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java new file mode 100644 index 00000000..a01c6586 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java | |||
@@ -0,0 +1,94 @@ | |||
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.literal; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.Constraint; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.Parameter; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | |||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
20 | import static org.hamcrest.Matchers.empty; | ||
21 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
22 | import static tools.refinery.store.query.literal.Literals.not; | ||
23 | |||
24 | class CallLiteralTest { | ||
25 | private static final NodeVariable p = Variable.of("p"); | ||
26 | private static final NodeVariable q = Variable.of("q"); | ||
27 | private static final NodeVariable r = Variable.of("r"); | ||
28 | private static final NodeVariable s = Variable.of("s"); | ||
29 | |||
30 | private static final Constraint fakeConstraint = new Constraint() { | ||
31 | @Override | ||
32 | public String name() { | ||
33 | return getClass().getName(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public List<Parameter> getParameters() { | ||
38 | return List.of( | ||
39 | new Parameter(null, ParameterDirection.IN), | ||
40 | new Parameter(null, ParameterDirection.IN), | ||
41 | new Parameter(null, ParameterDirection.OUT), | ||
42 | new Parameter(null, ParameterDirection.OUT) | ||
43 | ); | ||
44 | } | ||
45 | }; | ||
46 | |||
47 | @Test | ||
48 | void notRepeatedPositiveDirectionTest() { | ||
49 | var literal = fakeConstraint.call(p, q, r, s); | ||
50 | assertAll( | ||
51 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(r, s)), | ||
52 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), | ||
53 | () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q)), | ||
54 | () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), | ||
55 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), empty()) | ||
56 | ); | ||
57 | } | ||
58 | |||
59 | @Test | ||
60 | void notRepeatedNegativeDirectionTest() { | ||
61 | var literal = not(fakeConstraint.call(p, q, r, s)); | ||
62 | assertAll( | ||
63 | () -> assertThat(literal.getOutputVariables(), empty()), | ||
64 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p, q)), | ||
65 | () -> assertThat(literal.getInputVariables(Set.of(p, q, r)), containsInAnyOrder(p, q, r)), | ||
66 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(r, s)), | ||
67 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q, r)), containsInAnyOrder(s)) | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void repeatedPositiveDirectionTest() { | ||
73 | var literal = fakeConstraint.call(p, p, q, q); | ||
74 | assertAll( | ||
75 | () -> assertThat(literal.getOutputVariables(), containsInAnyOrder(q)), | ||
76 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), | ||
77 | () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p)), | ||
78 | () -> assertThat(literal.getPrivateVariables(Set.of()), empty()), | ||
79 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) | ||
80 | ); | ||
81 | } | ||
82 | |||
83 | @Test | ||
84 | void repeatedNegativeDirectionTest() { | ||
85 | var literal = not(fakeConstraint.call(p, p, q, q)); | ||
86 | assertAll( | ||
87 | () -> assertThat(literal.getOutputVariables(), empty()), | ||
88 | () -> assertThat(literal.getInputVariables(Set.of()), containsInAnyOrder(p)), | ||
89 | () -> assertThat(literal.getInputVariables(Set.of(p, q)), containsInAnyOrder(p, q)), | ||
90 | () -> assertThat(literal.getPrivateVariables(Set.of()), containsInAnyOrder(q)), | ||
91 | () -> assertThat(literal.getPrivateVariables(Set.of(p, q)), empty()) | ||
92 | ); | ||
93 | } | ||
94 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java new file mode 100644 index 00000000..1cbc101a --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java | |||
@@ -0,0 +1,97 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term; | ||
7 | |||
8 | import org.junit.jupiter.api.Assertions; | ||
9 | import org.junit.jupiter.params.ParameterizedTest; | ||
10 | import org.junit.jupiter.params.provider.Arguments; | ||
11 | import org.junit.jupiter.params.provider.MethodSource; | ||
12 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
14 | import tools.refinery.store.query.substitution.Substitution; | ||
15 | import tools.refinery.store.query.term.bool.BoolTerms; | ||
16 | import tools.refinery.store.query.term.int_.IntTerms; | ||
17 | import tools.refinery.store.query.term.real.RealTerms; | ||
18 | import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms; | ||
19 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
20 | |||
21 | import java.util.List; | ||
22 | import java.util.stream.Stream; | ||
23 | |||
24 | class TermSubstitutionTest { | ||
25 | private final static DataVariable<Integer> intA = Variable.of("intA", Integer.class); | ||
26 | private final static DataVariable<Integer> intB = Variable.of("intB", Integer.class); | ||
27 | private final static DataVariable<Double> realA = Variable.of("realA", Double.class); | ||
28 | private final static DataVariable<Double> realB = Variable.of("realB", Double.class); | ||
29 | private final static DataVariable<Boolean> boolA = Variable.of("boolA", Boolean.class); | ||
30 | private final static DataVariable<Boolean> boolB = Variable.of("boolB", Boolean.class); | ||
31 | private final static DataVariable<UpperCardinality> upperCardinalityA = Variable.of("upperCardinalityA", | ||
32 | UpperCardinality.class); | ||
33 | private final static DataVariable<UpperCardinality> upperCardinalityB = Variable.of("upperCardinalityB", | ||
34 | UpperCardinality.class); | ||
35 | private final static Substitution substitution = Substitution.builder() | ||
36 | .put(intA, intB) | ||
37 | .put(intB, intA) | ||
38 | .put(realA, realB) | ||
39 | .put(realB, realA) | ||
40 | .put(boolA, boolB) | ||
41 | .put(boolB, boolA) | ||
42 | .put(upperCardinalityA, upperCardinalityB) | ||
43 | .put(upperCardinalityB, upperCardinalityA) | ||
44 | .build(); | ||
45 | |||
46 | @ParameterizedTest | ||
47 | @MethodSource | ||
48 | void substitutionTest(AnyTerm term) { | ||
49 | var substitutedTerm1 = term.substitute(substitution); | ||
50 | Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term"); | ||
51 | var helper = new LiteralEqualityHelper(Dnf::equals, List.of(), List.of()); | ||
52 | Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper"); | ||
53 | // The {@link #substitution} is its own inverse. | ||
54 | var substitutedTerm2 = substitutedTerm1.substitute(substitution); | ||
55 | Assertions.assertEquals(term, substitutedTerm2, "Original term is not equal to back-substituted term"); | ||
56 | } | ||
57 | |||
58 | static Stream<Arguments> substitutionTest() { | ||
59 | return Stream.of( | ||
60 | Arguments.of(IntTerms.plus(intA)), | ||
61 | Arguments.of(IntTerms.minus(intA)), | ||
62 | Arguments.of(IntTerms.add(intA, intB)), | ||
63 | Arguments.of(IntTerms.sub(intA, intB)), | ||
64 | Arguments.of(IntTerms.mul(intA, intB)), | ||
65 | Arguments.of(IntTerms.div(intA, intB)), | ||
66 | Arguments.of(IntTerms.pow(intA, intB)), | ||
67 | Arguments.of(IntTerms.min(intA, intB)), | ||
68 | Arguments.of(IntTerms.max(intA, intB)), | ||
69 | Arguments.of(IntTerms.eq(intA, intB)), | ||
70 | Arguments.of(IntTerms.notEq(intA, intB)), | ||
71 | Arguments.of(IntTerms.less(intA, intB)), | ||
72 | Arguments.of(IntTerms.lessEq(intA, intB)), | ||
73 | Arguments.of(IntTerms.greater(intA, intB)), | ||
74 | Arguments.of(IntTerms.greaterEq(intA, intB)), | ||
75 | Arguments.of(IntTerms.asInt(realA)), | ||
76 | Arguments.of(RealTerms.plus(realA)), | ||
77 | Arguments.of(RealTerms.minus(realA)), | ||
78 | Arguments.of(RealTerms.add(realA, realB)), | ||
79 | Arguments.of(RealTerms.sub(realA, realB)), | ||
80 | Arguments.of(RealTerms.mul(realA, realB)), | ||
81 | Arguments.of(RealTerms.div(realA, realB)), | ||
82 | Arguments.of(RealTerms.pow(realA, realB)), | ||
83 | Arguments.of(RealTerms.min(realA, realB)), | ||
84 | Arguments.of(RealTerms.max(realA, realB)), | ||
85 | Arguments.of(RealTerms.asReal(intA)), | ||
86 | Arguments.of(BoolTerms.not(boolA)), | ||
87 | Arguments.of(BoolTerms.and(boolA, boolB)), | ||
88 | Arguments.of(BoolTerms.or(boolA, boolB)), | ||
89 | Arguments.of(BoolTerms.xor(boolA, boolB)), | ||
90 | Arguments.of(RealTerms.eq(realA, realB)), | ||
91 | Arguments.of(UpperCardinalityTerms.add(upperCardinalityA, upperCardinalityB)), | ||
92 | Arguments.of(UpperCardinalityTerms.mul(upperCardinalityA, upperCardinalityB)), | ||
93 | Arguments.of(UpperCardinalityTerms.min(upperCardinalityA, upperCardinalityB)), | ||
94 | Arguments.of(UpperCardinalityTerms.max(upperCardinalityA, upperCardinalityB)) | ||
95 | ); | ||
96 | } | ||
97 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java new file mode 100644 index 00000000..beff705e --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java | |||
@@ -0,0 +1,75 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.bool; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.CsvSource; | ||
10 | import tools.refinery.store.query.valuation.Valuation; | ||
11 | |||
12 | import static org.hamcrest.MatcherAssert.assertThat; | ||
13 | import static org.hamcrest.Matchers.is; | ||
14 | |||
15 | class BoolTermsEvaluateTest { | ||
16 | @ParameterizedTest(name = "!{0} == {1}") | ||
17 | @CsvSource(value = { | ||
18 | "false, true", | ||
19 | "true, false", | ||
20 | "null, null" | ||
21 | }, nullValues = "null") | ||
22 | void notTest(Boolean a, Boolean result) { | ||
23 | var term = BoolTerms.not(BoolTerms.constant(a)); | ||
24 | assertThat(term.getType(), is(Boolean.class)); | ||
25 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
26 | } | ||
27 | |||
28 | @ParameterizedTest(name = "{0} && {1} == {2}") | ||
29 | @CsvSource(value = { | ||
30 | "false, false, false", | ||
31 | "false, true, false", | ||
32 | "true, false, false", | ||
33 | "true, true, true", | ||
34 | "false, null, null", | ||
35 | "null, false, null", | ||
36 | "null, null, null" | ||
37 | }, nullValues = "null") | ||
38 | void andTest(Boolean a, Boolean b, Boolean result) { | ||
39 | var term = BoolTerms.and(BoolTerms.constant(a), BoolTerms.constant(b)); | ||
40 | assertThat(term.getType(), is(Boolean.class)); | ||
41 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
42 | } | ||
43 | |||
44 | @ParameterizedTest(name = "{0} || {1} == {2}") | ||
45 | @CsvSource(value = { | ||
46 | "false, false, false", | ||
47 | "false, true, true", | ||
48 | "true, false, true", | ||
49 | "true, true, true", | ||
50 | "true, null, null", | ||
51 | "null, true, null", | ||
52 | "null, null, null" | ||
53 | }, nullValues = "null") | ||
54 | void orTest(Boolean a, Boolean b, Boolean result) { | ||
55 | var term = BoolTerms.or(BoolTerms.constant(a), BoolTerms.constant(b)); | ||
56 | assertThat(term.getType(), is(Boolean.class)); | ||
57 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
58 | } | ||
59 | |||
60 | @ParameterizedTest(name = "{0} ^^ {1} == {2}") | ||
61 | @CsvSource(value = { | ||
62 | "false, false, false", | ||
63 | "false, true, true", | ||
64 | "true, false, true", | ||
65 | "true, true, false", | ||
66 | "false, null, null", | ||
67 | "null, false, null", | ||
68 | "null, null, null" | ||
69 | }, nullValues = "null") | ||
70 | void xorTest(Boolean a, Boolean b, Boolean result) { | ||
71 | var term = BoolTerms.xor(BoolTerms.constant(a), BoolTerms.constant(b)); | ||
72 | assertThat(term.getType(), is(Boolean.class)); | ||
73 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
74 | } | ||
75 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java new file mode 100644 index 00000000..abe50d75 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java | |||
@@ -0,0 +1,259 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.int_; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.CsvSource; | ||
11 | import org.junit.jupiter.params.provider.MethodSource; | ||
12 | import tools.refinery.store.query.term.real.RealTerms; | ||
13 | import tools.refinery.store.query.valuation.Valuation; | ||
14 | |||
15 | import java.util.stream.Stream; | ||
16 | |||
17 | import static org.hamcrest.Matchers.is; | ||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | |||
20 | class IntTermsEvaluateTest { | ||
21 | @ParameterizedTest(name = "+{0} == {1}") | ||
22 | @CsvSource(value = { | ||
23 | "2, 2", | ||
24 | "null, null" | ||
25 | }, nullValues = "null") | ||
26 | void plusTest(Integer a, Integer result) { | ||
27 | var term = IntTerms.plus(IntTerms.constant(a)); | ||
28 | assertThat(term.getType(), is(Integer.class)); | ||
29 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
30 | } | ||
31 | |||
32 | @ParameterizedTest(name = "-{0} == {1}") | ||
33 | @CsvSource(value = { | ||
34 | "2, -2", | ||
35 | "null, null" | ||
36 | }, nullValues = "null") | ||
37 | void minusTest(Integer a, Integer result) { | ||
38 | var term = IntTerms.minus(IntTerms.constant(a)); | ||
39 | assertThat(term.getType(), is(Integer.class)); | ||
40 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
41 | } | ||
42 | |||
43 | @ParameterizedTest(name = "{0} + {1} == {2}") | ||
44 | @CsvSource(value = { | ||
45 | "1, 2, 3", | ||
46 | "null, 2, null", | ||
47 | "1, null, null", | ||
48 | "null, null, null" | ||
49 | }, nullValues = "null") | ||
50 | void addTest(Integer a, Integer b, Integer result) { | ||
51 | var term = IntTerms.add(IntTerms.constant(a), IntTerms.constant(b)); | ||
52 | assertThat(term.getType(), is(Integer.class)); | ||
53 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
54 | } | ||
55 | |||
56 | @ParameterizedTest(name = "{0} - {1} == {2}") | ||
57 | @CsvSource(value = { | ||
58 | "1, 3, -2", | ||
59 | "null, 3, null", | ||
60 | "1, null, null", | ||
61 | "null, null, null" | ||
62 | }, nullValues = "null") | ||
63 | void subTest(Integer a, Integer b, Integer result) { | ||
64 | var term = IntTerms.sub(IntTerms.constant(a), IntTerms.constant(b)); | ||
65 | assertThat(term.getType(), is(Integer.class)); | ||
66 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
67 | } | ||
68 | |||
69 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
70 | @CsvSource(value = { | ||
71 | "2, 3, 6", | ||
72 | "null, 3, null", | ||
73 | "2, null, null", | ||
74 | "null, null, null" | ||
75 | }, nullValues = "null") | ||
76 | void mulTest(Integer a, Integer b, Integer result) { | ||
77 | var term = IntTerms.mul(IntTerms.constant(a), IntTerms.constant(b)); | ||
78 | assertThat(term.getType(), is(Integer.class)); | ||
79 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
80 | } | ||
81 | |||
82 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
83 | @CsvSource(value = { | ||
84 | "6, 3, 2", | ||
85 | "7, 3, 2", | ||
86 | "6, 0, null", | ||
87 | "null, 3, null", | ||
88 | "6, null, null", | ||
89 | "null, null, null" | ||
90 | }, nullValues = "null") | ||
91 | void divTest(Integer a, Integer b, Integer result) { | ||
92 | var term = IntTerms.div(IntTerms.constant(a), IntTerms.constant(b)); | ||
93 | assertThat(term.getType(), is(Integer.class)); | ||
94 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
95 | } | ||
96 | |||
97 | @ParameterizedTest(name = "{0} ** {1} == {2}") | ||
98 | @CsvSource(value = { | ||
99 | "1, 0, 1", | ||
100 | "1, 3, 1", | ||
101 | "1, -3, null", | ||
102 | "2, 0, 1", | ||
103 | "2, 2, 4", | ||
104 | "2, 3, 8", | ||
105 | "2, 4, 16", | ||
106 | "2, 5, 32", | ||
107 | "2, 6, 64", | ||
108 | "2, -3, null", | ||
109 | "null, 3, null", | ||
110 | "2, null, null", | ||
111 | "null, null, null" | ||
112 | }, nullValues = "null") | ||
113 | void powTest(Integer a, Integer b, Integer result) { | ||
114 | var term = IntTerms.pow(IntTerms.constant(a), IntTerms.constant(b)); | ||
115 | assertThat(term.getType(), is(Integer.class)); | ||
116 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
117 | } | ||
118 | |||
119 | @ParameterizedTest(name = "min({0}, {1}) == {2}") | ||
120 | @CsvSource(value = { | ||
121 | "1, 2, 1", | ||
122 | "2, 1, 1", | ||
123 | "null, 2, null", | ||
124 | "1, null, null", | ||
125 | "null, null, null" | ||
126 | }, nullValues = "null") | ||
127 | void minTest(Integer a, Integer b, Integer result) { | ||
128 | var term = IntTerms.min(IntTerms.constant(a), IntTerms.constant(b)); | ||
129 | assertThat(term.getType(), is(Integer.class)); | ||
130 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
131 | } | ||
132 | |||
133 | @ParameterizedTest(name = "max({0}, {1}) == {2}") | ||
134 | @CsvSource(value = { | ||
135 | "1, 2, 2", | ||
136 | "2, 1, 2", | ||
137 | "null, 2, null", | ||
138 | "1, null, null", | ||
139 | "null, null, null" | ||
140 | }, nullValues = "null") | ||
141 | void maxTest(Integer a, Integer b, Integer result) { | ||
142 | var term = IntTerms.max(IntTerms.constant(a), IntTerms.constant(b)); | ||
143 | assertThat(term.getType(), is(Integer.class)); | ||
144 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
145 | } | ||
146 | |||
147 | @ParameterizedTest(name = "({0} == {1}) == {2}") | ||
148 | @CsvSource(value = { | ||
149 | "1, 1, true", | ||
150 | "1, 2, false", | ||
151 | "null, 1, null", | ||
152 | "1, null, null", | ||
153 | "null, null, null" | ||
154 | }, nullValues = "null") | ||
155 | void eqTest(Integer a, Integer b, Boolean result) { | ||
156 | var term = IntTerms.eq(IntTerms.constant(a), IntTerms.constant(b)); | ||
157 | assertThat(term.getType(), is(Boolean.class)); | ||
158 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
159 | } | ||
160 | |||
161 | @ParameterizedTest(name = "({0} != {1}) == {2}") | ||
162 | @CsvSource(value = { | ||
163 | "1, 1, false", | ||
164 | "1, 2, true", | ||
165 | "null, 1, null", | ||
166 | "1, null, null", | ||
167 | "null, null, null" | ||
168 | }, nullValues = "null") | ||
169 | void notEqTest(Integer a, Integer b, Boolean result) { | ||
170 | var term = IntTerms.notEq(IntTerms.constant(a), IntTerms.constant(b)); | ||
171 | assertThat(term.getType(), is(Boolean.class)); | ||
172 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
173 | } | ||
174 | |||
175 | @ParameterizedTest(name = "({0} < {1}) == {2}") | ||
176 | @CsvSource(value = { | ||
177 | "1, -2, false", | ||
178 | "1, 1, false", | ||
179 | "1, 2, true", | ||
180 | "null, 1, null", | ||
181 | "1, null, null", | ||
182 | "null, null, null" | ||
183 | }, nullValues = "null") | ||
184 | void lessTest(Integer a, Integer b, Boolean result) { | ||
185 | var term = IntTerms.less(IntTerms.constant(a), IntTerms.constant(b)); | ||
186 | assertThat(term.getType(), is(Boolean.class)); | ||
187 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
188 | } | ||
189 | |||
190 | @ParameterizedTest(name = "({0} <= {1}) == {2}") | ||
191 | @CsvSource(value = { | ||
192 | "1, -2, false", | ||
193 | "1, 1, true", | ||
194 | "1, 2, true", | ||
195 | "null, 1, null", | ||
196 | "1, null, null", | ||
197 | "null, null, null" | ||
198 | }, nullValues = "null") | ||
199 | void lessEqTest(Integer a, Integer b, Boolean result) { | ||
200 | var term = IntTerms.lessEq(IntTerms.constant(a), IntTerms.constant(b)); | ||
201 | assertThat(term.getType(), is(Boolean.class)); | ||
202 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
203 | } | ||
204 | |||
205 | @ParameterizedTest(name = "({0} > {1}) == {2}") | ||
206 | @CsvSource(value = { | ||
207 | "1, -2, true", | ||
208 | "1, 1, false", | ||
209 | "1, 2, false", | ||
210 | "null, 1, null", | ||
211 | "1, null, null", | ||
212 | "null, null, null" | ||
213 | }, nullValues = "null") | ||
214 | void greaterTest(Integer a, Integer b, Boolean result) { | ||
215 | var term = IntTerms.greater(IntTerms.constant(a), IntTerms.constant(b)); | ||
216 | assertThat(term.getType(), is(Boolean.class)); | ||
217 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
218 | } | ||
219 | |||
220 | @ParameterizedTest(name = "({0} >= {1}) == {2}") | ||
221 | @CsvSource(value = { | ||
222 | "1, -2, true", | ||
223 | "1, 1, true", | ||
224 | "1, 2, false", | ||
225 | "null, 1, null", | ||
226 | "1, null, null", | ||
227 | "null, null, null" | ||
228 | }, nullValues = "null") | ||
229 | void greaterEqTest(Integer a, Integer b, Boolean result) { | ||
230 | var term = IntTerms.greaterEq(IntTerms.constant(a), IntTerms.constant(b)); | ||
231 | assertThat(term.getType(), is(Boolean.class)); | ||
232 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
233 | } | ||
234 | |||
235 | @ParameterizedTest(name = "{0} as int == {1}") | ||
236 | @MethodSource | ||
237 | void asIntTest(Double a, Integer result) { | ||
238 | var term = IntTerms.asInt(RealTerms.constant(a)); | ||
239 | assertThat(term.getType(), is(Integer.class)); | ||
240 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
241 | } | ||
242 | |||
243 | static Stream<Arguments> asIntTest() { | ||
244 | return Stream.of( | ||
245 | Arguments.of(2.0, 2), | ||
246 | Arguments.of(2.1, 2), | ||
247 | Arguments.of(2.9, 2), | ||
248 | Arguments.of(-2.0, -2), | ||
249 | Arguments.of(-2.1, -2), | ||
250 | Arguments.of(-2.9, -2), | ||
251 | Arguments.of(0.0, 0), | ||
252 | Arguments.of(-0.0, 0), | ||
253 | Arguments.of(Double.POSITIVE_INFINITY, Integer.MAX_VALUE), | ||
254 | Arguments.of(Double.NEGATIVE_INFINITY, Integer.MIN_VALUE), | ||
255 | Arguments.of(Double.NaN, null), | ||
256 | Arguments.of(null, null) | ||
257 | ); | ||
258 | } | ||
259 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java new file mode 100644 index 00000000..6a8eebf1 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java | |||
@@ -0,0 +1,238 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.real; | ||
7 | |||
8 | import org.hamcrest.Matcher; | ||
9 | import org.junit.jupiter.params.ParameterizedTest; | ||
10 | import org.junit.jupiter.params.provider.CsvSource; | ||
11 | import tools.refinery.store.query.term.int_.IntTerms; | ||
12 | import tools.refinery.store.query.valuation.Valuation; | ||
13 | |||
14 | import static org.hamcrest.MatcherAssert.assertThat; | ||
15 | import static org.hamcrest.Matchers.*; | ||
16 | |||
17 | class RealTermEvaluateTest { | ||
18 | public static final double TOLERANCE = 1e-6; | ||
19 | |||
20 | private static Matcher<Double> closeToOrNull(Double expected) { | ||
21 | return expected == null ? nullValue(Double.class) : closeTo(expected, TOLERANCE); | ||
22 | } | ||
23 | |||
24 | @ParameterizedTest(name = "+{0} == {1}") | ||
25 | @CsvSource(value = { | ||
26 | "2.5, 2.5", | ||
27 | "null, null" | ||
28 | }, nullValues = "null") | ||
29 | void plusTest(Double a, Double result) { | ||
30 | var term = RealTerms.plus(RealTerms.constant(a)); | ||
31 | assertThat(term.getType(), is(Double.class)); | ||
32 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
33 | } | ||
34 | |||
35 | @ParameterizedTest(name = "-{0} == {1}") | ||
36 | @CsvSource(value = { | ||
37 | "2.5, -2.5", | ||
38 | "null, null" | ||
39 | }, nullValues = "null") | ||
40 | void minusTest(Double a, Double result) { | ||
41 | var term = RealTerms.minus(RealTerms.constant(a)); | ||
42 | assertThat(term.getType(), is(Double.class)); | ||
43 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
44 | } | ||
45 | |||
46 | @ParameterizedTest(name = "{0} + {1} == {2}") | ||
47 | @CsvSource(value = { | ||
48 | "1.2, 2.3, 3.5", | ||
49 | "null, 2.3, null", | ||
50 | "1.2, null, null", | ||
51 | "null, null, null" | ||
52 | }, nullValues = "null") | ||
53 | void addTest(Double a, Double b, Double result) { | ||
54 | var term = RealTerms.add(RealTerms.constant(a), RealTerms.constant(b)); | ||
55 | assertThat(term.getType(), is(Double.class)); | ||
56 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
57 | } | ||
58 | |||
59 | @ParameterizedTest(name = "{0} - {1} == {2}") | ||
60 | @CsvSource(value = { | ||
61 | "1.2, 3.4, -2.2", | ||
62 | "null, 3.4, null", | ||
63 | "1.2, null, null", | ||
64 | "null, null, null" | ||
65 | }, nullValues = "null") | ||
66 | void subTest(Double a, Double b, Double result) { | ||
67 | var term = RealTerms.sub(RealTerms.constant(a), RealTerms.constant(b)); | ||
68 | assertThat(term.getType(), is(Double.class)); | ||
69 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
70 | } | ||
71 | |||
72 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
73 | @CsvSource(value = { | ||
74 | "2.3, 3.4, 7.82", | ||
75 | "null, 3.4, null", | ||
76 | "2.3, null, null", | ||
77 | "null, null, null" | ||
78 | }, nullValues = "null") | ||
79 | void mulTest(Double a, Double b, Double result) { | ||
80 | var term = RealTerms.mul(RealTerms.constant(a), RealTerms.constant(b)); | ||
81 | assertThat(term.getType(), is(Double.class)); | ||
82 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
83 | } | ||
84 | |||
85 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
86 | @CsvSource(value = { | ||
87 | "7.82, 3.4, 2.3", | ||
88 | "null, 3.4, null", | ||
89 | "7.82, null, null", | ||
90 | "null, null, null" | ||
91 | }, nullValues = "null") | ||
92 | void divTest(Double a, Double b, Double result) { | ||
93 | var term = RealTerms.div(RealTerms.constant(a), RealTerms.constant(b)); | ||
94 | assertThat(term.getType(), is(Double.class)); | ||
95 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
96 | } | ||
97 | |||
98 | @ParameterizedTest(name = "{0} ** {1} == {2}") | ||
99 | @CsvSource(value = { | ||
100 | "2.0, 6.0, 64.0", | ||
101 | "null, 6.0, null", | ||
102 | "2.0, null, null", | ||
103 | "null, null, null" | ||
104 | }, nullValues = "null") | ||
105 | void powTest(Double a, Double b, Double result) { | ||
106 | var term = RealTerms.pow(RealTerms.constant(a), RealTerms.constant(b)); | ||
107 | assertThat(term.getType(), is(Double.class)); | ||
108 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
109 | } | ||
110 | |||
111 | @ParameterizedTest(name = "min({0}, {1}) == {2}") | ||
112 | @CsvSource(value = { | ||
113 | "1.5, 2.7, 1.5", | ||
114 | "2.7, 1.5, 1.5", | ||
115 | "null, 2.7, null", | ||
116 | "1.5, null, null", | ||
117 | "null, null, null" | ||
118 | }, nullValues = "null") | ||
119 | void minTest(Double a, Double b, Double result) { | ||
120 | var term = RealTerms.min(RealTerms.constant(a), RealTerms.constant(b)); | ||
121 | assertThat(term.getType(), is(Double.class)); | ||
122 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
123 | } | ||
124 | |||
125 | @ParameterizedTest(name = "max({0}, {1}) == {2}") | ||
126 | @CsvSource(value = { | ||
127 | "1.5, 2.7, 2.7", | ||
128 | "2.7, 1.7, 2.7", | ||
129 | "null, 2.7, null", | ||
130 | "1.5, null, null", | ||
131 | "null, null, null" | ||
132 | }, nullValues = "null") | ||
133 | void maxTest(Double a, Double b, Double result) { | ||
134 | var term = RealTerms.max(RealTerms.constant(a), RealTerms.constant(b)); | ||
135 | assertThat(term.getType(), is(Double.class)); | ||
136 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
137 | } | ||
138 | |||
139 | @ParameterizedTest(name = "({0} == {1}) == {2}") | ||
140 | @CsvSource(value = { | ||
141 | "1.5, 1.5, true", | ||
142 | "1.5, 2.7, false", | ||
143 | "null, 1.5, null", | ||
144 | "1.5, null, null", | ||
145 | "null, null, null" | ||
146 | }, nullValues = "null") | ||
147 | void eqTest(Double a, Double b, Boolean result) { | ||
148 | var term = RealTerms.eq(RealTerms.constant(a), RealTerms.constant(b)); | ||
149 | assertThat(term.getType(), is(Boolean.class)); | ||
150 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
151 | } | ||
152 | |||
153 | @ParameterizedTest(name = "({0} != {1}) == {2}") | ||
154 | @CsvSource(value = { | ||
155 | "1.5, 1.5, false", | ||
156 | "1.5, 2.7, true", | ||
157 | "null, 1.5, null", | ||
158 | "1.5, null, null", | ||
159 | "null, null, null" | ||
160 | }, nullValues = "null") | ||
161 | void notEqTest(Double a, Double b, Boolean result) { | ||
162 | var term = RealTerms.notEq(RealTerms.constant(a), RealTerms.constant(b)); | ||
163 | assertThat(term.getType(), is(Boolean.class)); | ||
164 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
165 | } | ||
166 | |||
167 | @ParameterizedTest(name = "({0} < {1}) == {2}") | ||
168 | @CsvSource(value = { | ||
169 | "1.5, -2.7, false", | ||
170 | "1.5, 1.5, false", | ||
171 | "1.5, 2.7, true", | ||
172 | "null, 1.5, null", | ||
173 | "1.5, null, null", | ||
174 | "null, null, null" | ||
175 | }, nullValues = "null") | ||
176 | void lessTest(Double a, Double b, Boolean result) { | ||
177 | var term = RealTerms.less(RealTerms.constant(a), RealTerms.constant(b)); | ||
178 | assertThat(term.getType(), is(Boolean.class)); | ||
179 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
180 | } | ||
181 | |||
182 | @ParameterizedTest(name = "({0} <= {1}) == {2}") | ||
183 | @CsvSource(value = { | ||
184 | "1.5, -2.7, false", | ||
185 | "1.5, 1.5, true", | ||
186 | "1.5, 2.7, true", | ||
187 | "null, 1.5, null", | ||
188 | "1.5, null, null", | ||
189 | "null, null, null" | ||
190 | }, nullValues = "null") | ||
191 | void lessEqTest(Double a, Double b, Boolean result) { | ||
192 | var term = RealTerms.lessEq(RealTerms.constant(a), RealTerms.constant(b)); | ||
193 | assertThat(term.getType(), is(Boolean.class)); | ||
194 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
195 | } | ||
196 | |||
197 | @ParameterizedTest(name = "({0} > {1}) == {2}") | ||
198 | @CsvSource(value = { | ||
199 | "1.5, -2.7, true", | ||
200 | "1.5, 1.5, false", | ||
201 | "1.5, 2.7, false", | ||
202 | "null, 1.5, null", | ||
203 | "1.5, null, null", | ||
204 | "null, null, null" | ||
205 | }, nullValues = "null") | ||
206 | void greaterTest(Double a, Double b, Boolean result) { | ||
207 | var term = RealTerms.greater(RealTerms.constant(a), RealTerms.constant(b)); | ||
208 | assertThat(term.getType(), is(Boolean.class)); | ||
209 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
210 | } | ||
211 | |||
212 | @ParameterizedTest(name = "({0} >= {1}) == {2}") | ||
213 | @CsvSource(value = { | ||
214 | "1.5, -2.7, true", | ||
215 | "1.5, 1.5, true", | ||
216 | "1.5, 2.7, false", | ||
217 | "null, 1.5, null", | ||
218 | "1.5, null, null", | ||
219 | "null, null, null" | ||
220 | }, nullValues = "null") | ||
221 | void greaterEqTest(Double a, Double b, Boolean result) { | ||
222 | var term = RealTerms.greaterEq(RealTerms.constant(a), RealTerms.constant(b)); | ||
223 | assertThat(term.getType(), is(Boolean.class)); | ||
224 | assertThat(term.evaluate(Valuation.empty()), is(result)); | ||
225 | } | ||
226 | |||
227 | @ParameterizedTest(name = "{0} as real == {1}") | ||
228 | @CsvSource(value = { | ||
229 | "0, 0.0", | ||
230 | "5, 5.0", | ||
231 | "null, null" | ||
232 | }, nullValues = "null") | ||
233 | void asRealTest(Integer a, Double result) { | ||
234 | var term = RealTerms.asReal(IntTerms.constant(a)); | ||
235 | assertThat(term.getType(), is(Double.class)); | ||
236 | assertThat(term.evaluate(Valuation.empty()), closeToOrNull(result)); | ||
237 | } | ||
238 | } | ||
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java index c529117e..31baf36e 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java | |||
@@ -1,4 +1,9 @@ | |||
1 | package tools.refinery.store.query.viatra.internal.cardinality; | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
2 | 7 | ||
3 | import org.junit.jupiter.params.ParameterizedTest; | 8 | import org.junit.jupiter.params.ParameterizedTest; |
4 | import org.junit.jupiter.params.provider.Arguments; | 9 | import org.junit.jupiter.params.provider.Arguments; |
@@ -10,14 +15,14 @@ import java.util.List; | |||
10 | import java.util.stream.Stream; | 15 | import java.util.stream.Stream; |
11 | 16 | ||
12 | import static org.hamcrest.MatcherAssert.assertThat; | 17 | import static org.hamcrest.MatcherAssert.assertThat; |
13 | import static org.hamcrest.Matchers.equalTo; | 18 | import static org.hamcrest.Matchers.is; |
14 | 19 | ||
15 | class UpperCardinalitySumAggregationOperatorStreamTest { | 20 | class UpperCardinalitySumAggregatorStreamTest { |
16 | @ParameterizedTest | 21 | @ParameterizedTest |
17 | @MethodSource | 22 | @MethodSource |
18 | void testStream(List<UpperCardinality> list, UpperCardinality expected) { | 23 | void testStream(List<UpperCardinality> list, UpperCardinality expected) { |
19 | var result = UpperCardinalitySumAggregationOperator.INSTANCE.aggregateStream(list.stream()); | 24 | var result = UpperCardinalitySumAggregator.INSTANCE.aggregateStream(list.stream()); |
20 | assertThat(result, equalTo(expected)); | 25 | assertThat(result, is(expected)); |
21 | } | 26 | } |
22 | 27 | ||
23 | static Stream<Arguments> testStream() { | 28 | static Stream<Arguments> testStream() { |
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java new file mode 100644 index 00000000..780cd0ab --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java | |||
@@ -0,0 +1,80 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.query.term.StatefulAggregate; | ||
11 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
13 | |||
14 | import static org.hamcrest.MatcherAssert.assertThat; | ||
15 | import static org.hamcrest.Matchers.is; | ||
16 | |||
17 | class UpperCardinalitySumAggregatorTest { | ||
18 | private StatefulAggregate<UpperCardinality, UpperCardinality> accumulator; | ||
19 | |||
20 | @BeforeEach | ||
21 | void beforeEach() { | ||
22 | accumulator = UpperCardinalitySumAggregator.INSTANCE.createEmptyAggregate(); | ||
23 | } | ||
24 | |||
25 | @Test | ||
26 | void emptyAggregationTest() { | ||
27 | assertThat(accumulator.getResult(), is(UpperCardinality.of(0))); | ||
28 | } | ||
29 | |||
30 | @Test | ||
31 | void singleBoundedTest() { | ||
32 | accumulator.add(UpperCardinality.of(3)); | ||
33 | assertThat(accumulator.getResult(), is(UpperCardinality.of(3))); | ||
34 | } | ||
35 | |||
36 | @Test | ||
37 | void multipleBoundedTest() { | ||
38 | accumulator.add(UpperCardinality.of(2)); | ||
39 | accumulator.add(UpperCardinality.of(3)); | ||
40 | assertThat(accumulator.getResult(), is(UpperCardinality.of(5))); | ||
41 | } | ||
42 | |||
43 | @Test | ||
44 | void singleUnboundedTest() { | ||
45 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
46 | assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED)); | ||
47 | } | ||
48 | |||
49 | @Test | ||
50 | void multipleUnboundedTest() { | ||
51 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
52 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
53 | assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED)); | ||
54 | } | ||
55 | |||
56 | @Test | ||
57 | void removeBoundedTest() { | ||
58 | accumulator.add(UpperCardinality.of(2)); | ||
59 | accumulator.add(UpperCardinality.of(3)); | ||
60 | accumulator.remove(UpperCardinality.of(2)); | ||
61 | assertThat(accumulator.getResult(), is(UpperCardinality.of(3))); | ||
62 | } | ||
63 | |||
64 | @Test | ||
65 | void removeAllUnboundedTest() { | ||
66 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
67 | accumulator.add(UpperCardinality.of(3)); | ||
68 | accumulator.remove(UpperCardinalities.UNBOUNDED); | ||
69 | assertThat(accumulator.getResult(), is(UpperCardinality.of(3))); | ||
70 | } | ||
71 | |||
72 | @Test | ||
73 | void removeSomeUnboundedTest() { | ||
74 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
75 | accumulator.add(UpperCardinalities.UNBOUNDED); | ||
76 | accumulator.add(UpperCardinality.of(3)); | ||
77 | accumulator.remove(UpperCardinalities.UNBOUNDED); | ||
78 | assertThat(accumulator.getResult(), is(UpperCardinalities.UNBOUNDED)); | ||
79 | } | ||
80 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java new file mode 100644 index 00000000..9d0f3bde --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java | |||
@@ -0,0 +1,104 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.term.uppercardinality; | ||
7 | |||
8 | import org.junit.jupiter.params.ParameterizedTest; | ||
9 | import org.junit.jupiter.params.provider.Arguments; | ||
10 | import org.junit.jupiter.params.provider.MethodSource; | ||
11 | import tools.refinery.store.query.valuation.Valuation; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
13 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
14 | |||
15 | import java.util.stream.Stream; | ||
16 | |||
17 | import static org.hamcrest.MatcherAssert.assertThat; | ||
18 | import static org.hamcrest.Matchers.is; | ||
19 | |||
20 | class UpperCardinalityTermsEvaluateTest { | ||
21 | @ParameterizedTest(name = "min({0}, {1}) == {2}") | ||
22 | @MethodSource | ||
23 | void minTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
24 | var term = UpperCardinalityTerms.min(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
25 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
26 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
27 | } | ||
28 | |||
29 | static Stream<Arguments> minTest() { | ||
30 | return Stream.of( | ||
31 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), | ||
32 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(0)), | ||
33 | Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(0)), | ||
34 | Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinality.of(0)), | ||
35 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinality.of(0)), | ||
36 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
37 | Arguments.of(UpperCardinality.of(1), null, null), | ||
38 | Arguments.of(null, UpperCardinality.of(1), null), | ||
39 | Arguments.of(null, null, null) | ||
40 | ); | ||
41 | } | ||
42 | |||
43 | @ParameterizedTest(name = "max({0}, {1}) == {2}") | ||
44 | @MethodSource | ||
45 | void maxTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
46 | var term = UpperCardinalityTerms.max(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
47 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
48 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
49 | } | ||
50 | |||
51 | static Stream<Arguments> maxTest() { | ||
52 | return Stream.of( | ||
53 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(0), UpperCardinality.of(0)), | ||
54 | Arguments.of(UpperCardinality.of(0), UpperCardinality.of(1), UpperCardinality.of(1)), | ||
55 | Arguments.of(UpperCardinality.of(1), UpperCardinality.of(0), UpperCardinality.of(1)), | ||
56 | Arguments.of(UpperCardinality.of(0), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
57 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(0), UpperCardinalities.UNBOUNDED), | ||
58 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
59 | Arguments.of(UpperCardinality.of(1), null, null), | ||
60 | Arguments.of(null, UpperCardinality.of(1), null), | ||
61 | Arguments.of(null, null, null) | ||
62 | ); | ||
63 | } | ||
64 | |||
65 | @ParameterizedTest(name = "{0} + {1} == {2}") | ||
66 | @MethodSource | ||
67 | void addTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
68 | var term = UpperCardinalityTerms.add(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
69 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
70 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
71 | } | ||
72 | |||
73 | static Stream<Arguments> addTest() { | ||
74 | return Stream.of( | ||
75 | Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(5)), | ||
76 | Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
77 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), | ||
78 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
79 | Arguments.of(UpperCardinality.of(1), null, null), | ||
80 | Arguments.of(null, UpperCardinality.of(1), null), | ||
81 | Arguments.of(null, null, null) | ||
82 | ); | ||
83 | } | ||
84 | |||
85 | @ParameterizedTest(name = "{0} * {1} == {2}") | ||
86 | @MethodSource | ||
87 | void mulTest(UpperCardinality a, UpperCardinality b, UpperCardinality expected) { | ||
88 | var term = UpperCardinalityTerms.mul(UpperCardinalityTerms.constant(a), UpperCardinalityTerms.constant(b)); | ||
89 | assertThat(term.getType(), is(UpperCardinality.class)); | ||
90 | assertThat(term.evaluate(Valuation.empty()), is(expected)); | ||
91 | } | ||
92 | |||
93 | static Stream<Arguments> mulTest() { | ||
94 | return Stream.of( | ||
95 | Arguments.of(UpperCardinality.of(2), UpperCardinality.of(3), UpperCardinality.of(6)), | ||
96 | Arguments.of(UpperCardinality.of(2), UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
97 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinality.of(2), UpperCardinalities.UNBOUNDED), | ||
98 | Arguments.of(UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED, UpperCardinalities.UNBOUNDED), | ||
99 | Arguments.of(UpperCardinality.of(1), null, null), | ||
100 | Arguments.of(null, UpperCardinality.of(1), null), | ||
101 | Arguments.of(null, null, null) | ||
102 | ); | ||
103 | } | ||
104 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java new file mode 100644 index 00000000..d447e99c --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java | |||
@@ -0,0 +1,159 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.query.view.KeyOnlyView; | ||
16 | import tools.refinery.store.representation.Symbol; | ||
17 | |||
18 | import java.util.List; | ||
19 | |||
20 | import static org.hamcrest.CoreMatchers.containsString; | ||
21 | import static org.hamcrest.MatcherAssert.assertThat; | ||
22 | import static org.hamcrest.Matchers.allOf; | ||
23 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
24 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
25 | |||
26 | class StructurallyEqualToRawTest { | ||
27 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
28 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
29 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
30 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
31 | private static final NodeVariable p = Variable.of("p"); | ||
32 | private static final NodeVariable q = Variable.of("q"); | ||
33 | |||
34 | @Test | ||
35 | void flatEqualsTest() { | ||
36 | var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); | ||
37 | |||
38 | assertThat(actual, structurallyEqualTo( | ||
39 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
40 | List.of(List.of(personView.call(q))) | ||
41 | )); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void flatNotEqualsTest() { | ||
46 | var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); | ||
47 | |||
48 | var assertion = structurallyEqualTo( | ||
49 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
50 | List.of(List.of(friendView.call(q, q))) | ||
51 | ); | ||
52 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
53 | } | ||
54 | |||
55 | @Test | ||
56 | void deepEqualsTest() { | ||
57 | var actual = Dnf.builder("Actual").parameters(q).clause( | ||
58 | Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) | ||
59 | ).build(); | ||
60 | |||
61 | assertThat(actual, structurallyEqualTo( | ||
62 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
63 | List.of( | ||
64 | List.of( | ||
65 | Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) | ||
66 | ) | ||
67 | ) | ||
68 | )); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void deepNotEqualsTest() { | ||
73 | var actual = Dnf.builder("Actual").parameter(q).clause( | ||
74 | Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q) | ||
75 | ).build(); | ||
76 | |||
77 | var assertion = structurallyEqualTo( | ||
78 | List.of(new SymbolicParameter(q, ParameterDirection.OUT)), | ||
79 | List.of( | ||
80 | List.of( | ||
81 | Dnf.builder("Expected2") | ||
82 | .parameters(p) | ||
83 | .clause(friendView.call(p, p)) | ||
84 | .build() | ||
85 | .call(q) | ||
86 | ) | ||
87 | ) | ||
88 | ); | ||
89 | var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
90 | assertThat(error.getMessage(), allOf(containsString("Expected2"), containsString("Actual2"))); | ||
91 | } | ||
92 | |||
93 | @Test | ||
94 | void parameterListLengthMismatchTest() { | ||
95 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
96 | friendView.call(p, q) | ||
97 | ).build(); | ||
98 | |||
99 | var assertion = structurallyEqualTo( | ||
100 | List.of(new SymbolicParameter(p, ParameterDirection.OUT)), | ||
101 | List.of(List.of(friendView.call(p, p))) | ||
102 | ); | ||
103 | |||
104 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
105 | } | ||
106 | |||
107 | @Test | ||
108 | void parameterDirectionMismatchTest() { | ||
109 | var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause( | ||
110 | personView.call(p) | ||
111 | ).build(); | ||
112 | |||
113 | var assertion = structurallyEqualTo( | ||
114 | List.of(new SymbolicParameter(p, ParameterDirection.OUT)), | ||
115 | List.of(List.of(personView.call(p))) | ||
116 | ); | ||
117 | |||
118 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
119 | } | ||
120 | |||
121 | @Test | ||
122 | void clauseCountMismatchTest() { | ||
123 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
124 | friendView.call(p, q) | ||
125 | ).build(); | ||
126 | |||
127 | var assertion = structurallyEqualTo( | ||
128 | List.of( | ||
129 | new SymbolicParameter(p, ParameterDirection.OUT), | ||
130 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
131 | ), | ||
132 | List.of( | ||
133 | List.of(friendView.call(p, q)), | ||
134 | List.of(friendView.call(q, p)) | ||
135 | ) | ||
136 | ); | ||
137 | |||
138 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
139 | } | ||
140 | |||
141 | @Test | ||
142 | void literalCountMismatchTest() { | ||
143 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
144 | friendView.call(p, q) | ||
145 | ).build(); | ||
146 | |||
147 | var assertion = structurallyEqualTo( | ||
148 | List.of( | ||
149 | new SymbolicParameter(p, ParameterDirection.OUT), | ||
150 | new SymbolicParameter(q, ParameterDirection.OUT) | ||
151 | ), | ||
152 | List.of( | ||
153 | List.of(friendView.call(p, q), friendView.call(q, p)) | ||
154 | ) | ||
155 | ); | ||
156 | |||
157 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
158 | } | ||
159 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java new file mode 100644 index 00000000..f716b805 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java | |||
@@ -0,0 +1,127 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.term.NodeVariable; | ||
11 | import tools.refinery.store.query.term.ParameterDirection; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
14 | import tools.refinery.store.query.view.KeyOnlyView; | ||
15 | import tools.refinery.store.representation.Symbol; | ||
16 | |||
17 | import static org.hamcrest.CoreMatchers.containsString; | ||
18 | import static org.hamcrest.MatcherAssert.assertThat; | ||
19 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
20 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
21 | |||
22 | class StructurallyEqualToTest { | ||
23 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
24 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
25 | private static final AnySymbolView personView = new KeyOnlyView<>(person); | ||
26 | private static final AnySymbolView friendView = new KeyOnlyView<>(friend); | ||
27 | private static final NodeVariable p = Variable.of("p"); | ||
28 | private static final NodeVariable q = Variable.of("q"); | ||
29 | |||
30 | @Test | ||
31 | void flatEqualsTest() { | ||
32 | var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build(); | ||
33 | var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build(); | ||
34 | |||
35 | assertThat(actual, structurallyEqualTo(expected)); | ||
36 | } | ||
37 | |||
38 | @Test | ||
39 | void flatNotEqualsTest() { | ||
40 | var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build(); | ||
41 | var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build(); | ||
42 | |||
43 | var assertion = structurallyEqualTo(expected); | ||
44 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
45 | } | ||
46 | |||
47 | @Test | ||
48 | void deepEqualsTest() { | ||
49 | var expected = Dnf.builder("Expected").parameters(q).clause( | ||
50 | Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q) | ||
51 | ).build(); | ||
52 | var actual = Dnf.builder("Actual").parameters(q).clause( | ||
53 | Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q) | ||
54 | ).build(); | ||
55 | |||
56 | assertThat(actual, structurallyEqualTo(expected)); | ||
57 | } | ||
58 | |||
59 | @Test | ||
60 | void deepNotEqualsTest() { | ||
61 | var expected = Dnf.builder("Expected").parameters(q).clause( | ||
62 | Dnf.builder("Expected2").parameters(p).clause(friendView.call(p, p)).build().call(q) | ||
63 | ).build(); | ||
64 | var actual = Dnf.builder("Actual").parameter(q).clause( | ||
65 | Dnf.builder("Actual2").parameters(p).clause(friendView.call(p, q)).build().call(q) | ||
66 | ).build(); | ||
67 | |||
68 | var assertion = structurallyEqualTo(expected); | ||
69 | var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
70 | assertThat(error.getMessage(), containsString(" called from Expected/1 ")); | ||
71 | } | ||
72 | |||
73 | @Test | ||
74 | void parameterListLengthMismatchTest() { | ||
75 | var expected = Dnf.builder("Expected").parameter(p).clause( | ||
76 | friendView.call(p, p) | ||
77 | ).build(); | ||
78 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
79 | friendView.call(p, q) | ||
80 | ).build(); | ||
81 | |||
82 | var assertion = structurallyEqualTo(expected); | ||
83 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
84 | } | ||
85 | |||
86 | @Test | ||
87 | void parameterDirectionMismatchTest() { | ||
88 | var expected = Dnf.builder("Expected").parameter(p, ParameterDirection.OUT).clause( | ||
89 | personView.call(p) | ||
90 | ).build(); | ||
91 | var actual = Dnf.builder("Actual").parameter(p, ParameterDirection.IN).clause( | ||
92 | personView.call(p) | ||
93 | ).build(); | ||
94 | |||
95 | var assertion = structurallyEqualTo(expected); | ||
96 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
97 | } | ||
98 | |||
99 | @Test | ||
100 | void clauseCountMismatchTest() { | ||
101 | var expected = Dnf.builder("Expected") | ||
102 | .parameters(p, q) | ||
103 | .clause(friendView.call(p, q)) | ||
104 | .clause(friendView.call(q, p)) | ||
105 | .build(); | ||
106 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
107 | friendView.call(p, q) | ||
108 | ).build(); | ||
109 | |||
110 | var assertion = structurallyEqualTo(expected); | ||
111 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
112 | } | ||
113 | |||
114 | @Test | ||
115 | void literalCountMismatchTest() { | ||
116 | var expected = Dnf.builder("Expected").parameters(p, q).clause( | ||
117 | friendView.call(p, q), | ||
118 | friendView.call(q, p) | ||
119 | ).build(); | ||
120 | var actual = Dnf.builder("Actual").parameters(p, q).clause( | ||
121 | friendView.call(p, q) | ||
122 | ).build(); | ||
123 | |||
124 | var assertion = structurallyEqualTo(expected); | ||
125 | assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); | ||
126 | } | ||
127 | } | ||
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java new file mode 100644 index 00000000..cbb48603 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java | |||
@@ -0,0 +1,634 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2021 Rodion Efremov | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: MIT OR EPL-2.0 | ||
6 | */ | ||
7 | package tools.refinery.store.query.utils; | ||
8 | |||
9 | import org.junit.jupiter.api.BeforeEach; | ||
10 | import org.junit.jupiter.api.Test; | ||
11 | import org.junit.jupiter.params.ParameterizedTest; | ||
12 | import org.junit.jupiter.params.provider.Arguments; | ||
13 | import org.junit.jupiter.params.provider.MethodSource; | ||
14 | |||
15 | import java.util.*; | ||
16 | import java.util.stream.Stream; | ||
17 | |||
18 | import static org.junit.jupiter.api.Assertions.*; | ||
19 | |||
20 | /** | ||
21 | * Tests for an order statistic tree which is based on AVL-trees. | ||
22 | * <p> | ||
23 | * This class was copied into <i>Refinery</i> from | ||
24 | * <a href="https://github.com/coderodde/OrderStatisticTree/tree/546c343b9f5d868e394a079ff32691c9dbfd83e3">https://github.com/coderodde/OrderStatisticTree</a> | ||
25 | * and is available under the | ||
26 | * <a href="https://github.com/coderodde/OrderStatisticTree/blob/master/LICENSE">MIT License</a>. | ||
27 | * We also migrated the code to Junit 5, cleaned up some linter warnings, and made the tests deterministic by fixing | ||
28 | * the random seeds. | ||
29 | * | ||
30 | * @author Rodion "rodde" Efremov | ||
31 | * @version based on 1.6 (Feb 11, 2016) | ||
32 | */ | ||
33 | class OrderStatisticTreeTest { | ||
34 | private final OrderStatisticTree<Integer> tree = new OrderStatisticTree<>(); | ||
35 | |||
36 | private final TreeSet<Integer> set = new TreeSet<>(); | ||
37 | |||
38 | @BeforeEach | ||
39 | void before() { | ||
40 | tree.clear(); | ||
41 | set.clear(); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void testAdd() { | ||
46 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
47 | |||
48 | for (int i = 10; i < 30; i += 2) { | ||
49 | assertTrue(tree.isHealthy()); | ||
50 | assertEquals(set.contains(i), tree.contains(i)); | ||
51 | assertEquals(set.add(i), tree.add(i)); | ||
52 | assertEquals(set.add(i), tree.add(i)); | ||
53 | assertEquals(set.contains(i), tree.contains(i)); | ||
54 | assertTrue(tree.isHealthy()); | ||
55 | } | ||
56 | |||
57 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
58 | } | ||
59 | |||
60 | @Test | ||
61 | void testAddAll() { | ||
62 | for (int i = 0; i < 10; ++i) { | ||
63 | assertEquals(set.add(i), tree.add(i)); | ||
64 | } | ||
65 | |||
66 | Collection<Integer> coll = Arrays.asList(10, 9, 7, 11, 12); | ||
67 | |||
68 | assertEquals(set.addAll(coll), tree.addAll(coll)); | ||
69 | assertEquals(set.size(), tree.size()); | ||
70 | |||
71 | for (int i = -10; i < 20; ++i) { | ||
72 | assertEquals(set.contains(i), tree.contains(i)); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | @Test | ||
77 | void testClear() { | ||
78 | for (int i = 0; i < 2000; ++i) { | ||
79 | set.add(i); | ||
80 | tree.add(i); | ||
81 | } | ||
82 | |||
83 | assertEquals(set.size(), tree.size()); | ||
84 | set.clear(); | ||
85 | tree.clear(); | ||
86 | // We expect {@code tree.size()} to always be 0, but we also test for it. | ||
87 | //noinspection ConstantValue | ||
88 | assertEquals(0, tree.size()); | ||
89 | } | ||
90 | |||
91 | @Test | ||
92 | void testContains() { | ||
93 | for (int i = 100; i < 200; i += 3) { | ||
94 | assertTrue(tree.isHealthy()); | ||
95 | assertEquals(set.add(i), tree.add(i)); | ||
96 | assertTrue(tree.isHealthy()); | ||
97 | } | ||
98 | |||
99 | assertEquals(set.size(), tree.size()); | ||
100 | |||
101 | for (int i = 0; i < 300; ++i) { | ||
102 | assertEquals(set.contains(i), tree.contains(i)); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | @Test | ||
107 | void testContainsAll() { | ||
108 | for (int i = 0; i < 50; ++i) { | ||
109 | set.add(i); | ||
110 | tree.add(i); | ||
111 | } | ||
112 | |||
113 | Collection<Integer> coll = new HashSet<>(); | ||
114 | |||
115 | for (int i = 10; i < 20; ++i) { | ||
116 | coll.add(i); | ||
117 | } | ||
118 | |||
119 | assertEquals(set.containsAll(coll), tree.containsAll(coll)); | ||
120 | coll.add(100); | ||
121 | assertEquals(set.containsAll(coll), tree.containsAll(coll)); | ||
122 | } | ||
123 | |||
124 | @Test | ||
125 | void testRemove() { | ||
126 | for (int i = 0; i < 200; ++i) { | ||
127 | assertEquals(set.add(i), tree.add(i)); | ||
128 | } | ||
129 | |||
130 | for (int i = 50; i < 150; i += 2) { | ||
131 | assertEquals(set.remove(i), tree.remove(i)); | ||
132 | assertTrue(tree.isHealthy()); | ||
133 | } | ||
134 | |||
135 | for (int i = -100; i < 300; ++i) { | ||
136 | assertEquals(set.contains(i), tree.contains(i)); | ||
137 | } | ||
138 | } | ||
139 | |||
140 | @Test | ||
141 | void testRemoveLast() { | ||
142 | tree.add(1); | ||
143 | tree.remove(1); | ||
144 | assertEquals(0, tree.size()); | ||
145 | } | ||
146 | |||
147 | @Test | ||
148 | void testRemoveAll() { | ||
149 | for (int i = 0; i < 40; ++i) { | ||
150 | set.add(i); | ||
151 | tree.add(i); | ||
152 | } | ||
153 | |||
154 | Collection<Integer> coll = new HashSet<>(); | ||
155 | |||
156 | for (int i = 10; i < 20; ++i) { | ||
157 | coll.add(i); | ||
158 | } | ||
159 | |||
160 | assertEquals(set.removeAll(coll), tree.removeAll(coll)); | ||
161 | |||
162 | for (int i = -10; i < 50; ++i) { | ||
163 | assertEquals(set.contains(i), tree.contains(i)); | ||
164 | } | ||
165 | |||
166 | assertEquals(set.removeAll(coll), tree.removeAll(coll)); | ||
167 | |||
168 | for (int i = -10; i < 50; ++i) { | ||
169 | assertEquals(set.contains(i), tree.contains(i)); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | @Test | ||
174 | void testSize() { | ||
175 | for (int i = 0; i < 200; ++i) { | ||
176 | assertEquals(set.size(), tree.size()); | ||
177 | assertEquals(set.add(i), tree.add(i)); | ||
178 | assertEquals(set.size(), tree.size()); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | @Test | ||
183 | void testIndexOf() { | ||
184 | for (int i = 0; i < 100; ++i) { | ||
185 | assertTrue(tree.add(i * 2)); | ||
186 | } | ||
187 | |||
188 | for (int i = 0; i < 100; ++i) { | ||
189 | assertEquals(i, tree.indexOf(2 * i)); | ||
190 | } | ||
191 | |||
192 | for (int i = 100; i < 150; ++i) { | ||
193 | assertEquals(-1, tree.indexOf(2 * i)); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | @Test | ||
198 | void testEmpty() { | ||
199 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
200 | set.add(0); | ||
201 | tree.add(0); | ||
202 | assertEquals(set.isEmpty(), tree.isEmpty()); | ||
203 | } | ||
204 | |||
205 | @Test | ||
206 | void testEmptyTreeGetThrowsOnNegativeIndex() { | ||
207 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1)); | ||
208 | } | ||
209 | |||
210 | @Test | ||
211 | void testEmptyTreeSelectThrowsOnTooLargeIndex() { | ||
212 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(0)); | ||
213 | } | ||
214 | |||
215 | @Test | ||
216 | void testSelectThrowsOnNegativeIndex() { | ||
217 | for (int i = 0; i < 5; ++i) { | ||
218 | tree.add(i); | ||
219 | } | ||
220 | |||
221 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(-1)); | ||
222 | } | ||
223 | |||
224 | @Test | ||
225 | void testSelectThrowsOnTooLargeIndex() { | ||
226 | for (int i = 0; i < 5; ++i) { | ||
227 | tree.add(i); | ||
228 | } | ||
229 | |||
230 | assertThrows(IndexOutOfBoundsException.class, () -> tree.get(5)); | ||
231 | } | ||
232 | |||
233 | @Test | ||
234 | void testGet() { | ||
235 | for (int i = 0; i < 100; i += 3) { | ||
236 | tree.add(i); | ||
237 | } | ||
238 | |||
239 | for (int i = 0; i < tree.size(); ++i) { | ||
240 | assertEquals(Integer.valueOf(3 * i), tree.get(i)); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | @Test | ||
245 | void findBug() { | ||
246 | tree.add(0); | ||
247 | assertTrue(tree.isHealthy()); | ||
248 | |||
249 | tree.add(-1); | ||
250 | tree.remove(-1); | ||
251 | assertTrue(tree.isHealthy()); | ||
252 | |||
253 | tree.add(1); | ||
254 | tree.remove(1); | ||
255 | assertTrue(tree.isHealthy()); | ||
256 | |||
257 | tree.add(-1); | ||
258 | tree.add(1); | ||
259 | tree.remove(0); | ||
260 | assertTrue(tree.isHealthy()); | ||
261 | |||
262 | tree.clear(); | ||
263 | tree.add(0); | ||
264 | tree.add(-1); | ||
265 | tree.add(10); | ||
266 | tree.add(5); | ||
267 | tree.add(15); | ||
268 | tree.add(11); | ||
269 | tree.add(30); | ||
270 | tree.add(7); | ||
271 | |||
272 | tree.remove(-1); | ||
273 | |||
274 | assertTrue(tree.isHealthy()); | ||
275 | } | ||
276 | |||
277 | @ParameterizedTest(name = "seed = {0}") | ||
278 | @MethodSource("seedSource") | ||
279 | void tryReproduceTheCounterBug(long seed) { | ||
280 | Random random = new Random(seed); | ||
281 | List<Integer> list = new ArrayList<>(); | ||
282 | |||
283 | for (int i = 0; i < 10; ++i) { | ||
284 | int number = random.nextInt(1000); | ||
285 | list.add(number); | ||
286 | tree.add(number); | ||
287 | assertTrue(tree.isHealthy()); | ||
288 | } | ||
289 | |||
290 | for (Integer i : list) { | ||
291 | tree.remove(i); | ||
292 | boolean healthy = tree.isHealthy(); | ||
293 | assertTrue(healthy); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | @Test | ||
298 | void testEmptyIterator() { | ||
299 | var iterator = tree.iterator(); | ||
300 | assertThrows(NoSuchElementException.class, iterator::next); | ||
301 | } | ||
302 | |||
303 | @Test | ||
304 | void testIteratorThrowsOnDoubleRemove() { | ||
305 | for (int i = 10; i < 20; ++i) { | ||
306 | set.add(i); | ||
307 | tree.add(i); | ||
308 | } | ||
309 | |||
310 | Iterator<Integer> iterator1 = set.iterator(); | ||
311 | Iterator<Integer> iterator2 = tree.iterator(); | ||
312 | |||
313 | for (int i = 0; i < 3; ++i) { | ||
314 | assertEquals(iterator1.next(), iterator2.next()); | ||
315 | } | ||
316 | |||
317 | iterator1.remove(); | ||
318 | iterator2.remove(); | ||
319 | |||
320 | assertThrows(IllegalStateException.class, iterator1::remove); | ||
321 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
322 | } | ||
323 | |||
324 | @Test | ||
325 | void testIterator() { | ||
326 | for (int i = 0; i < 5; ++i) { | ||
327 | tree.add(i); | ||
328 | set.add(i); | ||
329 | } | ||
330 | |||
331 | Iterator<Integer> iterator1 = set.iterator(); | ||
332 | Iterator<Integer> iterator2 = tree.iterator(); | ||
333 | |||
334 | for (int i = 0; i < 5; ++i) { | ||
335 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
336 | assertEquals(iterator1.next(), iterator2.next()); | ||
337 | } | ||
338 | |||
339 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
340 | |||
341 | assertThrows(NoSuchElementException.class, iterator1::next); | ||
342 | assertThrows(NoSuchElementException.class, iterator2::next); | ||
343 | } | ||
344 | |||
345 | @Test | ||
346 | void testRemoveBeforeNextThrowsEmpty() { | ||
347 | var setIterator = set.iterator(); | ||
348 | assertThrows(IllegalStateException.class, setIterator::remove); | ||
349 | |||
350 | var treeIterator = tree.iterator(); | ||
351 | assertThrows(IllegalStateException.class, treeIterator::remove); | ||
352 | } | ||
353 | |||
354 | @Test | ||
355 | void testRemoveThrowsWithoutNext() { | ||
356 | for (int i = 0; i < 10; ++i) { | ||
357 | tree.add(i); | ||
358 | set.add(i); | ||
359 | } | ||
360 | |||
361 | Iterator<Integer> iterator1 = set.iterator(); | ||
362 | Iterator<Integer> iterator2 = tree.iterator(); | ||
363 | |||
364 | for (int i = 0; i < 4; ++i) { | ||
365 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
366 | assertEquals(iterator1.next(), iterator2.next()); | ||
367 | } | ||
368 | |||
369 | iterator1.remove(); | ||
370 | iterator2.remove(); | ||
371 | |||
372 | assertThrows(IllegalStateException.class, iterator1::remove); | ||
373 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
374 | } | ||
375 | |||
376 | @Test | ||
377 | void testRetainAll() { | ||
378 | for (int i = 0; i < 100; ++i) { | ||
379 | set.add(i); | ||
380 | tree.add(i); | ||
381 | } | ||
382 | |||
383 | Collection<Integer> coll = Arrays.asList(26, 29, 25); | ||
384 | |||
385 | assertEquals(set.retainAll(coll), tree.retainAll(coll)); | ||
386 | assertEquals(set.size(), tree.size()); | ||
387 | |||
388 | assertTrue(set.containsAll(tree)); | ||
389 | assertTrue(tree.containsAll(set)); | ||
390 | } | ||
391 | |||
392 | @Test | ||
393 | void testIteratorRemove() { | ||
394 | for (int i = 10; i < 16; ++i) { | ||
395 | assertEquals(set.add(i), tree.add(i)); | ||
396 | } | ||
397 | |||
398 | Iterator<Integer> iterator1 = set.iterator(); | ||
399 | Iterator<Integer> iterator2 = tree.iterator(); | ||
400 | |||
401 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
402 | assertEquals(iterator1.next(), iterator2.next()); | ||
403 | |||
404 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
405 | assertEquals(iterator1.next(), iterator2.next()); | ||
406 | |||
407 | iterator1.remove(); // remove 11 | ||
408 | iterator2.remove(); | ||
409 | |||
410 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
411 | assertEquals(iterator1.next(), iterator2.next()); | ||
412 | |||
413 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
414 | assertEquals(iterator1.next(), iterator2.next()); | ||
415 | |||
416 | iterator1.remove(); // remove 13 | ||
417 | iterator2.remove(); | ||
418 | |||
419 | assertEquals(set.size(), tree.size()); | ||
420 | |||
421 | for (int i = 10; i < 16; ++i) { | ||
422 | assertEquals(set.contains(i), tree.contains(i)); | ||
423 | } | ||
424 | } | ||
425 | |||
426 | @ParameterizedTest(name = "seed = {0}") | ||
427 | @MethodSource("seedSource") | ||
428 | void testIteratorBruteForce(long seed) { | ||
429 | for (int i = 0; i < 1000; ++i) { | ||
430 | assertEquals(set.add(i), tree.add(i)); | ||
431 | } | ||
432 | |||
433 | Iterator<Integer> iterator1 = set.iterator(); | ||
434 | Iterator<Integer> iterator2 = tree.iterator(); | ||
435 | |||
436 | Random random = new Random(seed); | ||
437 | |||
438 | while (true) { | ||
439 | if (!iterator1.hasNext()) { | ||
440 | assertFalse(iterator2.hasNext()); | ||
441 | break; | ||
442 | } | ||
443 | |||
444 | boolean toRemove = random.nextBoolean(); | ||
445 | |||
446 | if (toRemove) { | ||
447 | boolean thrown = false; | ||
448 | |||
449 | try { | ||
450 | iterator1.remove(); | ||
451 | } catch (IllegalStateException ex) { | ||
452 | thrown = true; | ||
453 | } | ||
454 | |||
455 | if (thrown) { | ||
456 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
457 | } else { | ||
458 | iterator2.remove(); | ||
459 | } | ||
460 | } else { | ||
461 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
462 | |||
463 | if (iterator1.hasNext()) { | ||
464 | assertEquals(iterator1.next(), iterator2.next()); | ||
465 | } else { | ||
466 | break; | ||
467 | } | ||
468 | } | ||
469 | } | ||
470 | |||
471 | assertEquals(set.size(), tree.size()); | ||
472 | assertTrue(tree.isHealthy()); | ||
473 | assertTrue(set.containsAll(tree)); | ||
474 | assertTrue(tree.containsAll(set)); | ||
475 | } | ||
476 | |||
477 | @Test | ||
478 | void testIteratorConcurrentModification() { | ||
479 | for (int i = 0; i < 100; ++i) { | ||
480 | set.add(i); | ||
481 | tree.add(i); | ||
482 | } | ||
483 | |||
484 | Iterator<Integer> iterator1 = set.iterator(); | ||
485 | Iterator<Integer> iterator2 = tree.iterator(); | ||
486 | |||
487 | set.remove(10); | ||
488 | tree.remove(10); | ||
489 | |||
490 | assertEquals(iterator1.hasNext(), iterator2.hasNext()); | ||
491 | |||
492 | boolean thrown = false; | ||
493 | |||
494 | try { | ||
495 | iterator1.next(); | ||
496 | } catch (ConcurrentModificationException ex) { | ||
497 | thrown = true; | ||
498 | } | ||
499 | |||
500 | if (thrown) { | ||
501 | assertThrows(ConcurrentModificationException.class, iterator2::next); | ||
502 | } else { | ||
503 | iterator2.next(); | ||
504 | } | ||
505 | } | ||
506 | |||
507 | @Test | ||
508 | void testIteratorConcurrentRemove() { | ||
509 | for (int i = 10; i < 20; ++i) { | ||
510 | set.add(i); | ||
511 | tree.add(i); | ||
512 | } | ||
513 | |||
514 | Iterator<Integer> iterator1 = set.iterator(); | ||
515 | Iterator<Integer> iterator2 = tree.iterator(); | ||
516 | |||
517 | for (int i = 0; i < 4; ++i) { | ||
518 | iterator1.next(); | ||
519 | iterator2.next(); | ||
520 | } | ||
521 | |||
522 | // None of them contains 2, should not change the modification count. | ||
523 | set.remove(2); | ||
524 | tree.remove(2); | ||
525 | |||
526 | iterator1.remove(); | ||
527 | iterator2.remove(); | ||
528 | |||
529 | iterator1.next(); | ||
530 | iterator2.next(); | ||
531 | |||
532 | set.remove(12); | ||
533 | tree.remove(12); | ||
534 | |||
535 | // Both of them should throw. | ||
536 | assertThrows(ConcurrentModificationException.class, iterator1::remove); | ||
537 | assertThrows(ConcurrentModificationException.class, iterator2::remove); | ||
538 | } | ||
539 | |||
540 | @Test | ||
541 | void testConcurrentOrIllegalStateOnRemove() { | ||
542 | for (int i = 0; i < 10; ++i) { | ||
543 | set.add(i); | ||
544 | tree.add(i); | ||
545 | } | ||
546 | |||
547 | Iterator<Integer> iterator1 = set.iterator(); | ||
548 | Iterator<Integer> iterator2 = tree.iterator(); | ||
549 | |||
550 | set.add(100); | ||
551 | tree.add(100); | ||
552 | |||
553 | assertThrows(IllegalStateException.class, iterator1::remove); | ||
554 | assertThrows(IllegalStateException.class, iterator2::remove); | ||
555 | } | ||
556 | |||
557 | @Test | ||
558 | void testConcurrentIterators() { | ||
559 | for (int i = 0; i < 10; ++i) { | ||
560 | set.add(i); | ||
561 | tree.add(i); | ||
562 | } | ||
563 | |||
564 | Iterator<Integer> iterator1a = set.iterator(); | ||
565 | Iterator<Integer> iterator1b = set.iterator(); | ||
566 | Iterator<Integer> iterator2a = tree.iterator(); | ||
567 | Iterator<Integer> iterator2b = tree.iterator(); | ||
568 | |||
569 | for (int i = 0; i < 3; ++i) { | ||
570 | iterator1a.next(); | ||
571 | iterator2a.next(); | ||
572 | } | ||
573 | |||
574 | iterator1a.remove(); | ||
575 | iterator2a.remove(); | ||
576 | |||
577 | assertEquals(iterator1b.hasNext(), iterator2b.hasNext()); | ||
578 | |||
579 | assertThrows(ConcurrentModificationException.class, iterator1b::next); | ||
580 | assertThrows(ConcurrentModificationException.class, iterator2b::next); | ||
581 | } | ||
582 | |||
583 | @ParameterizedTest(name = "seed = {0}") | ||
584 | @MethodSource("seedSource") | ||
585 | void testToArray(long seed) { | ||
586 | Random r = new Random(seed); | ||
587 | |||
588 | for (int i = 0; i < 50; ++i) { | ||
589 | int num = r.nextInt(); | ||
590 | set.add(num); | ||
591 | tree.add(num); | ||
592 | } | ||
593 | |||
594 | assertArrayEquals(set.toArray(), tree.toArray()); | ||
595 | } | ||
596 | |||
597 | @Test | ||
598 | void testToArrayGeneric() { | ||
599 | for (int i = 0; i < 100; ++i) { | ||
600 | set.add(i); | ||
601 | tree.add(i); | ||
602 | } | ||
603 | |||
604 | Integer[] array1before = new Integer[99]; | ||
605 | Integer[] array2before = new Integer[99]; | ||
606 | |||
607 | Integer[] array1after = set.toArray(array1before); | ||
608 | Integer[] array2after = tree.toArray(array2before); | ||
609 | |||
610 | assertNotSame(array1before, array1after); | ||
611 | assertNotSame(array2before, array2after); | ||
612 | assertArrayEquals(array1after, array2after); | ||
613 | |||
614 | set.remove(1); | ||
615 | tree.remove(1); | ||
616 | |||
617 | array1after = set.toArray(array1before); | ||
618 | array2after = tree.toArray(array2before); | ||
619 | |||
620 | assertSame(array1before, array1after); | ||
621 | assertSame(array2before, array2after); | ||
622 | assertArrayEquals(array1after, array2after); | ||
623 | } | ||
624 | |||
625 | static Stream<Arguments> seedSource() { | ||
626 | return Stream.of( | ||
627 | Arguments.of(0L), | ||
628 | Arguments.of(1L), | ||
629 | Arguments.of(2L), | ||
630 | Arguments.of(3L), | ||
631 | Arguments.of(4L) | ||
632 | ); | ||
633 | } | ||
634 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java new file mode 100644 index 00000000..6a3301b3 --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Description; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.equality.DeepDnfEqualityChecker; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | |||
14 | import java.util.List; | ||
15 | |||
16 | class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker { | ||
17 | private final Description description; | ||
18 | private boolean raw; | ||
19 | private boolean needsDescription = true; | ||
20 | |||
21 | MismatchDescribingDnfEqualityChecker(Description description) { | ||
22 | this.description = description; | ||
23 | } | ||
24 | |||
25 | public boolean needsDescription() { | ||
26 | return needsDescription; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public boolean dnfEqualRaw(List<SymbolicParameter> symbolicParameters, List<? extends List<? extends Literal>> clauses, Dnf other) { | ||
31 | try { | ||
32 | raw = true; | ||
33 | boolean result = super.dnfEqualRaw(symbolicParameters, clauses, other); | ||
34 | if (!result && needsDescription) { | ||
35 | description.appendText("was ").appendText(other.toDefinitionString()); | ||
36 | } | ||
37 | return false; | ||
38 | } finally { | ||
39 | raw = false; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | protected boolean doCheckEqual(Pair pair) { | ||
45 | boolean result = super.doCheckEqual(pair); | ||
46 | if (!result && needsDescription) { | ||
47 | describeMismatch(pair); | ||
48 | // Only describe the first found (innermost) mismatch. | ||
49 | needsDescription = false; | ||
50 | } | ||
51 | return result; | ||
52 | } | ||
53 | |||
54 | private void describeMismatch(Pair pair) { | ||
55 | var inProgress = getInProgress(); | ||
56 | int size = inProgress.size(); | ||
57 | if (size <= 1 && !raw) { | ||
58 | description.appendText("was ").appendText(pair.right().toDefinitionString()); | ||
59 | return; | ||
60 | } | ||
61 | var last = inProgress.get(size - 1); | ||
62 | description.appendText("expected ").appendText(last.left().toDefinitionString()); | ||
63 | for (int i = size - 2; i >= 0; i--) { | ||
64 | description.appendText(" called from ").appendText(inProgress.get(i).left().toString()); | ||
65 | } | ||
66 | description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString()); | ||
67 | } | ||
68 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java new file mode 100644 index 00000000..cd449a6a --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java | |||
@@ -0,0 +1,46 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Matcher; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | |||
13 | import java.util.List; | ||
14 | |||
15 | public final class QueryMatchers { | ||
16 | private QueryMatchers() { | ||
17 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
18 | } | ||
19 | |||
20 | /** | ||
21 | * Compare two {@link Dnf} instances up to renaming of variables. | ||
22 | * | ||
23 | * @param expected The expected {@link Dnf} instance. | ||
24 | * @return A Hamcrest matcher for equality up to renaming of variables. | ||
25 | */ | ||
26 | public static Matcher<Dnf> structurallyEqualTo(Dnf expected) { | ||
27 | return new StructurallyEqualTo(expected); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Compare a {@link Dnf} instance to another predicate in DNF form without constructing it. | ||
32 | * <p> | ||
33 | * This matcher should be used instead of {@link #structurallyEqualTo(Dnf)} when the validation and | ||
34 | * pre-processing associated with the {@link Dnf} constructor, i.e., validation of parameter directions, | ||
35 | * topological sorting of literals, and the reduction of trivial predicates is not desired. In particular, this | ||
36 | * matcher can be used to test for exact order of literal after pre-processing. | ||
37 | * | ||
38 | * @param expectedSymbolicParameters The expected list of symbolic parameters. | ||
39 | * @param expectedLiterals The expected clauses. Each clause is represented by a list of literals. | ||
40 | * @return A Hamcrest matcher for equality up to renaming of variables. | ||
41 | */ | ||
42 | public static Matcher<Dnf> structurallyEqualTo(List<SymbolicParameter> expectedSymbolicParameters, | ||
43 | List<? extends List<? extends Literal>> expectedLiterals) { | ||
44 | return new StructurallyEqualToRaw(expectedSymbolicParameters, expectedLiterals); | ||
45 | } | ||
46 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java new file mode 100644 index 00000000..86149141 --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java | |||
@@ -0,0 +1,41 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Description; | ||
9 | import org.hamcrest.TypeSafeMatcher; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.equality.DeepDnfEqualityChecker; | ||
12 | |||
13 | public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> { | ||
14 | private final Dnf expected; | ||
15 | |||
16 | public StructurallyEqualTo(Dnf expected) { | ||
17 | this.expected = expected; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | protected boolean matchesSafely(Dnf item) { | ||
22 | var checker = new DeepDnfEqualityChecker(); | ||
23 | return checker.dnfEqual(expected, item); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | protected void describeMismatchSafely(Dnf item, Description mismatchDescription) { | ||
28 | var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription); | ||
29 | if (describingChecker.dnfEqual(expected, item)) { | ||
30 | throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison"); | ||
31 | } | ||
32 | if (describingChecker.needsDescription()) { | ||
33 | super.describeMismatchSafely(item, mismatchDescription); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public void describeTo(Description description) { | ||
39 | description.appendText("structurally equal to ").appendText(expected.toDefinitionString()); | ||
40 | } | ||
41 | } | ||
diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java new file mode 100644 index 00000000..2f8c2944 --- /dev/null +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.query.tests; | ||
7 | |||
8 | import org.hamcrest.Description; | ||
9 | import org.hamcrest.TypeSafeMatcher; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.dnf.SymbolicParameter; | ||
12 | import tools.refinery.store.query.equality.DeepDnfEqualityChecker; | ||
13 | import tools.refinery.store.query.literal.Literal; | ||
14 | |||
15 | import java.util.List; | ||
16 | |||
17 | public class StructurallyEqualToRaw extends TypeSafeMatcher<Dnf> { | ||
18 | private final List<SymbolicParameter> expectedSymbolicParameters; | ||
19 | private final List<? extends List<? extends Literal>> expectedClauses; | ||
20 | |||
21 | public StructurallyEqualToRaw(List<SymbolicParameter> expectedSymbolicParameters, | ||
22 | List<? extends List<? extends Literal>> expectedClauses) { | ||
23 | this.expectedSymbolicParameters = expectedSymbolicParameters; | ||
24 | this.expectedClauses = expectedClauses; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | protected boolean matchesSafely(Dnf item) { | ||
29 | var checker = new DeepDnfEqualityChecker(); | ||
30 | return checker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | protected void describeMismatchSafely(Dnf item, Description mismatchDescription) { | ||
35 | var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription); | ||
36 | if (describingChecker.dnfEqualRaw(expectedSymbolicParameters, expectedClauses, item)) { | ||
37 | throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison"); | ||
38 | } | ||
39 | if (describingChecker.needsDescription()) { | ||
40 | super.describeMismatchSafely(item, mismatchDescription); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public void describeTo(Description description) { | ||
46 | description.appendText("structurally equal to ") | ||
47 | .appendValueList("(", ", ", ")", expectedSymbolicParameters) | ||
48 | .appendText(" <-> ") | ||
49 | .appendValueList("", ", ", ".", expectedClauses); | ||
50 | } | ||
51 | } | ||
diff --git a/subprojects/store-reasoning/build.gradle.kts b/subprojects/store-reasoning/build.gradle.kts new file mode 100644 index 00000000..abad0491 --- /dev/null +++ b/subprojects/store-reasoning/build.gradle.kts | |||
@@ -0,0 +1,13 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-library") | ||
9 | } | ||
10 | |||
11 | dependencies { | ||
12 | api(project(":refinery-store-query")) | ||
13 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java new file mode 100644 index 00000000..000171a1 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
9 | |||
10 | public sealed interface AnyPartialInterpretation permits PartialInterpretation { | ||
11 | ReasoningAdapter getAdapter(); | ||
12 | |||
13 | AnyPartialSymbol getPartialSymbol(); | ||
14 | |||
15 | int countUnfinished(); | ||
16 | |||
17 | int countErrors(); | ||
18 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java new file mode 100644 index 00000000..d3a216d8 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java | |||
@@ -0,0 +1,20 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | public enum MergeResult { | ||
9 | UNCHANGED, | ||
10 | REFINED, | ||
11 | REJECTED; | ||
12 | |||
13 | public MergeResult andAlso(MergeResult other) { | ||
14 | return switch (this) { | ||
15 | case UNCHANGED -> other; | ||
16 | case REFINED -> other == REJECTED ? REJECTED : REFINED; | ||
17 | case REJECTED -> REJECTED; | ||
18 | }; | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java new file mode 100644 index 00000000..4140d640 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java | |||
@@ -0,0 +1,25 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public non-sealed interface PartialInterpretation<A, C> extends AnyPartialInterpretation { | ||
13 | @Override | ||
14 | PartialSymbol<A, C> getPartialSymbol(); | ||
15 | |||
16 | A get(Tuple key); | ||
17 | |||
18 | Cursor<Tuple, A> getAll(); | ||
19 | |||
20 | MergeResult merge(Tuple key, A value); | ||
21 | |||
22 | C getConcrete(Tuple key); | ||
23 | |||
24 | Cursor<Tuple, C> getAllConcrete(); | ||
25 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java new file mode 100644 index 00000000..6d5d6f89 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapter; | ||
9 | import tools.refinery.store.query.resultset.ResultSet; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
12 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
13 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
14 | |||
15 | public interface ReasoningAdapter extends ModelAdapter { | ||
16 | PartialRelation EXISTS = new PartialRelation("exists", 1); | ||
17 | |||
18 | @Override | ||
19 | ReasoningStoreAdapter getStoreAdapter(); | ||
20 | |||
21 | default AnyPartialInterpretation getPartialInterpretation(AnyPartialSymbol partialSymbol) { | ||
22 | // Cast to disambiguate overloads. | ||
23 | var typedPartialSymbol = (PartialSymbol<?, ?>) partialSymbol; | ||
24 | return getPartialInterpretation(typedPartialSymbol); | ||
25 | } | ||
26 | |||
27 | <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol); | ||
28 | |||
29 | ResultSet<Boolean> getLiftedResultSet(Dnf query); | ||
30 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java new file mode 100644 index 00000000..d3a337e8 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.reasoning.literal.Modality; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | ||
12 | |||
13 | import java.util.Collection; | ||
14 | import java.util.List; | ||
15 | |||
16 | @SuppressWarnings("UnusedReturnValue") | ||
17 | public interface ReasoningBuilder extends ModelAdapterBuilder { | ||
18 | default ReasoningBuilder liftedQueries(Dnf... liftedQueries) { | ||
19 | return liftedQueries(List.of(liftedQueries)); | ||
20 | } | ||
21 | |||
22 | default ReasoningBuilder liftedQueries(Collection<Dnf> liftedQueries) { | ||
23 | liftedQueries.forEach(this::liftedQuery); | ||
24 | return this; | ||
25 | } | ||
26 | |||
27 | ReasoningBuilder liftedQuery(Dnf liftedQuery); | ||
28 | |||
29 | Dnf lift(Modality modality, Dnf query); | ||
30 | |||
31 | @Override | ||
32 | ReasoningStoreAdapter build(ModelStore store); | ||
33 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java new file mode 100644 index 00000000..c9795255 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | ||
12 | |||
13 | import java.util.Collection; | ||
14 | |||
15 | public interface ReasoningStoreAdapter extends ModelStoreAdapter { | ||
16 | Collection<AnyPartialSymbol> getPartialSymbols(); | ||
17 | |||
18 | Collection<Dnf> getLiftedQueries(); | ||
19 | |||
20 | @Override | ||
21 | ReasoningAdapter createModelAdapter(Model model); | ||
22 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java new file mode 100644 index 00000000..1bd3ad2e --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java | |||
@@ -0,0 +1,43 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.internal; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
10 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
11 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
12 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | import tools.refinery.store.query.resultset.ResultSet; | ||
14 | |||
15 | public class ReasoningAdapterImpl implements ReasoningAdapter { | ||
16 | private final Model model; | ||
17 | private final ReasoningStoreAdapterImpl storeAdapter; | ||
18 | |||
19 | ReasoningAdapterImpl(Model model, ReasoningStoreAdapterImpl storeAdapter) { | ||
20 | this.model = model; | ||
21 | this.storeAdapter = storeAdapter; | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public Model getModel() { | ||
26 | return model; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public ReasoningStoreAdapterImpl getStoreAdapter() { | ||
31 | return storeAdapter; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) { | ||
36 | return null; | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public ResultSet<Boolean> getLiftedResultSet(Dnf query) { | ||
41 | return null; | ||
42 | } | ||
43 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java new file mode 100644 index 00000000..aa71496c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java | |||
@@ -0,0 +1,31 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.internal; | ||
7 | |||
8 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
12 | import tools.refinery.store.reasoning.literal.Modality; | ||
13 | |||
14 | public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder<ReasoningStoreAdapterImpl> | ||
15 | implements ReasoningBuilder { | ||
16 | @Override | ||
17 | public ReasoningBuilder liftedQuery(Dnf liftedQuery) { | ||
18 | return null; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Dnf lift(Modality modality, Dnf query) { | ||
23 | checkNotConfigured(); | ||
24 | return null; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public ReasoningStoreAdapterImpl doBuild(ModelStore store) { | ||
29 | return null; | ||
30 | } | ||
31 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java new file mode 100644 index 00000000..cdddd8d6 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.internal; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
12 | import tools.refinery.store.query.dnf.Dnf; | ||
13 | |||
14 | import java.util.Collection; | ||
15 | |||
16 | public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter { | ||
17 | private final ModelStore store; | ||
18 | |||
19 | ReasoningStoreAdapterImpl(ModelStore store) { | ||
20 | this.store = store; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public ModelStore getStore() { | ||
25 | return store; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Collection<AnyPartialSymbol> getPartialSymbols() { | ||
30 | return null; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public Collection<Dnf> getLiftedQueries() { | ||
35 | return null; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public ReasoningAdapterImpl createModelAdapter(Model model) { | ||
40 | return new ReasoningAdapterImpl(model, this); | ||
41 | } | ||
42 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java new file mode 100644 index 00000000..ac41d170 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java | |||
@@ -0,0 +1,128 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.lifting; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.DnfBuilder; | ||
11 | import tools.refinery.store.query.dnf.DnfClause; | ||
12 | import tools.refinery.store.query.literal.CallLiteral; | ||
13 | import tools.refinery.store.query.literal.CallPolarity; | ||
14 | import tools.refinery.store.query.literal.Literal; | ||
15 | import tools.refinery.store.query.term.NodeVariable; | ||
16 | import tools.refinery.store.query.term.Variable; | ||
17 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
18 | import tools.refinery.store.reasoning.literal.ModalConstraint; | ||
19 | import tools.refinery.store.reasoning.literal.Modality; | ||
20 | import tools.refinery.store.reasoning.literal.PartialLiterals; | ||
21 | import tools.refinery.store.util.CycleDetectingMapper; | ||
22 | |||
23 | import java.util.ArrayList; | ||
24 | import java.util.LinkedHashSet; | ||
25 | import java.util.List; | ||
26 | |||
27 | public class DnfLifter { | ||
28 | private final CycleDetectingMapper<ModalDnf, Dnf> mapper = new CycleDetectingMapper<>(ModalDnf::toString, | ||
29 | this::doLift); | ||
30 | |||
31 | public Dnf lift(Modality modality, Dnf query) { | ||
32 | return mapper.map(new ModalDnf(modality, query)); | ||
33 | } | ||
34 | |||
35 | private Dnf doLift(ModalDnf modalDnf) { | ||
36 | var modality = modalDnf.modality(); | ||
37 | var dnf = modalDnf.dnf(); | ||
38 | var builder = Dnf.builder(); | ||
39 | builder.symbolicParameters(dnf.getSymbolicParameters()); | ||
40 | boolean changed = false; | ||
41 | for (var clause : dnf.getClauses()) { | ||
42 | if (liftClause(modality, dnf, clause, builder)) { | ||
43 | changed = true; | ||
44 | } | ||
45 | } | ||
46 | if (changed) { | ||
47 | return builder.build(); | ||
48 | } | ||
49 | return dnf; | ||
50 | } | ||
51 | |||
52 | private boolean liftClause(Modality modality, Dnf originalDnf, DnfClause clause, DnfBuilder builder) { | ||
53 | boolean changed = false; | ||
54 | var quantifiedVariables = getQuantifiedDataVariables(originalDnf, clause); | ||
55 | var literals = clause.literals(); | ||
56 | var liftedLiterals = new ArrayList<Literal>(literals.size()); | ||
57 | for (var literal : literals) { | ||
58 | Literal liftedLiteral = liftLiteral(modality, literal); | ||
59 | if (liftedLiteral == null) { | ||
60 | liftedLiteral = literal; | ||
61 | } else { | ||
62 | changed = true; | ||
63 | } | ||
64 | liftedLiterals.add(liftedLiteral); | ||
65 | var variable = isExistsLiteralForVariable(modality, liftedLiteral); | ||
66 | if (variable != null) { | ||
67 | // If we already quantify over the existence of the variable with the expected modality, | ||
68 | // we don't need to insert quantification manually. | ||
69 | quantifiedVariables.remove(variable); | ||
70 | } | ||
71 | } | ||
72 | for (var quantifiedVariable : quantifiedVariables) { | ||
73 | // Quantify over data variables that are not already quantified with the expected modality. | ||
74 | liftedLiterals.add(new CallLiteral(CallPolarity.POSITIVE, | ||
75 | new ModalConstraint(modality, ReasoningAdapter.EXISTS), List.of(quantifiedVariable))); | ||
76 | } | ||
77 | builder.clause(liftedLiterals); | ||
78 | return changed || !quantifiedVariables.isEmpty(); | ||
79 | } | ||
80 | |||
81 | private static LinkedHashSet<Variable> getQuantifiedDataVariables(Dnf originalDnf, DnfClause clause) { | ||
82 | var quantifiedVariables = new LinkedHashSet<>(clause.positiveVariables()); | ||
83 | for (var symbolicParameter : originalDnf.getSymbolicParameters()) { | ||
84 | // The existence of parameters will be checked outside this DNF. | ||
85 | quantifiedVariables.remove(symbolicParameter.getVariable()); | ||
86 | } | ||
87 | quantifiedVariables.removeIf(variable -> !(variable instanceof NodeVariable)); | ||
88 | return quantifiedVariables; | ||
89 | } | ||
90 | |||
91 | @Nullable | ||
92 | private Variable isExistsLiteralForVariable(Modality modality, Literal literal) { | ||
93 | if (literal instanceof CallLiteral callLiteral && | ||
94 | callLiteral.getPolarity() == CallPolarity.POSITIVE && | ||
95 | callLiteral.getTarget() instanceof ModalConstraint modalConstraint && | ||
96 | modalConstraint.modality() == modality && | ||
97 | modalConstraint.constraint().equals(ReasoningAdapter.EXISTS)) { | ||
98 | return callLiteral.getArguments().get(0); | ||
99 | } | ||
100 | return null; | ||
101 | } | ||
102 | |||
103 | @Nullable | ||
104 | private Literal liftLiteral(Modality modality, Literal literal) { | ||
105 | if (!(literal instanceof CallLiteral callLiteral)) { | ||
106 | return null; | ||
107 | } | ||
108 | var target = callLiteral.getTarget(); | ||
109 | if (target instanceof ModalConstraint modalTarget) { | ||
110 | var actualTarget = modalTarget.constraint(); | ||
111 | if (actualTarget instanceof Dnf dnf) { | ||
112 | var targetModality = modalTarget.modality(); | ||
113 | var liftedTarget = lift(targetModality, dnf); | ||
114 | return new CallLiteral(callLiteral.getPolarity(), liftedTarget, callLiteral.getArguments()); | ||
115 | } | ||
116 | // No more lifting to be done, pass any modal call to a partial symbol through. | ||
117 | return null; | ||
118 | } else if (target instanceof Dnf dnf) { | ||
119 | var polarity = callLiteral.getPolarity(); | ||
120 | var liftedTarget = lift(modality.commute(polarity), dnf); | ||
121 | // Use == instead of equals(), because lift will return the same object by reference is there are no | ||
122 | // changes made during lifting. | ||
123 | return liftedTarget == target ? null : new CallLiteral(polarity, liftedTarget, callLiteral.getArguments()); | ||
124 | } else { | ||
125 | return PartialLiterals.addModality(callLiteral, modality); | ||
126 | } | ||
127 | } | ||
128 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java new file mode 100644 index 00000000..16fb8fbf --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.lifting; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.reasoning.literal.Modality; | ||
10 | |||
11 | record ModalDnf(Modality modality, Dnf dnf) { | ||
12 | @Override | ||
13 | public String toString() { | ||
14 | return "%s %s".formatted(modality, dnf.name()); | ||
15 | } | ||
16 | } | ||
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 new file mode 100644 index 00000000..4e5a6099 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | ||
10 | import tools.refinery.store.query.literal.Reduction; | ||
11 | import tools.refinery.store.query.term.Parameter; | ||
12 | |||
13 | import java.util.List; | ||
14 | |||
15 | public record ModalConstraint(Modality modality, Constraint constraint) implements Constraint { | ||
16 | private static final String FORMAT = "%s %s"; | ||
17 | |||
18 | @Override | ||
19 | public String name() { | ||
20 | return FORMAT.formatted(modality, constraint.name()); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public List<Parameter> getParameters() { | ||
25 | return constraint.getParameters(); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Reduction getReduction() { | ||
30 | return constraint.getReduction(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public boolean equals(LiteralEqualityHelper helper, Constraint other) { | ||
35 | if (getClass() != other.getClass()) { | ||
36 | return false; | ||
37 | } | ||
38 | var otherModalConstraint = (ModalConstraint) other; | ||
39 | return modality == otherModalConstraint.modality && constraint.equals(helper, otherModalConstraint.constraint); | ||
40 | } | ||
41 | |||
42 | @Override | ||
43 | public String toReferenceString() { | ||
44 | return FORMAT.formatted(modality, constraint.toReferenceString()); | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public String toString() { | ||
49 | return FORMAT.formatted(modality, constraint); | ||
50 | } | ||
51 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java new file mode 100644 index 00000000..96466d5c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.literal.CallPolarity; | ||
9 | |||
10 | import java.util.Locale; | ||
11 | |||
12 | public enum Modality { | ||
13 | MUST, | ||
14 | MAY, | ||
15 | CURRENT; | ||
16 | |||
17 | public Modality negate() { | ||
18 | return switch(this) { | ||
19 | case MUST -> MAY; | ||
20 | case MAY -> MUST; | ||
21 | case CURRENT -> CURRENT; | ||
22 | }; | ||
23 | } | ||
24 | |||
25 | public Modality commute(CallPolarity polarity) { | ||
26 | if (polarity.isPositive()) { | ||
27 | return this; | ||
28 | } | ||
29 | return this.negate(); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public String toString() { | ||
34 | return name().toLowerCase(Locale.ROOT); | ||
35 | } | ||
36 | } | ||
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 new file mode 100644 index 00000000..0e46a795 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java | |||
@@ -0,0 +1,36 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.literal.CallLiteral; | ||
9 | |||
10 | public final class PartialLiterals { | ||
11 | private PartialLiterals() { | ||
12 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
13 | } | ||
14 | |||
15 | public static CallLiteral may(CallLiteral literal) { | ||
16 | return addModality(literal, Modality.MAY); | ||
17 | } | ||
18 | |||
19 | public static CallLiteral must(CallLiteral literal) { | ||
20 | return addModality(literal, Modality.MUST); | ||
21 | } | ||
22 | |||
23 | public static CallLiteral current(CallLiteral literal) { | ||
24 | return addModality(literal, Modality.CURRENT); | ||
25 | } | ||
26 | |||
27 | public static CallLiteral addModality(CallLiteral literal, Modality modality) { | ||
28 | var target = literal.getTarget(); | ||
29 | if (target instanceof ModalConstraint) { | ||
30 | throw new IllegalArgumentException("Literal %s already has modality".formatted(literal)); | ||
31 | } | ||
32 | var polarity = literal.getPolarity(); | ||
33 | var modalTarget = new ModalConstraint(modality.commute(polarity), target); | ||
34 | return new CallLiteral(polarity, modalTarget, literal.getArguments()); | ||
35 | } | ||
36 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java new file mode 100644 index 00000000..8d2cb5cf --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java | |||
@@ -0,0 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.representation; | ||
7 | |||
8 | public sealed interface AnyPartialFunction extends AnyPartialSymbol permits PartialFunction { | ||
9 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java new file mode 100644 index 00000000..788eef73 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.representation; | ||
7 | |||
8 | import tools.refinery.store.representation.AnyAbstractDomain; | ||
9 | |||
10 | public sealed interface AnyPartialSymbol permits AnyPartialFunction, PartialSymbol { | ||
11 | String name(); | ||
12 | |||
13 | int arity(); | ||
14 | |||
15 | AnyAbstractDomain abstractDomain(); | ||
16 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java new file mode 100644 index 00000000..d58d026f --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.representation; | ||
7 | |||
8 | import tools.refinery.store.representation.AbstractDomain; | ||
9 | |||
10 | public record PartialFunction<A, C>(String name, int arity, AbstractDomain<A, C> abstractDomain) | ||
11 | implements AnyPartialFunction, PartialSymbol<A, C> { | ||
12 | @Override | ||
13 | public A defaultValue() { | ||
14 | return null; | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | public C defaultConcreteValue() { | ||
19 | return null; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public boolean equals(Object o) { | ||
24 | return this == o; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public int hashCode() { | ||
29 | // Compare by identity to make hash table lookups more efficient. | ||
30 | return System.identityHashCode(this); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public String toString() { | ||
35 | return "%s/%d".formatted(name, arity); | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java new file mode 100644 index 00000000..6b2f050b --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.representation; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.term.Parameter; | ||
10 | import tools.refinery.store.representation.AbstractDomain; | ||
11 | import tools.refinery.store.representation.TruthValue; | ||
12 | import tools.refinery.store.representation.TruthValueDomain; | ||
13 | |||
14 | import java.util.Arrays; | ||
15 | import java.util.List; | ||
16 | |||
17 | public record PartialRelation(String name, int arity) implements PartialSymbol<TruthValue, Boolean>, Constraint { | ||
18 | @Override | ||
19 | public AbstractDomain<TruthValue, Boolean> abstractDomain() { | ||
20 | return TruthValueDomain.INSTANCE; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public TruthValue defaultValue() { | ||
25 | return TruthValue.FALSE; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Boolean defaultConcreteValue() { | ||
30 | return false; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public List<Parameter> getParameters() { | ||
35 | var parameters = new Parameter[arity]; | ||
36 | Arrays.fill(parameters, Parameter.NODE_OUT); | ||
37 | return List.of(parameters); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public String toReferenceString() { | ||
42 | return name; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public boolean equals(Object o) { | ||
47 | return this == o; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public int hashCode() { | ||
52 | // Compare by identity to make hash table lookups more efficient. | ||
53 | return System.identityHashCode(this); | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public String toString() { | ||
58 | return "%s/%d".formatted(name, arity); | ||
59 | } | ||
60 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java new file mode 100644 index 00000000..3a08bdd8 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.representation; | ||
7 | |||
8 | import tools.refinery.store.representation.AbstractDomain; | ||
9 | |||
10 | public sealed interface PartialSymbol<A, C> extends AnyPartialSymbol permits PartialFunction, PartialRelation { | ||
11 | @Override | ||
12 | AbstractDomain<A, C> abstractDomain(); | ||
13 | |||
14 | A defaultValue(); | ||
15 | |||
16 | C defaultConcreteValue(); | ||
17 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java new file mode 100644 index 00000000..08079f12 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java | |||
@@ -0,0 +1,19 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.seed; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | public interface Seed<T> { | ||
12 | int arity(); | ||
13 | |||
14 | T reducedValue(); | ||
15 | |||
16 | T get(Tuple key); | ||
17 | |||
18 | Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount); | ||
19 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java new file mode 100644 index 00000000..451d1513 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.seed; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | public record UniformSeed<T>(int arity, T reducedValue) implements Seed<T> { | ||
12 | public UniformSeed { | ||
13 | if (arity < 0) { | ||
14 | throw new IllegalArgumentException("Arity must not be negative"); | ||
15 | } | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public T get(Tuple key) { | ||
20 | return reducedValue; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) { | ||
25 | return null; | ||
26 | } | ||
27 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java new file mode 100644 index 00000000..d6a9e02c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java | |||
@@ -0,0 +1,159 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.query.substitution.Substitution; | ||
9 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | |||
14 | import java.util.*; | ||
15 | |||
16 | public final class Advice { | ||
17 | private final AnyPartialSymbol source; | ||
18 | private final PartialRelation target; | ||
19 | private final AdviceSlot slot; | ||
20 | private final boolean mandatory; | ||
21 | private final List<Variable> parameters; | ||
22 | private final List<Literal> literals; | ||
23 | private boolean processed; | ||
24 | |||
25 | public Advice(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot, boolean mandatory, List<Variable> parameters, List<Literal> literals) { | ||
26 | if (mandatory && !slot.isMonotonic()) { | ||
27 | throw new IllegalArgumentException("Only monotonic advice can be mandatory"); | ||
28 | } | ||
29 | this.source = source; | ||
30 | this.target = target; | ||
31 | this.slot = slot; | ||
32 | this.mandatory = mandatory; | ||
33 | checkArity(parameters); | ||
34 | this.parameters = parameters; | ||
35 | this.literals = literals; | ||
36 | } | ||
37 | |||
38 | public AnyPartialSymbol source() { | ||
39 | return source; | ||
40 | } | ||
41 | |||
42 | public PartialRelation target() { | ||
43 | return target; | ||
44 | } | ||
45 | |||
46 | public AdviceSlot slot() { | ||
47 | return slot; | ||
48 | } | ||
49 | |||
50 | public boolean mandatory() { | ||
51 | return mandatory; | ||
52 | } | ||
53 | |||
54 | public List<Variable> parameters() { | ||
55 | return parameters; | ||
56 | } | ||
57 | |||
58 | public List<Literal> literals() { | ||
59 | return literals; | ||
60 | } | ||
61 | |||
62 | public boolean processed() { | ||
63 | return processed; | ||
64 | } | ||
65 | |||
66 | public List<Literal> substitute(List<Variable> substituteParameters) { | ||
67 | checkArity(substituteParameters); | ||
68 | markProcessed(); | ||
69 | // Use a renewing substitution to remove any non-parameter variables and avoid clashed between variables | ||
70 | // coming from different advice in the same clause. | ||
71 | var substitution = Substitution.builder().putManyChecked(parameters, substituteParameters).renewing().build(); | ||
72 | return literals.stream().map(literal -> literal.substitute(substitution)).toList(); | ||
73 | } | ||
74 | |||
75 | private void markProcessed() { | ||
76 | processed = true; | ||
77 | } | ||
78 | |||
79 | public void checkProcessed() { | ||
80 | if (mandatory && !processed) { | ||
81 | throw new IllegalStateException("Mandatory advice %s was not processed".formatted(this)); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | private void checkArity(List<Variable> toCheck) { | ||
86 | if (toCheck.size() != target.arity()) { | ||
87 | throw new IllegalArgumentException("%s needs %d parameters, but got %s".formatted(target.name(), | ||
88 | target.arity(), parameters.size())); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | public static Builder builderFor(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot) { | ||
93 | return new Builder(source, target, slot); | ||
94 | } | ||
95 | |||
96 | |||
97 | @Override | ||
98 | public String toString() { | ||
99 | return "Advice[source=%s, target=%s, slot=%s, mandatory=%s, parameters=%s, literals=%s]".formatted(source, | ||
100 | target, slot, mandatory, parameters, literals); | ||
101 | } | ||
102 | |||
103 | public static class Builder { | ||
104 | private final AnyPartialSymbol source; | ||
105 | private final PartialRelation target; | ||
106 | private final AdviceSlot slot; | ||
107 | private boolean mandatory; | ||
108 | private final List<Variable> parameters = new ArrayList<>(); | ||
109 | private final List<Literal> literals = new ArrayList<>(); | ||
110 | |||
111 | private Builder(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot) { | ||
112 | this.source = source; | ||
113 | this.target = target; | ||
114 | this.slot = slot; | ||
115 | } | ||
116 | |||
117 | public Builder mandatory(boolean mandatory) { | ||
118 | this.mandatory = mandatory; | ||
119 | return this; | ||
120 | } | ||
121 | |||
122 | public Builder mandatory() { | ||
123 | return mandatory(false); | ||
124 | } | ||
125 | |||
126 | public Builder parameters(List<Variable> variables) { | ||
127 | parameters.addAll(variables); | ||
128 | return this; | ||
129 | } | ||
130 | |||
131 | public Builder parameters(Variable... variables) { | ||
132 | return parameters(List.of(variables)); | ||
133 | } | ||
134 | |||
135 | public Builder parameter(Variable variable) { | ||
136 | parameters.add(variable); | ||
137 | return this; | ||
138 | } | ||
139 | |||
140 | public Builder literals(Collection<Literal> literals) { | ||
141 | this.literals.addAll(literals); | ||
142 | return this; | ||
143 | } | ||
144 | |||
145 | public Builder literals(Literal... literals) { | ||
146 | return literals(List.of(literals)); | ||
147 | } | ||
148 | |||
149 | public Builder literal(Literal literal) { | ||
150 | literals.add(literal); | ||
151 | return this; | ||
152 | } | ||
153 | |||
154 | public Advice build() { | ||
155 | return new Advice(source, target, slot, mandatory, Collections.unmodifiableList(parameters), | ||
156 | Collections.unmodifiableList(literals)); | ||
157 | } | ||
158 | } | ||
159 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java new file mode 100644 index 00000000..bab20340 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java | |||
@@ -0,0 +1,30 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.representation.TruthValue; | ||
9 | |||
10 | public enum AdviceSlot { | ||
11 | EXTEND_MUST(true), | ||
12 | |||
13 | RESTRICT_MAY(true), | ||
14 | |||
15 | /** | ||
16 | * Same as {@link #RESTRICT_MAY}, but only active if the value of the relation is not {@link TruthValue#TRUE} or | ||
17 | * {@link TruthValue#ERROR}. | ||
18 | */ | ||
19 | RESTRICT_NEW(false); | ||
20 | |||
21 | private final boolean monotonic; | ||
22 | |||
23 | AdviceSlot(boolean monotonic) { | ||
24 | this.monotonic = monotonic; | ||
25 | } | ||
26 | |||
27 | public boolean isMonotonic() { | ||
28 | return monotonic; | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java new file mode 100644 index 00000000..4a5a8843 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.model.Model; | ||
9 | import tools.refinery.store.query.term.Variable; | ||
10 | import tools.refinery.store.query.literal.CallPolarity; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.literal.Modality; | ||
14 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | |||
17 | import java.util.List; | ||
18 | |||
19 | public interface TranslatedRelation { | ||
20 | PartialRelation getSource(); | ||
21 | |||
22 | void configure(List<Advice> advices); | ||
23 | |||
24 | List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments); | ||
25 | |||
26 | PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model); | ||
27 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java new file mode 100644 index 00000000..6e44a7d7 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java | |||
@@ -0,0 +1,32 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-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.model.Model; | ||
9 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | public abstract class TranslationUnit { | ||
14 | private ReasoningBuilder reasoningBuilder; | ||
15 | |||
16 | protected ReasoningBuilder getReasoningBuilder() { | ||
17 | return reasoningBuilder; | ||
18 | } | ||
19 | |||
20 | public void setPartialInterpretationBuilder(ReasoningBuilder reasoningBuilder) { | ||
21 | this.reasoningBuilder = reasoningBuilder; | ||
22 | configureReasoningBuilder(); | ||
23 | } | ||
24 | |||
25 | protected void configureReasoningBuilder() { | ||
26 | // Nothing to configure by default. | ||
27 | } | ||
28 | |||
29 | public abstract Collection<TranslatedRelation> getTranslatedRelations(); | ||
30 | |||
31 | public abstract void initializeModel(Model model, int nodeCount); | ||
32 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java new file mode 100644 index 00000000..2a151aa2 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java | |||
@@ -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 | package tools.refinery.store.reasoning.translator.base; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
10 | import tools.refinery.store.query.resultset.ResultSet; | ||
11 | import tools.refinery.store.reasoning.MergeResult; | ||
12 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
14 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | public class BaseDecisionInterpretation implements PartialInterpretation<TruthValue, Boolean> { | ||
19 | private final ReasoningAdapter reasoningAdapter; | ||
20 | private PartialRelation partialRelation; | ||
21 | private final ResultSet<Boolean> mustResultSet; | ||
22 | private final ResultSet<Boolean> mayResultSet; | ||
23 | private final ResultSet<Boolean> errorResultSet; | ||
24 | private final ResultSet<Boolean> currentResultSet; | ||
25 | private final Interpretation<TruthValue> interpretation; | ||
26 | |||
27 | public BaseDecisionInterpretation(ReasoningAdapter reasoningAdapter, ResultSet<Boolean> mustResultSet, | ||
28 | ResultSet<Boolean> mayResultSet, ResultSet<Boolean> errorResultSet, | ||
29 | ResultSet<Boolean> currentResultSet, Interpretation<TruthValue> interpretation) { | ||
30 | this.reasoningAdapter = reasoningAdapter; | ||
31 | this.mustResultSet = mustResultSet; | ||
32 | this.mayResultSet = mayResultSet; | ||
33 | this.errorResultSet = errorResultSet; | ||
34 | this.currentResultSet = currentResultSet; | ||
35 | this.interpretation = interpretation; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public ReasoningAdapter getAdapter() { | ||
40 | return reasoningAdapter; | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public int countUnfinished() { | ||
45 | return 0; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public int countErrors() { | ||
50 | return errorResultSet.size(); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public PartialRelation getPartialSymbol() { | ||
55 | return partialRelation; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public TruthValue get(Tuple key) { | ||
60 | return null; | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public Cursor<Tuple, TruthValue> getAll() { | ||
65 | return null; | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public MergeResult merge(Tuple key, TruthValue value) { | ||
70 | TruthValue newValue; | ||
71 | switch (value) { | ||
72 | case UNKNOWN -> { | ||
73 | return MergeResult.UNCHANGED; | ||
74 | } | ||
75 | case TRUE -> newValue = mayResultSet.get(key) ? TruthValue.TRUE : TruthValue.ERROR; | ||
76 | case FALSE -> newValue = mustResultSet.get(key) ? TruthValue.ERROR : TruthValue.FALSE; | ||
77 | case ERROR -> newValue = TruthValue.ERROR; | ||
78 | default -> throw new IllegalArgumentException("Unknown truth value: " + value); | ||
79 | } | ||
80 | var oldValue = interpretation.put(key, newValue); | ||
81 | return oldValue == TruthValue.ERROR ? MergeResult.UNCHANGED : MergeResult.REFINED; | ||
82 | } | ||
83 | |||
84 | @Override | ||
85 | public Boolean getConcrete(Tuple key) { | ||
86 | return currentResultSet.get(key); | ||
87 | } | ||
88 | |||
89 | @Override | ||
90 | public Cursor<Tuple, Boolean> getAllConcrete() { | ||
91 | return null; | ||
92 | } | ||
93 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java new file mode 100644 index 00000000..a1e4b816 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.base; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.reasoning.seed.Seed; | ||
11 | import tools.refinery.store.reasoning.seed.UniformSeed; | ||
12 | import tools.refinery.store.reasoning.translator.TranslatedRelation; | ||
13 | import tools.refinery.store.reasoning.translator.TranslationUnit; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | |||
17 | import java.util.Collection; | ||
18 | import java.util.List; | ||
19 | |||
20 | public class BaseDecisionTranslationUnit extends TranslationUnit { | ||
21 | private final PartialRelation partialRelation; | ||
22 | private final Seed<TruthValue> seed; | ||
23 | private final Symbol<TruthValue> symbol; | ||
24 | |||
25 | public BaseDecisionTranslationUnit(PartialRelation partialRelation, Seed<TruthValue> seed) { | ||
26 | if (seed.arity() != partialRelation.arity()) { | ||
27 | throw new IllegalArgumentException("Expected seed with arity %d for %s, got arity %s" | ||
28 | .formatted(partialRelation.arity(), partialRelation, seed.arity())); | ||
29 | } | ||
30 | this.partialRelation = partialRelation; | ||
31 | this.seed = seed; | ||
32 | symbol = Symbol.of(partialRelation.name(), partialRelation.arity(), TruthValue.class, TruthValue.UNKNOWN); | ||
33 | } | ||
34 | |||
35 | public BaseDecisionTranslationUnit(PartialRelation partialRelation) { | ||
36 | this(partialRelation, new UniformSeed<>(partialRelation.arity(), TruthValue.UNKNOWN)); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public Collection<TranslatedRelation> getTranslatedRelations() { | ||
41 | return List.of(new TranslatedBaseDecision(getReasoningBuilder(), partialRelation, symbol)); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public void initializeModel(Model model, int nodeCount) { | ||
46 | var interpretation = model.getInterpretation(symbol); | ||
47 | interpretation.putAll(seed.getCursor(TruthValue.UNKNOWN, nodeCount)); | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java new file mode 100644 index 00000000..4782eb46 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java | |||
@@ -0,0 +1,54 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.base; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.term.Variable; | ||
10 | import tools.refinery.store.query.literal.CallPolarity; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
14 | import tools.refinery.store.reasoning.literal.Modality; | ||
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
16 | import tools.refinery.store.reasoning.translator.Advice; | ||
17 | import tools.refinery.store.reasoning.translator.TranslatedRelation; | ||
18 | import tools.refinery.store.representation.Symbol; | ||
19 | import tools.refinery.store.representation.TruthValue; | ||
20 | |||
21 | import java.util.List; | ||
22 | |||
23 | class TranslatedBaseDecision implements TranslatedRelation { | ||
24 | private final ReasoningBuilder reasoningBuilder; | ||
25 | private final PartialRelation partialRelation; | ||
26 | private final Symbol<TruthValue> symbol; | ||
27 | |||
28 | public TranslatedBaseDecision(ReasoningBuilder reasoningBuilder, PartialRelation partialRelation, | ||
29 | Symbol<TruthValue> symbol) { | ||
30 | this.reasoningBuilder = reasoningBuilder; | ||
31 | this.partialRelation = partialRelation; | ||
32 | this.symbol = symbol; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public PartialRelation getSource() { | ||
37 | return partialRelation; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public void configure(List<Advice> advices) { | ||
42 | |||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments) { | ||
47 | return null; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model) { | ||
52 | return null; | ||
53 | } | ||
54 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java new file mode 100644 index 00000000..6e4728db --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java | |||
@@ -0,0 +1,11 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | |||
10 | record EliminatedType(PartialRelation replacement) implements TypeAnalysisResult { | ||
11 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java new file mode 100644 index 00000000..7a917dcf --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java | |||
@@ -0,0 +1,106 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | |||
11 | import java.util.HashSet; | ||
12 | import java.util.LinkedHashSet; | ||
13 | import java.util.Objects; | ||
14 | import java.util.Set; | ||
15 | |||
16 | final class ExtendedTypeInfo implements Comparable<ExtendedTypeInfo> { | ||
17 | private final int index; | ||
18 | private final PartialRelation type; | ||
19 | private final TypeInfo typeInfo; | ||
20 | private final Set<PartialRelation> allSubtypes = new LinkedHashSet<>(); | ||
21 | private final Set<PartialRelation> allSupertypes; | ||
22 | private final Set<PartialRelation> concreteSubtypesAndSelf = new LinkedHashSet<>(); | ||
23 | private Set<PartialRelation> directSubtypes; | ||
24 | private final Set<PartialRelation> unsortedDirectSupertypes = new HashSet<>(); | ||
25 | |||
26 | public ExtendedTypeInfo(int index, PartialRelation type, TypeInfo typeInfo) { | ||
27 | this.index = index; | ||
28 | this.type = type; | ||
29 | this.typeInfo = typeInfo; | ||
30 | this.allSupertypes = new LinkedHashSet<>(typeInfo.supertypes()); | ||
31 | } | ||
32 | |||
33 | public PartialRelation getType() { | ||
34 | return type; | ||
35 | } | ||
36 | |||
37 | public TypeInfo getTypeInfo() { | ||
38 | return typeInfo; | ||
39 | } | ||
40 | |||
41 | public boolean isAbstractType() { | ||
42 | return getTypeInfo().abstractType(); | ||
43 | } | ||
44 | |||
45 | public Set<PartialRelation> getAllSubtypes() { | ||
46 | return allSubtypes; | ||
47 | } | ||
48 | |||
49 | public Set<PartialRelation> getAllSupertypes() { | ||
50 | return allSupertypes; | ||
51 | } | ||
52 | |||
53 | public Set<PartialRelation> getAllSupertypesAndSelf() { | ||
54 | var allSubtypesAndSelf = new HashSet<PartialRelation>(allSupertypes.size() + 1); | ||
55 | addMust(allSubtypesAndSelf); | ||
56 | return allSubtypesAndSelf; | ||
57 | } | ||
58 | |||
59 | public Set<PartialRelation> getConcreteSubtypesAndSelf() { | ||
60 | return concreteSubtypesAndSelf; | ||
61 | } | ||
62 | |||
63 | public Set<PartialRelation> getDirectSubtypes() { | ||
64 | return directSubtypes; | ||
65 | } | ||
66 | |||
67 | public Set<PartialRelation> getUnsortedDirectSupertypes() { | ||
68 | return unsortedDirectSupertypes; | ||
69 | } | ||
70 | |||
71 | public void setDirectSubtypes(Set<PartialRelation> directSubtypes) { | ||
72 | this.directSubtypes = directSubtypes; | ||
73 | } | ||
74 | |||
75 | public boolean allowsAllConcreteTypes(Set<PartialRelation> concreteTypes) { | ||
76 | for (var concreteType : concreteTypes) { | ||
77 | if (!concreteSubtypesAndSelf.contains(concreteType)) { | ||
78 | return false; | ||
79 | } | ||
80 | } | ||
81 | return true; | ||
82 | } | ||
83 | |||
84 | public void addMust(Set<PartialRelation> mustTypes) { | ||
85 | mustTypes.add(type); | ||
86 | mustTypes.addAll(allSupertypes); | ||
87 | } | ||
88 | |||
89 | @Override | ||
90 | public int compareTo(@NotNull ExtendedTypeInfo extendedTypeInfo) { | ||
91 | return Integer.compare(index, extendedTypeInfo.index); | ||
92 | } | ||
93 | |||
94 | @Override | ||
95 | public boolean equals(Object o) { | ||
96 | if (this == o) return true; | ||
97 | if (o == null || getClass() != o.getClass()) return false; | ||
98 | ExtendedTypeInfo that = (ExtendedTypeInfo) o; | ||
99 | return index == that.index; | ||
100 | } | ||
101 | |||
102 | @Override | ||
103 | public int hashCode() { | ||
104 | return Objects.hash(index); | ||
105 | } | ||
106 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java new file mode 100644 index 00000000..40de4644 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.query.view.TuplePreservingView; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | class InferredMayTypeView extends TuplePreservingView<InferredType> { | ||
15 | private final PartialRelation type; | ||
16 | |||
17 | InferredMayTypeView(PartialRelation type) { | ||
18 | super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#may".formatted(type)); | ||
19 | this.type = type; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected boolean doFilter(Tuple key, InferredType value) { | ||
24 | return value.mayConcreteTypes().contains(type); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean equals(Object o) { | ||
29 | if (this == o) return true; | ||
30 | if (o == null || getClass() != o.getClass()) return false; | ||
31 | if (!super.equals(o)) return false; | ||
32 | InferredMayTypeView that = (InferredMayTypeView) o; | ||
33 | return Objects.equals(type, that.type); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public int hashCode() { | ||
38 | return Objects.hash(super.hashCode(), type); | ||
39 | } | ||
40 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java new file mode 100644 index 00000000..1a121547 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java | |||
@@ -0,0 +1,40 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.query.view.TuplePreservingView; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | class InferredMustTypeView extends TuplePreservingView<InferredType> { | ||
15 | private final PartialRelation type; | ||
16 | |||
17 | InferredMustTypeView(PartialRelation type) { | ||
18 | super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#must".formatted(type)); | ||
19 | this.type = type; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected boolean doFilter(Tuple key, InferredType value) { | ||
24 | return value.mustTypes().contains(type); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean equals(Object o) { | ||
29 | if (this == o) return true; | ||
30 | if (o == null || getClass() != o.getClass()) return false; | ||
31 | if (!super.equals(o)) return false; | ||
32 | InferredMustTypeView that = (InferredMustTypeView) o; | ||
33 | return Objects.equals(type, that.type); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public int hashCode() { | ||
38 | return Objects.hash(super.hashCode(), type); | ||
39 | } | ||
40 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java new file mode 100644 index 00000000..fd05158b --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | |||
10 | import java.util.Collections; | ||
11 | import java.util.Set; | ||
12 | |||
13 | record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, | ||
14 | PartialRelation currentType) { | ||
15 | public static final InferredType UNTYPED = new InferredType(Set.of(), Set.of(), null); | ||
16 | |||
17 | public InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, | ||
18 | PartialRelation currentType) { | ||
19 | this.mustTypes = Collections.unmodifiableSet(mustTypes); | ||
20 | this.mayConcreteTypes = Collections.unmodifiableSet(mayConcreteTypes); | ||
21 | this.currentType = currentType; | ||
22 | } | ||
23 | |||
24 | public boolean isConsistent() { | ||
25 | return currentType != null || mustTypes.isEmpty(); | ||
26 | } | ||
27 | |||
28 | public boolean isMust(PartialRelation partialRelation) { | ||
29 | return mustTypes.contains(partialRelation); | ||
30 | } | ||
31 | |||
32 | public boolean isMayConcrete(PartialRelation partialRelation) { | ||
33 | return mayConcreteTypes.contains(partialRelation); | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java new file mode 100644 index 00000000..0696f4c3 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java | |||
@@ -0,0 +1,141 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | |||
11 | import java.util.*; | ||
12 | |||
13 | final class PreservedType implements TypeAnalysisResult { | ||
14 | private final ExtendedTypeInfo extendedTypeInfo; | ||
15 | private final List<PartialRelation> directSubtypes; | ||
16 | private final List<ExtendedTypeInfo> allExternalTypeInfoList; | ||
17 | private final InferredType inferredType; | ||
18 | |||
19 | public PreservedType(ExtendedTypeInfo extendedTypeInfo, List<ExtendedTypeInfo> allExternalTypeInfoList) { | ||
20 | this.extendedTypeInfo = extendedTypeInfo; | ||
21 | directSubtypes = List.copyOf(extendedTypeInfo.getDirectSubtypes()); | ||
22 | this.allExternalTypeInfoList = allExternalTypeInfoList; | ||
23 | inferredType = propagateMust(extendedTypeInfo.getAllSupertypesAndSelf(), | ||
24 | extendedTypeInfo.getConcreteSubtypesAndSelf()); | ||
25 | } | ||
26 | |||
27 | public PartialRelation type() { | ||
28 | return extendedTypeInfo.getType(); | ||
29 | } | ||
30 | |||
31 | public List<PartialRelation> getDirectSubtypes() { | ||
32 | return directSubtypes; | ||
33 | } | ||
34 | |||
35 | public boolean isAbstractType() { | ||
36 | return extendedTypeInfo.isAbstractType(); | ||
37 | } | ||
38 | |||
39 | public boolean isVacuous() { | ||
40 | return isAbstractType() && directSubtypes.isEmpty(); | ||
41 | } | ||
42 | |||
43 | public InferredType asInferredType() { | ||
44 | return inferredType; | ||
45 | } | ||
46 | |||
47 | public InferredType merge(InferredType inferredType, TruthValue value) { | ||
48 | return switch (value) { | ||
49 | case UNKNOWN -> inferredType; | ||
50 | case TRUE -> addMust(inferredType); | ||
51 | case FALSE -> removeMay(inferredType); | ||
52 | case ERROR -> addError(inferredType); | ||
53 | }; | ||
54 | } | ||
55 | |||
56 | private InferredType addMust(InferredType inferredType) { | ||
57 | var originalMustTypes = inferredType.mustTypes(); | ||
58 | if (originalMustTypes.contains(type())) { | ||
59 | return inferredType; | ||
60 | } | ||
61 | var mustTypes = new HashSet<>(originalMustTypes); | ||
62 | extendedTypeInfo.addMust(mustTypes); | ||
63 | var originalMayConcreteTypes = inferredType.mayConcreteTypes(); | ||
64 | var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes); | ||
65 | Set<PartialRelation> mayConcreteTypesResult; | ||
66 | if (mayConcreteTypes.retainAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) { | ||
67 | mayConcreteTypesResult = mayConcreteTypes; | ||
68 | } else { | ||
69 | mayConcreteTypesResult = originalMayConcreteTypes; | ||
70 | } | ||
71 | return propagateMust(mustTypes, mayConcreteTypesResult); | ||
72 | } | ||
73 | |||
74 | private InferredType removeMay(InferredType inferredType) { | ||
75 | var originalMayConcreteTypes = inferredType.mayConcreteTypes(); | ||
76 | var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes); | ||
77 | if (!mayConcreteTypes.removeAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) { | ||
78 | return inferredType; | ||
79 | } | ||
80 | return propagateMust(inferredType.mustTypes(), mayConcreteTypes); | ||
81 | } | ||
82 | |||
83 | private InferredType addError(InferredType inferredType) { | ||
84 | var originalMustTypes = inferredType.mustTypes(); | ||
85 | if (originalMustTypes.contains(type())) { | ||
86 | if (inferredType.mayConcreteTypes().isEmpty()) { | ||
87 | return inferredType; | ||
88 | } | ||
89 | return new InferredType(originalMustTypes, Set.of(), null); | ||
90 | } | ||
91 | var mustTypes = new HashSet<>(originalMustTypes); | ||
92 | extendedTypeInfo.addMust(mustTypes); | ||
93 | return new InferredType(mustTypes, Set.of(), null); | ||
94 | } | ||
95 | |||
96 | private InferredType propagateMust(Set<PartialRelation> originalMustTypes, | ||
97 | Set<PartialRelation> mayConcreteTypes) { | ||
98 | // It is possible that there is not type at all, do not force one by propagation. | ||
99 | var maybeUntyped = originalMustTypes.isEmpty(); | ||
100 | // Para-consistent case, do not propagate must types to avoid logical explosion. | ||
101 | var paraConsistentOrSurelyUntyped = mayConcreteTypes.isEmpty(); | ||
102 | if (maybeUntyped || paraConsistentOrSurelyUntyped) { | ||
103 | return new InferredType(originalMustTypes, mayConcreteTypes, null); | ||
104 | } | ||
105 | var currentType = computeCurrentType(mayConcreteTypes); | ||
106 | var mustTypes = new HashSet<>(originalMustTypes); | ||
107 | boolean changed = false; | ||
108 | for (var newMustExtendedTypeInfo : allExternalTypeInfoList) { | ||
109 | var newMustType = newMustExtendedTypeInfo.getType(); | ||
110 | if (mustTypes.contains(newMustType)) { | ||
111 | continue; | ||
112 | } | ||
113 | if (newMustExtendedTypeInfo.allowsAllConcreteTypes(mayConcreteTypes)) { | ||
114 | newMustExtendedTypeInfo.addMust(mustTypes); | ||
115 | changed = true; | ||
116 | } | ||
117 | } | ||
118 | if (!changed) { | ||
119 | return new InferredType(originalMustTypes, mayConcreteTypes, currentType); | ||
120 | } | ||
121 | return new InferredType(mustTypes, mayConcreteTypes, currentType); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Returns a concrete type that is allowed by a (consistent, i.e., nonempty) set of <b>may</b> concrete types. | ||
126 | * | ||
127 | * @param mayConcreteTypes The set of allowed concrete types. Must not be empty. | ||
128 | * @return The first concrete type that is allowed by {@code matConcreteTypes}. | ||
129 | */ | ||
130 | private PartialRelation computeCurrentType(Set<PartialRelation> mayConcreteTypes) { | ||
131 | for (var concreteExtendedTypeInfo : allExternalTypeInfoList) { | ||
132 | var concreteType = concreteExtendedTypeInfo.getType(); | ||
133 | if (!concreteExtendedTypeInfo.isAbstractType() && mayConcreteTypes.contains(concreteType)) { | ||
134 | return concreteType; | ||
135 | } | ||
136 | } | ||
137 | // We have already filtered out the para-consistent case in {@link #propagateMust(Set<PartialRelation>, | ||
138 | // Set<PartialRelation>}. | ||
139 | throw new AssertionError("No concrete type in %s".formatted(mayConcreteTypes)); | ||
140 | } | ||
141 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java new file mode 100644 index 00000000..fbf8a7c9 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java | |||
@@ -0,0 +1,9 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | sealed interface TypeAnalysisResult permits EliminatedType, PreservedType { | ||
9 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java new file mode 100644 index 00000000..e97ce954 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java | |||
@@ -0,0 +1,207 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | |||
10 | import java.util.*; | ||
11 | |||
12 | class TypeAnalyzer { | ||
13 | private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap; | ||
14 | private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>(); | ||
15 | private final InferredType unknownType; | ||
16 | private final Map<PartialRelation, TypeAnalysisResult> analysisResults; | ||
17 | |||
18 | public TypeAnalyzer(Map<PartialRelation, TypeInfo> typeInfoMap) { | ||
19 | int size = typeInfoMap.size(); | ||
20 | extendedTypeInfoMap = new LinkedHashMap<>(size); | ||
21 | var concreteTypes = new LinkedHashSet<PartialRelation>(); | ||
22 | int index = 0; | ||
23 | for (var entry : typeInfoMap.entrySet()) { | ||
24 | var type = entry.getKey(); | ||
25 | var typeInfo = entry.getValue(); | ||
26 | extendedTypeInfoMap.put(type, new ExtendedTypeInfo(index, type, typeInfo)); | ||
27 | if (!typeInfo.abstractType()) { | ||
28 | concreteTypes.add(type); | ||
29 | } | ||
30 | index++; | ||
31 | } | ||
32 | unknownType = new InferredType(Set.of(), concreteTypes, null); | ||
33 | computeAllSupertypes(); | ||
34 | computeAllAndConcreteSubtypes(); | ||
35 | computeDirectSubtypes(); | ||
36 | eliminateTrivialSupertypes(); | ||
37 | analysisResults = computeAnalysisResults(); | ||
38 | } | ||
39 | |||
40 | public InferredType getUnknownType() { | ||
41 | return unknownType; | ||
42 | } | ||
43 | |||
44 | public Map<PartialRelation, TypeAnalysisResult> getAnalysisResults() { | ||
45 | return analysisResults; | ||
46 | } | ||
47 | |||
48 | private void computeAllSupertypes() { | ||
49 | boolean changed; | ||
50 | do { | ||
51 | changed = false; | ||
52 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
53 | var found = new HashSet<PartialRelation>(); | ||
54 | var allSupertypes = extendedTypeInfo.getAllSupertypes(); | ||
55 | for (var supertype : allSupertypes) { | ||
56 | found.addAll(extendedTypeInfoMap.get(supertype).getAllSupertypes()); | ||
57 | } | ||
58 | if (allSupertypes.addAll(found)) { | ||
59 | changed = true; | ||
60 | } | ||
61 | } | ||
62 | } while (changed); | ||
63 | } | ||
64 | |||
65 | private void computeAllAndConcreteSubtypes() { | ||
66 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
67 | var type = extendedTypeInfo.getType(); | ||
68 | if (!extendedTypeInfo.isAbstractType()) { | ||
69 | extendedTypeInfo.getConcreteSubtypesAndSelf().add(type); | ||
70 | } | ||
71 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { | ||
72 | if (type.equals(supertype)) { | ||
73 | throw new IllegalArgumentException("%s cannot be a supertype of itself".formatted(type)); | ||
74 | } | ||
75 | var supertypeInfo = extendedTypeInfoMap.get(supertype); | ||
76 | supertypeInfo.getAllSubtypes().add(type); | ||
77 | if (!extendedTypeInfo.isAbstractType()) { | ||
78 | supertypeInfo.getConcreteSubtypesAndSelf().add(type); | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | |||
84 | private void computeDirectSubtypes() { | ||
85 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
86 | var allSubtypes = extendedTypeInfo.getAllSubtypes(); | ||
87 | var directSubtypes = new LinkedHashSet<>(allSubtypes); | ||
88 | var indirectSubtypes = new LinkedHashSet<PartialRelation>(allSubtypes.size()); | ||
89 | for (var subtype : allSubtypes) { | ||
90 | indirectSubtypes.addAll(extendedTypeInfoMap.get(subtype).getAllSubtypes()); | ||
91 | } | ||
92 | directSubtypes.removeAll(indirectSubtypes); | ||
93 | extendedTypeInfo.setDirectSubtypes(directSubtypes); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | private void eliminateTrivialSupertypes() { | ||
98 | boolean changed; | ||
99 | do { | ||
100 | var toRemove = new ArrayList<PartialRelation>(); | ||
101 | for (var entry : extendedTypeInfoMap.entrySet()) { | ||
102 | var extendedTypeInfo = entry.getValue(); | ||
103 | boolean isAbstract = extendedTypeInfo.isAbstractType(); | ||
104 | // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e., | ||
105 | // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist. | ||
106 | boolean hasSingleDirectSubtype = extendedTypeInfo.getDirectSubtypes().size() == 1; | ||
107 | if (isAbstract && hasSingleDirectSubtype) { | ||
108 | toRemove.add(entry.getKey()); | ||
109 | } | ||
110 | } | ||
111 | toRemove.forEach(this::removeTrivialType); | ||
112 | changed = !toRemove.isEmpty(); | ||
113 | } while (changed); | ||
114 | } | ||
115 | |||
116 | private void removeTrivialType(PartialRelation trivialType) { | ||
117 | var extendedTypeInfo = extendedTypeInfoMap.get(trivialType); | ||
118 | var iterator = extendedTypeInfo.getDirectSubtypes().iterator(); | ||
119 | if (!iterator.hasNext()) { | ||
120 | throw new AssertionError("Expected trivial supertype %s to have a direct subtype" | ||
121 | .formatted(trivialType)); | ||
122 | } | ||
123 | PartialRelation replacement = setReplacement(trivialType, iterator.next()); | ||
124 | if (iterator.hasNext()) { | ||
125 | throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype" | ||
126 | .formatted(trivialType)); | ||
127 | } | ||
128 | replacements.put(trivialType, replacement); | ||
129 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { | ||
130 | var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype); | ||
131 | if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) { | ||
132 | throw new AssertionError("Expected %s to be subtype of %s".formatted(trivialType, supertype)); | ||
133 | } | ||
134 | var directSubtypes = extendedSupertypeInfo.getDirectSubtypes(); | ||
135 | if (directSubtypes.remove(trivialType)) { | ||
136 | directSubtypes.add(replacement); | ||
137 | } | ||
138 | } | ||
139 | for (var subtype : extendedTypeInfo.getAllSubtypes()) { | ||
140 | var extendedSubtypeInfo = extendedTypeInfoMap.get(subtype); | ||
141 | if (!extendedSubtypeInfo.getAllSupertypes().remove(trivialType)) { | ||
142 | throw new AssertionError("Expected %s to be supertype of %s".formatted(trivialType, subtype)); | ||
143 | } | ||
144 | } | ||
145 | extendedTypeInfoMap.remove(trivialType); | ||
146 | } | ||
147 | |||
148 | private PartialRelation setReplacement(PartialRelation trivialRelation, PartialRelation replacement) { | ||
149 | if (replacement == null) { | ||
150 | return trivialRelation; | ||
151 | } | ||
152 | var resolved = setReplacement(replacement, replacements.get(replacement)); | ||
153 | replacements.put(trivialRelation, resolved); | ||
154 | return resolved; | ||
155 | } | ||
156 | |||
157 | private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() { | ||
158 | var allExtendedTypeInfoList = sortTypes(); | ||
159 | var results = new LinkedHashMap<PartialRelation, TypeAnalysisResult>( | ||
160 | allExtendedTypeInfoList.size() + replacements.size()); | ||
161 | for (var extendedTypeInfo : allExtendedTypeInfoList) { | ||
162 | var type = extendedTypeInfo.getType(); | ||
163 | results.put(type, new PreservedType(extendedTypeInfo, allExtendedTypeInfoList)); | ||
164 | } | ||
165 | for (var entry : replacements.entrySet()) { | ||
166 | var type = entry.getKey(); | ||
167 | results.put(type, new EliminatedType(entry.getValue())); | ||
168 | } | ||
169 | return Collections.unmodifiableMap(results); | ||
170 | } | ||
171 | |||
172 | private List<ExtendedTypeInfo> sortTypes() { | ||
173 | // Invert {@code directSubtypes} to keep track of the out-degree of types. | ||
174 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
175 | for (var directSubtype : extendedTypeInfo.getDirectSubtypes()) { | ||
176 | var extendedDirectSubtypeInfo = extendedTypeInfoMap.get(directSubtype); | ||
177 | extendedDirectSubtypeInfo.getUnsortedDirectSupertypes().add(extendedTypeInfo.getType()); | ||
178 | } | ||
179 | } | ||
180 | // Build a <i>inverse</i> topological order ({@code extends} edges always points to earlier nodes in the order, | ||
181 | // breaking ties according to the original order ({@link ExtendedTypeInfo#index}) to form a 'stable' sort. | ||
182 | // See, e.g., https://stackoverflow.com/a/11236027. | ||
183 | var priorityQueue = new PriorityQueue<ExtendedTypeInfo>(); | ||
184 | for (var extendedTypeInfo : extendedTypeInfoMap.values()) { | ||
185 | if (extendedTypeInfo.getUnsortedDirectSupertypes().isEmpty()) { | ||
186 | priorityQueue.add(extendedTypeInfo); | ||
187 | } | ||
188 | } | ||
189 | var sorted = new ArrayList<ExtendedTypeInfo>(extendedTypeInfoMap.size()); | ||
190 | while (!priorityQueue.isEmpty()) { | ||
191 | var extendedTypeInfo = priorityQueue.remove(); | ||
192 | sorted.add(extendedTypeInfo); | ||
193 | for (var directSubtype : extendedTypeInfo.getDirectSubtypes()) { | ||
194 | var extendedDirectSubtypeInfo = extendedTypeInfoMap.get(directSubtype); | ||
195 | var unsortedDirectSupertypes = extendedDirectSubtypeInfo.getUnsortedDirectSupertypes(); | ||
196 | if (!unsortedDirectSupertypes.remove(extendedTypeInfo.getType())) { | ||
197 | throw new AssertionError("Expected %s to be a direct supertype of %s" | ||
198 | .formatted(extendedTypeInfo.getType(), directSubtype)); | ||
199 | } | ||
200 | if (unsortedDirectSupertypes.isEmpty()) { | ||
201 | priorityQueue.add(extendedDirectSubtypeInfo); | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | return Collections.unmodifiableList(sorted); | ||
206 | } | ||
207 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java new file mode 100644 index 00000000..06e3c05f --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.reasoning.translator.TranslatedRelation; | ||
11 | import tools.refinery.store.reasoning.translator.TranslationUnit; | ||
12 | import tools.refinery.store.representation.Symbol; | ||
13 | |||
14 | import java.util.Collection; | ||
15 | import java.util.List; | ||
16 | import java.util.Map; | ||
17 | |||
18 | public class TypeHierarchyTranslationUnit extends TranslationUnit { | ||
19 | static final Symbol<InferredType> INFERRED_TYPE_SYMBOL = Symbol.of( | ||
20 | "inferredType", 1, InferredType.class, InferredType.UNTYPED); | ||
21 | |||
22 | private final TypeAnalyzer typeAnalyzer; | ||
23 | |||
24 | public TypeHierarchyTranslationUnit(Map<PartialRelation, TypeInfo> typeInfoMap) { | ||
25 | typeAnalyzer = new TypeAnalyzer(typeInfoMap); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Collection<TranslatedRelation> getTranslatedRelations() { | ||
30 | return List.of(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public void initializeModel(Model model, int nodeCount) { | ||
35 | |||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java new file mode 100644 index 00000000..9f897e46 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | |||
10 | import java.util.*; | ||
11 | |||
12 | public record TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) { | ||
13 | public static Builder builder() { | ||
14 | return new Builder(); | ||
15 | } | ||
16 | |||
17 | public static class Builder { | ||
18 | private final Set<PartialRelation> supertypes = new LinkedHashSet<>(); | ||
19 | private boolean abstractType; | ||
20 | |||
21 | private Builder() { | ||
22 | } | ||
23 | |||
24 | public Builder supertypes(Collection<PartialRelation> supertypes) { | ||
25 | this.supertypes.addAll(supertypes); | ||
26 | return this; | ||
27 | } | ||
28 | |||
29 | public Builder supertypes(PartialRelation... supertypes) { | ||
30 | return supertypes(List.of(supertypes)); | ||
31 | } | ||
32 | |||
33 | public Builder supertype(PartialRelation supertype) { | ||
34 | supertypes.add(supertype); | ||
35 | return this; | ||
36 | } | ||
37 | |||
38 | public Builder abstractType(boolean abstractType) { | ||
39 | this.abstractType = abstractType; | ||
40 | return this; | ||
41 | } | ||
42 | |||
43 | public Builder abstractType() { | ||
44 | return abstractType(true); | ||
45 | } | ||
46 | |||
47 | public TypeInfo build() { | ||
48 | return new TypeInfo(Collections.unmodifiableSet(supertypes), abstractType); | ||
49 | } | ||
50 | } | ||
51 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java new file mode 100644 index 00000000..1d76855c --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | |||
11 | import java.util.Set; | ||
12 | |||
13 | import static org.hamcrest.MatcherAssert.assertThat; | ||
14 | import static org.hamcrest.Matchers.is; | ||
15 | |||
16 | class InferredTypeTest { | ||
17 | private final PartialRelation c1 = new PartialRelation("C1", 1); | ||
18 | private final PartialRelation c2 = new PartialRelation("C2", 1); | ||
19 | |||
20 | @Test | ||
21 | void untypedIsConsistentTest() { | ||
22 | var sut = new InferredType(Set.of(), Set.of(c1, c2), null); | ||
23 | assertThat(sut.isConsistent(), is(true)); | ||
24 | } | ||
25 | |||
26 | @Test | ||
27 | void typedIsConsistentTest() { | ||
28 | var sut = new InferredType(Set.of(c1), Set.of(c1, c2), c1); | ||
29 | assertThat(sut.isConsistent(), is(true)); | ||
30 | } | ||
31 | |||
32 | @Test | ||
33 | void typedIsInconsistentTest() { | ||
34 | var sut = new InferredType(Set.of(c1), Set.of(), null); | ||
35 | assertThat(sut.isConsistent(), is(false)); | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java new file mode 100644 index 00000000..05a476c6 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java | |||
@@ -0,0 +1,208 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
11 | import tools.refinery.store.representation.TruthValue; | ||
12 | |||
13 | import java.util.LinkedHashMap; | ||
14 | import java.util.Set; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.is; | ||
18 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
19 | |||
20 | class TypeAnalyzerExampleHierarchyTest { | ||
21 | private final PartialRelation a1 = new PartialRelation("A1", 1); | ||
22 | private final PartialRelation a2 = new PartialRelation("A2", 1); | ||
23 | private final PartialRelation a3 = new PartialRelation("A3", 1); | ||
24 | private final PartialRelation a4 = new PartialRelation("A4", 1); | ||
25 | private final PartialRelation a5 = new PartialRelation("A5", 1); | ||
26 | private final PartialRelation c1 = new PartialRelation("C1", 1); | ||
27 | private final PartialRelation c2 = new PartialRelation("C2", 1); | ||
28 | private final PartialRelation c3 = new PartialRelation("C3", 1); | ||
29 | private final PartialRelation c4 = new PartialRelation("C4", 1); | ||
30 | |||
31 | private TypeAnalyzer sut; | ||
32 | private TypeAnalyzerTester tester; | ||
33 | |||
34 | @BeforeEach | ||
35 | void beforeEach() { | ||
36 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
37 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); | ||
38 | typeInfoMap.put(a2, TypeInfo.builder().abstractType().build()); | ||
39 | typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); | ||
40 | typeInfoMap.put(a4, TypeInfo.builder().abstractType().build()); | ||
41 | typeInfoMap.put(a5, TypeInfo.builder().abstractType().build()); | ||
42 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(a1, a4).build()); | ||
43 | typeInfoMap.put(c2, TypeInfo.builder().supertypes(a1, a2, a3, a4).build()); | ||
44 | typeInfoMap.put(c3, TypeInfo.builder().supertype(a3).build()); | ||
45 | typeInfoMap.put(c4, TypeInfo.builder().supertype(a4).build()); | ||
46 | sut = new TypeAnalyzer(typeInfoMap); | ||
47 | tester = new TypeAnalyzerTester(sut); | ||
48 | } | ||
49 | |||
50 | @Test | ||
51 | void analysisResultsTest() { | ||
52 | assertAll( | ||
53 | () -> tester.assertAbstractType(a1, c1, c2), | ||
54 | () -> tester.assertEliminatedType(a2, c2), | ||
55 | () -> tester.assertAbstractType(a3, c2, c3), | ||
56 | () -> tester.assertAbstractType(a4, c1, c2, c4), | ||
57 | () -> tester.assertVacuousType(a5), | ||
58 | () -> tester.assertConcreteType(c1), | ||
59 | () -> tester.assertConcreteType(c2), | ||
60 | () -> tester.assertConcreteType(c3), | ||
61 | () -> tester.assertConcreteType(c4) | ||
62 | ); | ||
63 | } | ||
64 | |||
65 | @Test | ||
66 | void inferredTypesTest() { | ||
67 | assertAll( | ||
68 | () -> assertThat(sut.getUnknownType(), is(new InferredType(Set.of(), Set.of(c1, c2, c3, c4), null))), | ||
69 | () -> assertThat(tester.getInferredType(a1), is(new InferredType(Set.of(a1, a4), Set.of(c1, c2), c1))), | ||
70 | () -> assertThat(tester.getInferredType(a3), is(new InferredType(Set.of(a3), Set.of(c2, c3), c2))), | ||
71 | () -> assertThat(tester.getInferredType(a4), is(new InferredType(Set.of(a4), Set.of(c1, c2, c4), c1))), | ||
72 | () -> assertThat(tester.getInferredType(a5), is(new InferredType(Set.of(a5), Set.of(), null))), | ||
73 | () -> assertThat(tester.getInferredType(c1), is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), | ||
74 | () -> assertThat(tester.getInferredType(c2), | ||
75 | is(new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2))), | ||
76 | () -> assertThat(tester.getInferredType(c3), is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), | ||
77 | () -> assertThat(tester.getInferredType(c4), is(new InferredType(Set.of(a4, c4), Set.of(c4), c4))) | ||
78 | ); | ||
79 | } | ||
80 | |||
81 | @Test | ||
82 | void consistentMustTest() { | ||
83 | var a1Result = tester.getPreservedType(a1); | ||
84 | var a3Result = tester.getPreservedType(a3); | ||
85 | var expected = new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2); | ||
86 | assertAll( | ||
87 | () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.TRUE), is(expected)), | ||
88 | () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), is(expected)), | ||
89 | () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a1Result.asInferredType())), | ||
90 | () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a3Result.asInferredType())), | ||
91 | () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.TRUE), | ||
92 | is(a1Result.asInferredType())) | ||
93 | ); | ||
94 | } | ||
95 | |||
96 | @Test | ||
97 | void consistentMayNotTest() { | ||
98 | var a1Result = tester.getPreservedType(a1); | ||
99 | var a3Result = tester.getPreservedType(a3); | ||
100 | var a4Result = tester.getPreservedType(a4); | ||
101 | assertAll( | ||
102 | () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.FALSE), | ||
103 | is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), | ||
104 | () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
105 | is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), | ||
106 | () -> assertThat(a4Result.merge(a3Result.asInferredType(), TruthValue.FALSE), | ||
107 | is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), | ||
108 | () -> assertThat(a3Result.merge(a4Result.asInferredType(), TruthValue.FALSE), | ||
109 | is(new InferredType(Set.of(a4), Set.of(c1, c4), c1))), | ||
110 | () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.FALSE), | ||
111 | is(new InferredType(Set.of(), Set.of(c3, c4), null))), | ||
112 | () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.FALSE), | ||
113 | is(new InferredType(Set.of(), Set.of(c1, c4), null))), | ||
114 | () -> assertThat(a4Result.merge(sut.getUnknownType(), TruthValue.FALSE), | ||
115 | is(new InferredType(Set.of(), Set.of(c3), null))) | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | @Test | ||
120 | void consistentErrorTest() { | ||
121 | var c1Result = tester.getPreservedType(c1); | ||
122 | var a4Result = tester.getPreservedType(a4); | ||
123 | var expected = new InferredType(Set.of(c1, a1, a4), Set.of(), null); | ||
124 | assertAll( | ||
125 | () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.ERROR), is(expected)), | ||
126 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.ERROR), is(expected)) | ||
127 | ); | ||
128 | } | ||
129 | |||
130 | @Test | ||
131 | void consistentUnknownTest() { | ||
132 | var c1Result = tester.getPreservedType(c1); | ||
133 | var a4Result = tester.getPreservedType(a4); | ||
134 | assertAll( | ||
135 | () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.UNKNOWN), | ||
136 | is(a4Result.asInferredType())), | ||
137 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.UNKNOWN), | ||
138 | is(c1Result.asInferredType())) | ||
139 | ); | ||
140 | } | ||
141 | |||
142 | @Test | ||
143 | void inconsistentMustTest() { | ||
144 | var a1Result = tester.getPreservedType(a1); | ||
145 | var c3Result = tester.getPreservedType(c3); | ||
146 | assertAll( | ||
147 | () -> assertThat(a1Result.merge(c3Result.asInferredType(), TruthValue.TRUE), | ||
148 | is(new InferredType(Set.of(a1, a3, c3), Set.of(), null))), | ||
149 | () -> assertThat(c3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), | ||
150 | is(new InferredType(Set.of(a1, a3, a4, c3), Set.of(), null))) | ||
151 | ); | ||
152 | } | ||
153 | |||
154 | @Test | ||
155 | void inconsistentMayNotTest() { | ||
156 | var a1Result = tester.getPreservedType(a1); | ||
157 | var a4Result = tester.getPreservedType(a4); | ||
158 | var c1Result = tester.getPreservedType(c1); | ||
159 | assertAll( | ||
160 | () -> assertThat(a4Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
161 | is(new InferredType(Set.of(a1, a4), Set.of(), null))), | ||
162 | () -> assertThat(a1Result.merge(c1Result.asInferredType(), TruthValue.FALSE), | ||
163 | is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), | ||
164 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.FALSE), | ||
165 | is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), | ||
166 | () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
167 | is(new InferredType(Set.of(a1, a4), Set.of(), null))) | ||
168 | ); | ||
169 | } | ||
170 | |||
171 | @Test | ||
172 | void vacuousMustTest() { | ||
173 | var c1Result = tester.getPreservedType(c1); | ||
174 | var a5Result = tester.getPreservedType(a5); | ||
175 | assertAll( | ||
176 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.TRUE), | ||
177 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), | ||
178 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.TRUE), | ||
179 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))) | ||
180 | ); | ||
181 | } | ||
182 | |||
183 | @Test | ||
184 | void vacuousMayNotTest() { | ||
185 | var c1Result = tester.getPreservedType(c1); | ||
186 | var a5Result = tester.getPreservedType(a5); | ||
187 | assertAll( | ||
188 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.FALSE), | ||
189 | is(a5Result.asInferredType())), | ||
190 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.FALSE), | ||
191 | is(c1Result.asInferredType())) | ||
192 | ); | ||
193 | } | ||
194 | |||
195 | @Test | ||
196 | void vacuousErrorTest() { | ||
197 | var c1Result = tester.getPreservedType(c1); | ||
198 | var a5Result = tester.getPreservedType(a5); | ||
199 | assertAll( | ||
200 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.ERROR), | ||
201 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), | ||
202 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.ERROR), | ||
203 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), | ||
204 | () -> assertThat(a5Result.merge(a5Result.asInferredType(), TruthValue.ERROR), | ||
205 | is(a5Result.asInferredType())) | ||
206 | ); | ||
207 | } | ||
208 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java new file mode 100644 index 00000000..d0ef9d57 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java | |||
@@ -0,0 +1,205 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.representation.TruthValue; | ||
11 | |||
12 | import java.util.LinkedHashMap; | ||
13 | import java.util.Set; | ||
14 | |||
15 | import static org.hamcrest.MatcherAssert.assertThat; | ||
16 | import static org.hamcrest.Matchers.is; | ||
17 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
18 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
19 | |||
20 | class TypeAnalyzerTest { | ||
21 | @Test | ||
22 | void directSupertypesTest() { | ||
23 | var c1 = new PartialRelation("C1", 1); | ||
24 | var c2 = new PartialRelation("C2", 1); | ||
25 | var c3 = new PartialRelation("C3", 1); | ||
26 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
27 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(c2, c3).build()); | ||
28 | typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build()); | ||
29 | typeInfoMap.put(c3, TypeInfo.builder().build()); | ||
30 | |||
31 | var sut = new TypeAnalyzer(typeInfoMap); | ||
32 | var tester = new TypeAnalyzerTester(sut); | ||
33 | |||
34 | assertAll( | ||
35 | () -> tester.assertConcreteType(c1), | ||
36 | () -> tester.assertConcreteType(c2, c1), | ||
37 | () -> tester.assertConcreteType(c3, c2) | ||
38 | ); | ||
39 | } | ||
40 | |||
41 | @Test | ||
42 | void typeEliminationAbstractToConcreteTest() { | ||
43 | var c1 = new PartialRelation("C1", 1); | ||
44 | var c2 = new PartialRelation("C2", 1); | ||
45 | var a11 = new PartialRelation("A11", 1); | ||
46 | var a12 = new PartialRelation("A12", 1); | ||
47 | var a21 = new PartialRelation("A21", 1); | ||
48 | var a22 = new PartialRelation("A22", 1); | ||
49 | var a3 = new PartialRelation("A3", 1); | ||
50 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
51 | typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); | ||
52 | typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
53 | typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
54 | typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
55 | typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
56 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build()); | ||
57 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build()); | ||
58 | |||
59 | var sut = new TypeAnalyzer(typeInfoMap); | ||
60 | var tester = new TypeAnalyzerTester(sut); | ||
61 | |||
62 | assertAll( | ||
63 | () -> tester.assertConcreteType(c1), | ||
64 | () -> tester.assertConcreteType(c2), | ||
65 | () -> tester.assertEliminatedType(a11, c1), | ||
66 | () -> tester.assertEliminatedType(a12, c1), | ||
67 | () -> tester.assertEliminatedType(a21, c1), | ||
68 | () -> tester.assertEliminatedType(a22, c1), | ||
69 | () -> tester.assertAbstractType(a3, c1, c2) | ||
70 | ); | ||
71 | } | ||
72 | |||
73 | @Test | ||
74 | void typeEliminationConcreteToAbstractTest() { | ||
75 | var c1 = new PartialRelation("C1", 1); | ||
76 | var c2 = new PartialRelation("C2", 1); | ||
77 | var a11 = new PartialRelation("A11", 1); | ||
78 | var a12 = new PartialRelation("A12", 1); | ||
79 | var a21 = new PartialRelation("A21", 1); | ||
80 | var a22 = new PartialRelation("A22", 1); | ||
81 | var a3 = new PartialRelation("A3", 1); | ||
82 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
83 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build()); | ||
84 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build()); | ||
85 | typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
86 | typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
87 | typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
88 | typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
89 | typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); | ||
90 | |||
91 | var sut = new TypeAnalyzer(typeInfoMap); | ||
92 | var tester = new TypeAnalyzerTester(sut); | ||
93 | |||
94 | assertAll( | ||
95 | () -> tester.assertConcreteType(c1), | ||
96 | () -> tester.assertConcreteType(c2), | ||
97 | () -> tester.assertEliminatedType(a11, c1), | ||
98 | () -> tester.assertEliminatedType(a12, c1), | ||
99 | () -> tester.assertEliminatedType(a21, c1), | ||
100 | () -> tester.assertEliminatedType(a22, c1), | ||
101 | () -> tester.assertAbstractType(a3, c1, c2) | ||
102 | ); | ||
103 | } | ||
104 | |||
105 | @Test | ||
106 | void preserveConcreteTypeTest() { | ||
107 | var c1 = new PartialRelation("C1", 1); | ||
108 | var a1 = new PartialRelation("A1", 1); | ||
109 | var c2 = new PartialRelation("C2", 1); | ||
110 | var a2 = new PartialRelation("A2", 1); | ||
111 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
112 | typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build()); | ||
113 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().supertype(c2).build()); | ||
114 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a2).build()); | ||
115 | typeInfoMap.put(a2, TypeInfo.builder().abstractType().build()); | ||
116 | |||
117 | var sut = new TypeAnalyzer(typeInfoMap); | ||
118 | var tester = new TypeAnalyzerTester(sut); | ||
119 | |||
120 | assertAll( | ||
121 | () -> tester.assertConcreteType(c1), | ||
122 | () -> tester.assertEliminatedType(a1, c1), | ||
123 | () -> tester.assertConcreteType(c2, c1), | ||
124 | () -> tester.assertEliminatedType(a2, c2) | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | @Test | ||
129 | void mostGeneralCurrentTypeTest() { | ||
130 | var c1 = new PartialRelation("C1", 1); | ||
131 | var c2 = new PartialRelation("C2", 1); | ||
132 | var c3 = new PartialRelation("C3", 1); | ||
133 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
134 | typeInfoMap.put(c1, TypeInfo.builder().supertype(c3).build()); | ||
135 | typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build()); | ||
136 | typeInfoMap.put(c3, TypeInfo.builder().build()); | ||
137 | |||
138 | var sut = new TypeAnalyzer(typeInfoMap); | ||
139 | var tester = new TypeAnalyzerTester(sut); | ||
140 | var c3Result = tester.getPreservedType(c3); | ||
141 | |||
142 | var expected = new InferredType(Set.of(c3), Set.of(c1, c2, c3), c3); | ||
143 | assertAll( | ||
144 | () -> assertThat(tester.getInferredType(c3), is(expected)), | ||
145 | () -> assertThat(c3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(expected)) | ||
146 | ); | ||
147 | } | ||
148 | |||
149 | @Test | ||
150 | void preferFirstConcreteTypeTest() { | ||
151 | var a1 = new PartialRelation("A1", 1); | ||
152 | var c1 = new PartialRelation("C1", 1); | ||
153 | var c2 = new PartialRelation("C2", 1); | ||
154 | var c3 = new PartialRelation("C3", 1); | ||
155 | var c4 = new PartialRelation("C4", 1); | ||
156 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
157 | typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build()); | ||
158 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build()); | ||
159 | typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build()); | ||
160 | typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build()); | ||
161 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); | ||
162 | |||
163 | var sut = new TypeAnalyzer(typeInfoMap); | ||
164 | var tester = new TypeAnalyzerTester(sut); | ||
165 | var c1Result = tester.getPreservedType(c1); | ||
166 | var a1Result = tester.getPreservedType(a1); | ||
167 | |||
168 | assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
169 | is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c2))); | ||
170 | } | ||
171 | |||
172 | @Test | ||
173 | void preferFirstMostGeneralConcreteTypeTest() { | ||
174 | var a1 = new PartialRelation("A1", 1); | ||
175 | var c1 = new PartialRelation("C1", 1); | ||
176 | var c2 = new PartialRelation("C2", 1); | ||
177 | var c3 = new PartialRelation("C3", 1); | ||
178 | var c4 = new PartialRelation("C4", 1); | ||
179 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
180 | typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build()); | ||
181 | typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build()); | ||
182 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build()); | ||
183 | typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build()); | ||
184 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); | ||
185 | |||
186 | var sut = new TypeAnalyzer(typeInfoMap); | ||
187 | var tester = new TypeAnalyzerTester(sut); | ||
188 | var c1Result = tester.getPreservedType(c1); | ||
189 | var a1Result = tester.getPreservedType(a1); | ||
190 | |||
191 | assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
192 | is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c3))); | ||
193 | } | ||
194 | |||
195 | @Test | ||
196 | void circularTypeHierarchyTest() { | ||
197 | var c1 = new PartialRelation("C1", 1); | ||
198 | var c2 = new PartialRelation("C2", 1); | ||
199 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
200 | typeInfoMap.put(c1, TypeInfo.builder().supertype(c2).build()); | ||
201 | typeInfoMap.put(c2, TypeInfo.builder().supertype(c1).build()); | ||
202 | |||
203 | assertThrows(IllegalArgumentException.class, () -> new TypeAnalyzer(typeInfoMap)); | ||
204 | } | ||
205 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java new file mode 100644 index 00000000..2924816e --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java | |||
@@ -0,0 +1,56 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | |||
10 | import static org.hamcrest.MatcherAssert.assertThat; | ||
11 | import static org.hamcrest.Matchers.*; | ||
12 | import static org.hamcrest.Matchers.is; | ||
13 | |||
14 | class TypeAnalyzerTester { | ||
15 | private final TypeAnalyzer sut; | ||
16 | |||
17 | public TypeAnalyzerTester(TypeAnalyzer sut) { | ||
18 | this.sut = sut; | ||
19 | } | ||
20 | |||
21 | public void assertAbstractType(PartialRelation partialRelation, PartialRelation... directSubtypes) { | ||
22 | assertPreservedType(partialRelation, true, false, directSubtypes); | ||
23 | } | ||
24 | |||
25 | public void assertVacuousType(PartialRelation partialRelation) { | ||
26 | assertPreservedType(partialRelation, true, true); | ||
27 | } | ||
28 | |||
29 | public void assertConcreteType(PartialRelation partialRelation, PartialRelation... directSubtypes) { | ||
30 | assertPreservedType(partialRelation, false, false, directSubtypes); | ||
31 | } | ||
32 | |||
33 | private void assertPreservedType(PartialRelation partialRelation, boolean isAbstract, boolean isVacuous, | ||
34 | PartialRelation... directSubtypes) { | ||
35 | var result = sut.getAnalysisResults().get(partialRelation); | ||
36 | assertThat(result, is(instanceOf(PreservedType.class))); | ||
37 | var preservedResult = (PreservedType) result; | ||
38 | assertThat(preservedResult.isAbstractType(), is(isAbstract)); | ||
39 | assertThat(preservedResult.isVacuous(), is(isVacuous)); | ||
40 | assertThat(preservedResult.getDirectSubtypes(), hasItems(directSubtypes)); | ||
41 | } | ||
42 | |||
43 | public void assertEliminatedType(PartialRelation partialRelation, PartialRelation replacement) { | ||
44 | var result = sut.getAnalysisResults().get(partialRelation); | ||
45 | assertThat(result, is(instanceOf(EliminatedType.class))); | ||
46 | assertThat(((EliminatedType) result).replacement(), is(replacement)); | ||
47 | } | ||
48 | |||
49 | public PreservedType getPreservedType(PartialRelation partialRelation) { | ||
50 | return (PreservedType) sut.getAnalysisResults().get(partialRelation); | ||
51 | } | ||
52 | |||
53 | public InferredType getInferredType(PartialRelation partialRelation) { | ||
54 | return getPreservedType(partialRelation).asInferredType(); | ||
55 | } | ||
56 | } | ||
diff --git a/subprojects/store/build.gradle b/subprojects/store/build.gradle deleted file mode 100644 index 370d094b..00000000 --- a/subprojects/store/build.gradle +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | plugins { | ||
2 | id 'refinery-java-library' | ||
3 | id 'refinery-jmh' | ||
4 | } | ||
diff --git a/subprojects/store/build.gradle.kts b/subprojects/store/build.gradle.kts new file mode 100644 index 00000000..2c485020 --- /dev/null +++ b/subprojects/store/build.gradle.kts | |||
@@ -0,0 +1,10 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | |||
7 | plugins { | ||
8 | id("tools.refinery.gradle.java-library") | ||
9 | id("tools.refinery.gradle.jmh") | ||
10 | } | ||
diff --git a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutBenchmark.java b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutBenchmark.java index cdf3d3c8..485fda3d 100644 --- a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutBenchmark.java +++ b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutBenchmark.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.benchmarks; | 6 | package tools.refinery.store.map.benchmarks; |
2 | 7 | ||
3 | import java.util.ArrayList; | 8 | import java.util.ArrayList; |
diff --git a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java index 5484f115..7e89cd06 100644 --- a/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java +++ b/subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.benchmarks; | 6 | package tools.refinery.store.map.benchmarks; |
2 | 7 | ||
3 | import java.util.Random; | 8 | import java.util.Random; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java index 4c142217..8d3e998e 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java | |||
@@ -1,27 +1,48 @@ | |||
1 | package tools.refinery.store.adapter; | 1 | package tools.refinery.store.adapter; |
2 | 2 | /* | |
3 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: EPL-2.0 | ||
6 | */ | ||
3 | import tools.refinery.store.model.ModelStore; | 7 | import tools.refinery.store.model.ModelStore; |
4 | import tools.refinery.store.model.ModelStoreBuilder; | 8 | import tools.refinery.store.model.ModelStoreBuilder; |
5 | 9 | ||
6 | public abstract class AbstractModelAdapterBuilder implements ModelAdapterBuilder { | 10 | public abstract class AbstractModelAdapterBuilder<T extends ModelStoreAdapter> implements ModelAdapterBuilder { |
7 | private final ModelStoreBuilder storeBuilder; | 11 | private boolean configured; |
8 | 12 | ||
9 | protected AbstractModelAdapterBuilder(ModelStoreBuilder storeBuilder) { | 13 | @Override |
10 | this.storeBuilder = storeBuilder; | 14 | public boolean isConfigured() { |
15 | return configured; | ||
11 | } | 16 | } |
12 | 17 | ||
13 | @Override | 18 | protected void checkConfigured() { |
14 | public <T extends ModelAdapterBuilder> T with(ModelAdapterBuilderFactory<?, ?, T> adapterBuilderFactory) { | 19 | if (!configured) { |
15 | return storeBuilder.with(adapterBuilderFactory); | 20 | throw new IllegalStateException("Model adapter builder was not configured"); |
21 | } | ||
22 | } | ||
23 | |||
24 | protected void checkNotConfigured() { | ||
25 | if (configured) { | ||
26 | throw new IllegalStateException("Model adapter builder was already configured"); | ||
27 | } | ||
28 | } | ||
29 | |||
30 | protected void doConfigure(ModelStoreBuilder storeBuilder) { | ||
31 | // Nothing to configure by default. | ||
16 | } | 32 | } |
17 | 33 | ||
18 | @Override | 34 | @Override |
19 | public ModelStoreBuilder getStoreBuilder() { | 35 | public final void configure(ModelStoreBuilder storeBuilder) { |
20 | return storeBuilder; | 36 | checkNotConfigured(); |
37 | doConfigure(storeBuilder); | ||
38 | configured = true; | ||
21 | } | 39 | } |
22 | 40 | ||
41 | protected abstract T doBuild(ModelStore store); | ||
42 | |||
23 | @Override | 43 | @Override |
24 | public ModelStore build() { | 44 | public final T build(ModelStore store) { |
25 | return storeBuilder.build(); | 45 | checkConfigured(); |
46 | return doBuild(store); | ||
26 | } | 47 | } |
27 | } | 48 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java deleted file mode 100644 index 74bae6f0..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java +++ /dev/null | |||
@@ -1,97 +0,0 @@ | |||
1 | package tools.refinery.store.adapter; | ||
2 | |||
3 | import org.jetbrains.annotations.NotNull; | ||
4 | |||
5 | import java.util.*; | ||
6 | import java.util.function.Consumer; | ||
7 | |||
8 | public class AdapterList<T> implements Iterable<T> { | ||
9 | private final List<AnyModelAdapterType> adapterTypes; | ||
10 | private final List<T> adapters; | ||
11 | |||
12 | public AdapterList() { | ||
13 | adapterTypes = new ArrayList<>(); | ||
14 | adapters = new ArrayList<>(); | ||
15 | } | ||
16 | |||
17 | public AdapterList(int adapterCount) { | ||
18 | adapterTypes = new ArrayList<>(adapterCount); | ||
19 | adapters = new ArrayList<>(adapterCount); | ||
20 | } | ||
21 | |||
22 | public int size() { | ||
23 | return adapters.size(); | ||
24 | } | ||
25 | |||
26 | public void add(AnyModelAdapterType adapterType, T adapter) { | ||
27 | adapterTypes.add(adapterType); | ||
28 | adapters.add(adapter); | ||
29 | } | ||
30 | |||
31 | public <U extends T> Optional<U> tryGet(AnyModelAdapterType adapterType, Class<? extends U> adapterClass) { | ||
32 | int size = size(); | ||
33 | for (int i = 0; i < size; i++) { | ||
34 | if (getType(i).supports(adapterType)) { | ||
35 | return Optional.of(adapterClass.cast(get(i))); | ||
36 | } | ||
37 | } | ||
38 | return Optional.empty(); | ||
39 | } | ||
40 | |||
41 | public <U extends T> U get(AnyModelAdapterType adapterType, Class<U> adapterClass) { | ||
42 | return tryGet(adapterType, adapterClass).orElseThrow(() -> new IllegalArgumentException( | ||
43 | "No %s was configured".formatted(adapterType))); | ||
44 | } | ||
45 | |||
46 | public AnyModelAdapterType getType(int i) { | ||
47 | return adapterTypes.get(i); | ||
48 | } | ||
49 | |||
50 | public T get(int i) { | ||
51 | return adapters.get(i); | ||
52 | } | ||
53 | |||
54 | public Collection<AnyModelAdapterType> getAdapterTypes() { | ||
55 | return Collections.unmodifiableCollection(adapterTypes); | ||
56 | } | ||
57 | |||
58 | public Iterable<Entry<T>> withAdapterTypes() { | ||
59 | return () -> new Iterator<>() { | ||
60 | private int i = 0; | ||
61 | |||
62 | @Override | ||
63 | public boolean hasNext() { | ||
64 | return i < size(); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public Entry<T> next() { | ||
69 | if (i >= size()) { | ||
70 | throw new NoSuchElementException(); | ||
71 | } | ||
72 | var entry = new Entry<>(getType(i), get(i)); | ||
73 | i++; | ||
74 | return entry; | ||
75 | } | ||
76 | }; | ||
77 | } | ||
78 | |||
79 | @NotNull | ||
80 | @Override | ||
81 | public Iterator<T> iterator() { | ||
82 | return adapters.iterator(); | ||
83 | } | ||
84 | |||
85 | @Override | ||
86 | public void forEach(Consumer<? super T> action) { | ||
87 | adapters.forEach(action); | ||
88 | } | ||
89 | |||
90 | @Override | ||
91 | public Spliterator<T> spliterator() { | ||
92 | return adapters.spliterator(); | ||
93 | } | ||
94 | |||
95 | public record Entry<T>(AnyModelAdapterType adapterType, T adapter) { | ||
96 | } | ||
97 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterUtils.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterUtils.java new file mode 100644 index 00000000..556e99f0 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterUtils.java | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.adapter; | ||
7 | |||
8 | import java.util.Collection; | ||
9 | import java.util.Optional; | ||
10 | |||
11 | public class AdapterUtils { | ||
12 | private AdapterUtils() { | ||
13 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
14 | } | ||
15 | |||
16 | public static <T, U extends T> Optional<U> tryGetAdapter(Collection<T> adapters, Class<? extends U> type) { | ||
17 | var iterator = adapters.stream().filter(type::isInstance).iterator(); | ||
18 | if (!iterator.hasNext()) { | ||
19 | return Optional.empty(); | ||
20 | } | ||
21 | var adapter = type.cast(iterator.next()); | ||
22 | if (iterator.hasNext()) { | ||
23 | throw new IllegalArgumentException("Ambiguous adapter: both %s and %s match %s" | ||
24 | .formatted(adapter.getClass().getName(), iterator.next().getClass().getName(), type.getName())); | ||
25 | } | ||
26 | return Optional.of(adapter); | ||
27 | } | ||
28 | |||
29 | public static <T> T getAdapter(Collection<? super T> adapters, Class<T> type) { | ||
30 | return tryGetAdapter(adapters, type).orElseThrow(() -> new IllegalArgumentException( | ||
31 | "No %s adapter was configured".formatted(type.getName()))); | ||
32 | } | ||
33 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java deleted file mode 100644 index 37a247fe..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | package tools.refinery.store.adapter; | ||
2 | |||
3 | import java.util.Collection; | ||
4 | |||
5 | public sealed interface AnyModelAdapterType permits ModelAdapterType { | ||
6 | Class<? extends ModelAdapter> getModelAdapterClass(); | ||
7 | |||
8 | Class<? extends ModelStoreAdapter> getModelStoreAdapterClass(); | ||
9 | |||
10 | Class<? extends ModelAdapterBuilder> getModelAdapterBuilderClass(); | ||
11 | |||
12 | Collection<AnyModelAdapterType> getSupportedAdapterTypes(); | ||
13 | |||
14 | default boolean supports(AnyModelAdapterType targetAdapter) { | ||
15 | return getSupportedAdapterTypes().contains(targetAdapter); | ||
16 | } | ||
17 | |||
18 | String getName(); | ||
19 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java index aa079e01..672007aa 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.adapter; | 6 | package tools.refinery.store.adapter; |
2 | 7 | ||
3 | import tools.refinery.store.model.Model; | 8 | import tools.refinery.store.model.Model; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java index 64b3e59f..75e5e07d 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java | |||
@@ -1,17 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.adapter; | 6 | package tools.refinery.store.adapter; |
2 | 7 | ||
3 | import tools.refinery.store.model.ModelStore; | 8 | import tools.refinery.store.model.ModelStore; |
4 | import tools.refinery.store.model.ModelStoreBuilder; | 9 | import tools.refinery.store.model.ModelStoreBuilder; |
5 | 10 | ||
6 | public interface ModelAdapterBuilder { | 11 | public interface ModelAdapterBuilder { |
7 | ModelStoreAdapter createStoreAdapter(ModelStore store); | 12 | boolean isConfigured(); |
8 | 13 | ||
9 | <T extends ModelAdapterBuilder> T with(ModelAdapterBuilderFactory<?, ?, T> adapterBuilderFactory); | 14 | void configure(ModelStoreBuilder storeBuilder); |
10 | 15 | ||
11 | ModelStoreBuilder getStoreBuilder(); | 16 | ModelStoreAdapter build(ModelStore store); |
12 | |||
13 | default void configure() { | ||
14 | } | ||
15 | |||
16 | ModelStore build(); | ||
17 | } | 17 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java deleted file mode 100644 index 7c9b01bc..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | package tools.refinery.store.adapter; | ||
2 | |||
3 | import tools.refinery.store.model.ModelStoreBuilder; | ||
4 | |||
5 | public abstract class ModelAdapterBuilderFactory<T1 extends ModelAdapter, T2 extends ModelStoreAdapter, | ||
6 | T3 extends ModelAdapterBuilder> extends ModelAdapterType<T1, T2, T3> { | ||
7 | |||
8 | protected ModelAdapterBuilderFactory(Class<T1> modelAdapterClass, Class<T2> modelStoreAdapterClass, | ||
9 | Class<T3> modelAdapterBuilderClass) { | ||
10 | super(modelAdapterClass, modelStoreAdapterClass, modelAdapterBuilderClass); | ||
11 | } | ||
12 | |||
13 | public abstract T3 createBuilder(ModelStoreBuilder storeBuilder); | ||
14 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java deleted file mode 100644 index 82ddeb12..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java +++ /dev/null | |||
@@ -1,79 +0,0 @@ | |||
1 | package tools.refinery.store.adapter; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.model.ModelStore; | ||
5 | |||
6 | import java.lang.reflect.Method; | ||
7 | import java.util.Collection; | ||
8 | import java.util.Collections; | ||
9 | import java.util.HashSet; | ||
10 | import java.util.Set; | ||
11 | |||
12 | public abstract non-sealed class ModelAdapterType<T1 extends ModelAdapter, T2 extends ModelStoreAdapter, | ||
13 | T3 extends ModelAdapterBuilder> implements AnyModelAdapterType { | ||
14 | private final Class<? extends T1> modelAdapterClass; | ||
15 | private final Class<? extends T2> modelStoreAdapterClass; | ||
16 | private final Class<? extends T3> modelAdapterBuilderClass; | ||
17 | private final Set<AnyModelAdapterType> supportedAdapters = new HashSet<>(); | ||
18 | |||
19 | protected ModelAdapterType(Class<T1> modelAdapterClass, Class<T2> modelStoreAdapterClass, | ||
20 | Class<T3> modelAdapterBuilderClass) { | ||
21 | checkReturnType(modelAdapterClass, modelStoreAdapterClass, "createModelAdapter", Model.class); | ||
22 | checkReturnType(modelStoreAdapterClass, modelAdapterBuilderClass, "createStoreAdapter", ModelStore.class); | ||
23 | this.modelAdapterClass = modelAdapterClass; | ||
24 | this.modelStoreAdapterClass = modelStoreAdapterClass; | ||
25 | this.modelAdapterBuilderClass = modelAdapterBuilderClass; | ||
26 | supportedAdapters.add(this); | ||
27 | } | ||
28 | |||
29 | private void checkReturnType(Class<?> expectedReturnType, Class<?> ownerClass, String methodName, | ||
30 | Class<?>... argumentTypes) { | ||
31 | Method method; | ||
32 | try { | ||
33 | method = ownerClass.getMethod(methodName, argumentTypes); | ||
34 | } catch (NoSuchMethodException e) { | ||
35 | throw new IllegalStateException("Invalid %s: %s#%s method is required" | ||
36 | .formatted(this, ownerClass.getName(), methodName), e); | ||
37 | } | ||
38 | var returnType = method.getReturnType(); | ||
39 | if (!expectedReturnType.isAssignableFrom(returnType)) { | ||
40 | throw new IllegalStateException("Invalid %s: %s is not assignable from the return type %s of %s#%s" | ||
41 | .formatted(this, expectedReturnType.getName(), returnType.getCanonicalName(), | ||
42 | ownerClass.getName(), methodName)); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | protected void extendsAdapter(ModelAdapterType<? super T1, ? super T2, ? super T3> superAdapter) { | ||
47 | supportedAdapters.addAll(superAdapter.supportedAdapters); | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public final Class<? extends T1> getModelAdapterClass() { | ||
52 | return modelAdapterClass; | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public final Class<? extends T2> getModelStoreAdapterClass() { | ||
57 | return modelStoreAdapterClass; | ||
58 | } | ||
59 | |||
60 | @Override | ||
61 | public final Class<? extends T3> getModelAdapterBuilderClass() { | ||
62 | return modelAdapterBuilderClass; | ||
63 | } | ||
64 | |||
65 | @Override | ||
66 | public Collection<AnyModelAdapterType> getSupportedAdapterTypes() { | ||
67 | return Collections.unmodifiableCollection(supportedAdapters); | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public String getName() { | ||
72 | return "%s.ADAPTER".formatted(this.getClass().getName()); | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public String toString() { | ||
77 | return getName(); | ||
78 | } | ||
79 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java index 1eb40ada..bc5f7b6b 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java +++ b/subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.adapter; | 6 | package tools.refinery.store.adapter; |
2 | 7 | ||
3 | import tools.refinery.store.model.Model; | 8 | import tools.refinery.store.model.Model; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java index ead79878..01099eb0 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | public sealed interface AnyVersionedMap extends Versioned permits VersionedMap { | 8 | public sealed interface AnyVersionedMap extends Versioned permits VersionedMap { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/ContentHashCode.java b/subprojects/store/src/main/java/tools/refinery/store/map/ContentHashCode.java index 8deeab23..cbea05e1 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/ContentHashCode.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/ContentHashCode.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | public enum ContentHashCode { | 8 | public enum ContentHashCode { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java index 75f1e2ab..8e451230 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | import tools.refinery.store.map.internal.Node; | 8 | import tools.refinery.store.map.internal.Node; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Cursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/Cursor.java index b420585c..3bdca104 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/Cursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/Cursor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | import java.util.Set; | 8 | import java.util.Set; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/CursorAsIterator.java b/subprojects/store/src/main/java/tools/refinery/store/map/CursorAsIterator.java index 65ae6648..c7e4d279 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/CursorAsIterator.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/CursorAsIterator.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | import java.util.Iterator; | 8 | import java.util.Iterator; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java b/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java new file mode 100644 index 00000000..0a94d449 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java | |||
@@ -0,0 +1,41 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.map; | ||
7 | |||
8 | public final class Cursors { | ||
9 | private Cursors() { | ||
10 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
11 | } | ||
12 | |||
13 | public static <K, V> Cursor<K, V> empty() { | ||
14 | return new Empty<>(); | ||
15 | } | ||
16 | |||
17 | private static class Empty<K, V> implements Cursor<K, V> { | ||
18 | private boolean terminated = false; | ||
19 | |||
20 | @Override | ||
21 | public K getKey() { | ||
22 | return null; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public V getValue() { | ||
27 | return null; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public boolean isTerminated() { | ||
32 | return terminated; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public boolean move() { | ||
37 | terminated = true; | ||
38 | return false; | ||
39 | } | ||
40 | } | ||
41 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/DiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/DiffCursor.java index 701f3ec8..4322e041 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/DiffCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/DiffCursor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | public interface DiffCursor<K, V> extends Cursor<K,V> { | 8 | public interface DiffCursor<K, V> extends Cursor<K,V> { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/MapAsIterable.java b/subprojects/store/src/main/java/tools/refinery/store/map/MapAsIterable.java index 6b986732..199b548f 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/MapAsIterable.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/MapAsIterable.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | import java.util.Iterator; | 8 | import java.util.Iterator; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java index 6a23e9d5..55720db3 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | public interface Versioned { | 8 | public interface Versioned { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java index 08ce1dbd..c8226c3e 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap { | 8 | public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java index 7768287a..b24c404c 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl; | 8 | import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java index 3856460d..b00cd961 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | public class VersionedMapStoreConfiguration { | 8 | public class VersionedMapStoreConfiguration { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java index beeed110..a934d59e 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map; | 6 | package tools.refinery.store.map; |
2 | 7 | ||
3 | import tools.refinery.store.map.internal.*; | 8 | import tools.refinery.store.map.internal.*; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java index 0806c486..a357fbce 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | enum HashClash { | 8 | enum HashClash { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java index 92446711..d052318f 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | import java.util.Arrays; | 8 | import java.util.Arrays; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java index 7e4f82e8..d42519b2 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | import tools.refinery.store.map.AnyVersionedMap; | 8 | import tools.refinery.store.map.AnyVersionedMap; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java index 59e8d738..fb1d5d2b 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | import tools.refinery.store.map.AnyVersionedMap; | 8 | import tools.refinery.store.map.AnyVersionedMap; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java index 81bf6188..bb85deb9 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java | |||
@@ -1,10 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
8 | import tools.refinery.store.map.ContinousHashProvider; | ||
9 | |||
3 | import java.util.Arrays; | 10 | import java.util.Arrays; |
4 | import java.util.Map; | 11 | import java.util.Map; |
5 | 12 | ||
6 | import tools.refinery.store.map.ContinousHashProvider; | ||
7 | |||
8 | public class MutableNode<K, V> extends Node<K, V> { | 13 | public class MutableNode<K, V> extends Node<K, V> { |
9 | int cachedHash; | 14 | int cachedHash; |
10 | protected boolean cachedHashValid; | 15 | protected boolean cachedHashValid; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java index 3dd332da..4b44f760 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | import java.util.Map; | 8 | import java.util.Map; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java index 5534c703..354af51d 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | public class OldValueBox<V>{ | 8 | public class OldValueBox<V>{ |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java index 2ceca463..c107f7e0 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.internal; | 6 | package tools.refinery.store.map.internal; |
2 | 7 | ||
3 | import tools.refinery.store.map.*; | 8 | import tools.refinery.store.map.*; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java index d18ba71d..f906b48a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.representation.AnySymbol; | 8 | import tools.refinery.store.representation.AnySymbol; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java index 55949d0c..26ad9a69 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java b/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java index 73950779..6f7b24c1 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.tuple.Tuple; | 8 | import tools.refinery.store.tuple.Tuple; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java index 6ca1ac7b..d58d91c3 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/Model.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/Model.java | |||
@@ -1,7 +1,11 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.adapter.ModelAdapter; | 8 | import tools.refinery.store.adapter.ModelAdapter; |
4 | import tools.refinery.store.adapter.ModelAdapterType; | ||
5 | import tools.refinery.store.map.Versioned; | 9 | import tools.refinery.store.map.Versioned; |
6 | import tools.refinery.store.representation.AnySymbol; | 10 | import tools.refinery.store.representation.AnySymbol; |
7 | import tools.refinery.store.representation.Symbol; | 11 | import tools.refinery.store.representation.Symbol; |
@@ -25,9 +29,9 @@ public interface Model extends Versioned { | |||
25 | 29 | ||
26 | ModelDiffCursor getDiffCursor(long to); | 30 | ModelDiffCursor getDiffCursor(long to); |
27 | 31 | ||
28 | <T extends ModelAdapter> Optional<T> tryGetAdapter(ModelAdapterType<? extends T, ?, ?> adapterType); | 32 | <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType); |
29 | 33 | ||
30 | <T extends ModelAdapter> T getAdapter(ModelAdapterType<T, ?, ?> adapterType); | 34 | <T extends ModelAdapter> T getAdapter(Class<T> adapterType); |
31 | 35 | ||
32 | void addListener(ModelListener listener); | 36 | void addListener(ModelListener listener); |
33 | 37 | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java index 97bf2039..7b236891 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.map.DiffCursor; | 8 | import tools.refinery.store.map.DiffCursor; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java index f67540bb..a9ad8cfd 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | public interface ModelListener { | 8 | public interface ModelListener { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java index 2e7e62c3..b10eb8a4 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java | |||
@@ -1,6 +1,10 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.adapter.ModelAdapterType; | ||
4 | import tools.refinery.store.adapter.ModelStoreAdapter; | 8 | import tools.refinery.store.adapter.ModelStoreAdapter; |
5 | import tools.refinery.store.model.internal.ModelStoreBuilderImpl; | 9 | import tools.refinery.store.model.internal.ModelStoreBuilderImpl; |
6 | import tools.refinery.store.representation.AnySymbol; | 10 | import tools.refinery.store.representation.AnySymbol; |
@@ -20,9 +24,9 @@ public interface ModelStore { | |||
20 | 24 | ||
21 | ModelDiffCursor getDiffCursor(long from, long to); | 25 | ModelDiffCursor getDiffCursor(long from, long to); |
22 | 26 | ||
23 | <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(ModelAdapterType<?, ? extends T, ?> adapterType); | 27 | <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType); |
24 | 28 | ||
25 | <T extends ModelStoreAdapter> T getAdapter(ModelAdapterType<?, T, ?> adapterType); | 29 | <T extends ModelStoreAdapter> T getAdapter(Class<T> adapterType); |
26 | 30 | ||
27 | static ModelStoreBuilder builder() { | 31 | static ModelStoreBuilder builder() { |
28 | return new ModelStoreBuilderImpl(); | 32 | return new ModelStoreBuilderImpl(); |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java index 289099da..3a4024b5 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java | |||
@@ -1,8 +1,11 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.adapter.ModelAdapterBuilder; | 8 | import tools.refinery.store.adapter.ModelAdapterBuilder; |
4 | import tools.refinery.store.adapter.ModelAdapterBuilderFactory; | ||
5 | import tools.refinery.store.adapter.ModelAdapterType; | ||
6 | import tools.refinery.store.representation.AnySymbol; | 9 | import tools.refinery.store.representation.AnySymbol; |
7 | import tools.refinery.store.representation.Symbol; | 10 | import tools.refinery.store.representation.Symbol; |
8 | 11 | ||
@@ -26,11 +29,11 @@ public interface ModelStoreBuilder { | |||
26 | 29 | ||
27 | <T> ModelStoreBuilder symbol(Symbol<T> symbol); | 30 | <T> ModelStoreBuilder symbol(Symbol<T> symbol); |
28 | 31 | ||
29 | <T extends ModelAdapterBuilder> T with(ModelAdapterBuilderFactory<?, ?, T> adapterBuilderFactory); | 32 | <T extends ModelAdapterBuilder> ModelStoreBuilder with(T adapterBuilder); |
30 | 33 | ||
31 | <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(ModelAdapterType<?, ?, ? extends T> adapterType); | 34 | <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(Class<? extends T> adapterType); |
32 | 35 | ||
33 | <T extends ModelAdapterBuilder> T getAdapter(ModelAdapterType<?, ?, T> adapterType); | 36 | <T extends ModelAdapterBuilder> T getAdapter(Class<T> adapterType); |
34 | 37 | ||
35 | ModelStore build(); | 38 | ModelStore build(); |
36 | } | 39 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java index 1183b8f2..fdd4425e 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.map.ContinousHashProvider; | 8 | import tools.refinery.store.map.ContinousHashProvider; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java index 33059a1b..14116a90 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model; | 6 | package tools.refinery.store.model; |
2 | 7 | ||
3 | import tools.refinery.store.map.ContinousHashProvider; | 8 | import tools.refinery.store.map.ContinousHashProvider; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java index f68859db..dbd95d80 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
2 | 7 | ||
3 | public enum ModelAction { | 8 | public enum ModelAction { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java index 9eb438c4..c5475a1a 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java | |||
@@ -1,9 +1,12 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.adapter.AdapterList; | 8 | import tools.refinery.store.adapter.AdapterUtils; |
4 | import tools.refinery.store.adapter.AnyModelAdapterType; | ||
5 | import tools.refinery.store.adapter.ModelAdapter; | 9 | import tools.refinery.store.adapter.ModelAdapter; |
6 | import tools.refinery.store.adapter.ModelAdapterType; | ||
7 | import tools.refinery.store.map.DiffCursor; | 10 | import tools.refinery.store.map.DiffCursor; |
8 | import tools.refinery.store.model.*; | 11 | import tools.refinery.store.model.*; |
9 | import tools.refinery.store.representation.AnySymbol; | 12 | import tools.refinery.store.representation.AnySymbol; |
@@ -16,7 +19,7 @@ public class ModelImpl implements Model { | |||
16 | private final ModelStore store; | 19 | private final ModelStore store; |
17 | private long state; | 20 | private long state; |
18 | private Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations; | 21 | private Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations; |
19 | private final AdapterList<ModelAdapter> adapters; | 22 | private final List<ModelAdapter> adapters; |
20 | private final List<ModelListener> listeners = new ArrayList<>(); | 23 | private final List<ModelListener> listeners = new ArrayList<>(); |
21 | private boolean uncommittedChanges; | 24 | private boolean uncommittedChanges; |
22 | private ModelAction pendingAction = ModelAction.NONE; | 25 | private ModelAction pendingAction = ModelAction.NONE; |
@@ -25,7 +28,7 @@ public class ModelImpl implements Model { | |||
25 | ModelImpl(ModelStore store, long state, int adapterCount) { | 28 | ModelImpl(ModelStore store, long state, int adapterCount) { |
26 | this.store = store; | 29 | this.store = store; |
27 | this.state = state; | 30 | this.state = state; |
28 | adapters = new AdapterList<>(adapterCount); | 31 | adapters = new ArrayList<>(adapterCount); |
29 | } | 32 | } |
30 | 33 | ||
31 | void setInterpretations(Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) { | 34 | void setInterpretations(Map<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) { |
@@ -162,17 +165,17 @@ public class ModelImpl implements Model { | |||
162 | } | 165 | } |
163 | 166 | ||
164 | @Override | 167 | @Override |
165 | public <T extends ModelAdapter> Optional<T> tryGetAdapter(ModelAdapterType<? extends T, ?, ?> adapterType) { | 168 | public <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType) { |
166 | return adapters.tryGet(adapterType, adapterType.getModelAdapterClass()); | 169 | return AdapterUtils.tryGetAdapter(adapters, adapterType); |
167 | } | 170 | } |
168 | 171 | ||
169 | @Override | 172 | @Override |
170 | public <T extends ModelAdapter> T getAdapter(ModelAdapterType<T, ?, ?> adapterType) { | 173 | public <T extends ModelAdapter> T getAdapter(Class<T> adapterType) { |
171 | return adapters.get(adapterType, adapterType.getModelAdapterClass()); | 174 | return AdapterUtils.getAdapter(adapters, adapterType); |
172 | } | 175 | } |
173 | 176 | ||
174 | void addAdapter(AnyModelAdapterType adapterType, ModelAdapter adapter) { | 177 | void addAdapter(ModelAdapter adapter) { |
175 | adapters.add(adapterType, adapter); | 178 | adapters.add(adapter); |
176 | } | 179 | } |
177 | 180 | ||
178 | @Override | 181 | @Override |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java index 79f7195d..aafbe130 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java | |||
@@ -1,9 +1,12 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.adapter.AdapterList; | 8 | import tools.refinery.store.adapter.AdapterUtils; |
4 | import tools.refinery.store.adapter.ModelAdapterBuilder; | 9 | import tools.refinery.store.adapter.ModelAdapterBuilder; |
5 | import tools.refinery.store.adapter.ModelAdapterBuilderFactory; | ||
6 | import tools.refinery.store.adapter.ModelAdapterType; | ||
7 | import tools.refinery.store.map.VersionedMapStore; | 10 | import tools.refinery.store.map.VersionedMapStore; |
8 | import tools.refinery.store.map.VersionedMapStoreImpl; | 11 | import tools.refinery.store.map.VersionedMapStoreImpl; |
9 | import tools.refinery.store.model.ModelStore; | 12 | import tools.refinery.store.model.ModelStore; |
@@ -18,7 +21,7 @@ import java.util.*; | |||
18 | public class ModelStoreBuilderImpl implements ModelStoreBuilder { | 21 | public class ModelStoreBuilderImpl implements ModelStoreBuilder { |
19 | private final Set<AnySymbol> allSymbols = new HashSet<>(); | 22 | private final Set<AnySymbol> allSymbols = new HashSet<>(); |
20 | private final Map<SymbolEquivalenceClass<?>, List<AnySymbol>> equivalenceClasses = new HashMap<>(); | 23 | private final Map<SymbolEquivalenceClass<?>, List<AnySymbol>> equivalenceClasses = new HashMap<>(); |
21 | private final AdapterList<ModelAdapterBuilder> adapters = new AdapterList<>(); | 24 | private final List<ModelAdapterBuilder> adapters = new ArrayList<>(); |
22 | 25 | ||
23 | @Override | 26 | @Override |
24 | public <T> ModelStoreBuilder symbol(Symbol<T> symbol) { | 27 | public <T> ModelStoreBuilder symbol(Symbol<T> symbol) { |
@@ -33,46 +36,25 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder { | |||
33 | } | 36 | } |
34 | 37 | ||
35 | @Override | 38 | @Override |
36 | public <T extends ModelAdapterBuilder> T with(ModelAdapterBuilderFactory<?, ?, T> adapterBuilderFactory) { | 39 | public <T extends ModelAdapterBuilder> ModelStoreBuilder with(T adapterBuilder) { |
37 | return adapters.<T>tryGet(adapterBuilderFactory, adapterBuilderFactory.getModelAdapterBuilderClass()) | 40 | for (var existingAdapter : adapters) { |
38 | .orElseGet(() -> addAdapter(adapterBuilderFactory)); | 41 | if (existingAdapter.getClass().equals(adapterBuilder.getClass())) { |
39 | } | 42 | throw new IllegalArgumentException("%s adapter was already configured for store builder" |
40 | 43 | .formatted(adapterBuilder.getClass().getName())); | |
41 | private <T extends ModelAdapterBuilder> T addAdapter(ModelAdapterBuilderFactory<?, ?, T> adapterBuilderFactory) { | ||
42 | for (var configuredAdapterType : adapters.getAdapterTypes()) { | ||
43 | var intersection = new HashSet<>(adapterBuilderFactory.getSupportedAdapterTypes()); | ||
44 | intersection.retainAll(configuredAdapterType.getSupportedAdapterTypes()); | ||
45 | if (!intersection.isEmpty()) { | ||
46 | if (configuredAdapterType.supports(adapterBuilderFactory)) { | ||
47 | // Impossible to end up here from <code>#with</code>, because we should have returned | ||
48 | // the existing adapter there instead of adding a new one. | ||
49 | throw new IllegalArgumentException( | ||
50 | "Cannot add %s, because it is already provided by configured adapter %s" | ||
51 | .formatted(adapterBuilderFactory, configuredAdapterType)); | ||
52 | } else if (adapterBuilderFactory.supports(configuredAdapterType)) { | ||
53 | throw new IllegalArgumentException( | ||
54 | "Cannot add %s, because it provides already configured adapter %s" | ||
55 | .formatted(adapterBuilderFactory, configuredAdapterType)); | ||
56 | } else { | ||
57 | throw new IllegalArgumentException( | ||
58 | "Cannot add %s, because configured adapter %s already provides %s" | ||
59 | .formatted(adapterBuilderFactory, configuredAdapterType, intersection)); | ||
60 | } | ||
61 | } | 44 | } |
62 | } | 45 | } |
63 | var newAdapter = adapterBuilderFactory.createBuilder(this); | 46 | adapters.add(adapterBuilder); |
64 | adapters.add(adapterBuilderFactory, newAdapter); | 47 | return this; |
65 | return newAdapter; | ||
66 | } | 48 | } |
67 | 49 | ||
68 | @Override | 50 | @Override |
69 | public <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(ModelAdapterType<?, ?, ? extends T> adapterType) { | 51 | public <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(Class<? extends T> adapterType) { |
70 | return adapters.tryGet(adapterType, adapterType.getModelAdapterBuilderClass()); | 52 | return AdapterUtils.tryGetAdapter(adapters, adapterType); |
71 | } | 53 | } |
72 | 54 | ||
73 | @Override | 55 | @Override |
74 | public <T extends ModelAdapterBuilder> T getAdapter(ModelAdapterType<?, ?, T> adapterType) { | 56 | public <T extends ModelAdapterBuilder> T getAdapter(Class<T> adapterType) { |
75 | return adapters.get(adapterType, adapterType.getModelAdapterBuilderClass()); | 57 | return AdapterUtils.getAdapter(adapters, adapterType); |
76 | } | 58 | } |
77 | 59 | ||
78 | @Override | 60 | @Override |
@@ -81,13 +63,13 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder { | |||
81 | for (var entry : equivalenceClasses.entrySet()) { | 63 | for (var entry : equivalenceClasses.entrySet()) { |
82 | createStores(stores, entry.getKey(), entry.getValue()); | 64 | createStores(stores, entry.getKey(), entry.getValue()); |
83 | } | 65 | } |
84 | var modelStore = new ModelStoreImpl(stores, adapters.size()); | ||
85 | for (int i = adapters.size() - 1; i >= 0; i--) { | 66 | for (int i = adapters.size() - 1; i >= 0; i--) { |
86 | adapters.get(i).configure(); | 67 | adapters.get(i).configure(this); |
87 | } | 68 | } |
88 | for (var entry : adapters.withAdapterTypes()) { | 69 | var modelStore = new ModelStoreImpl(stores, adapters.size()); |
89 | var adapter = entry.adapter().createStoreAdapter(modelStore); | 70 | for (var adapterBuilder : adapters) { |
90 | modelStore.addAdapter(entry.adapterType(), adapter); | 71 | var storeAdapter = adapterBuilder.build(modelStore); |
72 | modelStore.addAdapter(storeAdapter); | ||
91 | } | 73 | } |
92 | return modelStore; | 74 | return modelStore; |
93 | } | 75 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java index e8c205e4..60b735e6 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java | |||
@@ -1,8 +1,11 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.adapter.AdapterList; | 8 | import tools.refinery.store.adapter.AdapterUtils; |
4 | import tools.refinery.store.adapter.AnyModelAdapterType; | ||
5 | import tools.refinery.store.adapter.ModelAdapterType; | ||
6 | import tools.refinery.store.adapter.ModelStoreAdapter; | 9 | import tools.refinery.store.adapter.ModelStoreAdapter; |
7 | import tools.refinery.store.map.DiffCursor; | 10 | import tools.refinery.store.map.DiffCursor; |
8 | import tools.refinery.store.map.VersionedMapStore; | 11 | import tools.refinery.store.map.VersionedMapStore; |
@@ -16,11 +19,11 @@ import java.util.*; | |||
16 | 19 | ||
17 | public class ModelStoreImpl implements ModelStore { | 20 | public class ModelStoreImpl implements ModelStore { |
18 | private final Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores; | 21 | private final Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores; |
19 | private final AdapterList<ModelStoreAdapter> adapters; | 22 | private final List<ModelStoreAdapter> adapters; |
20 | 23 | ||
21 | ModelStoreImpl(Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores, int adapterCount) { | 24 | ModelStoreImpl(Map<? extends AnySymbol, ? extends VersionedMapStore<Tuple, ?>> stores, int adapterCount) { |
22 | this.stores = stores; | 25 | this.stores = stores; |
23 | adapters = new AdapterList<>(adapterCount); | 26 | adapters = new ArrayList<>(adapterCount); |
24 | } | 27 | } |
25 | 28 | ||
26 | @Override | 29 | @Override |
@@ -59,9 +62,9 @@ public class ModelStoreImpl implements ModelStore { | |||
59 | } | 62 | } |
60 | 63 | ||
61 | private void adaptModel(ModelImpl model) { | 64 | private void adaptModel(ModelImpl model) { |
62 | for (var entry : adapters.withAdapterTypes()) { | 65 | for (var storeAdapter : adapters) { |
63 | var adapter = entry.adapter().createModelAdapter(model); | 66 | var adapter = storeAdapter.createModelAdapter(model); |
64 | model.addAdapter(entry.adapterType(), adapter); | 67 | model.addAdapter(adapter); |
65 | } | 68 | } |
66 | } | 69 | } |
67 | 70 | ||
@@ -86,16 +89,16 @@ public class ModelStoreImpl implements ModelStore { | |||
86 | } | 89 | } |
87 | 90 | ||
88 | @Override | 91 | @Override |
89 | public <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(ModelAdapterType<?, ? extends T, ?> adapterType) { | 92 | public <T extends ModelStoreAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType) { |
90 | return adapters.tryGet(adapterType, adapterType.getModelStoreAdapterClass()); | 93 | return AdapterUtils.tryGetAdapter(adapters, adapterType); |
91 | } | 94 | } |
92 | 95 | ||
93 | @Override | 96 | @Override |
94 | public <T extends ModelStoreAdapter> T getAdapter(ModelAdapterType<?, T, ?> adapterType) { | 97 | public <T extends ModelStoreAdapter> T getAdapter(Class<T> adapterType) { |
95 | return adapters.get(adapterType, adapterType.getModelStoreAdapterClass()); | 98 | return AdapterUtils.getAdapter(adapters, adapterType); |
96 | } | 99 | } |
97 | 100 | ||
98 | void addAdapter(AnyModelAdapterType adapterType, ModelStoreAdapter adapter) { | 101 | void addAdapter(ModelStoreAdapter adapter) { |
99 | adapters.add(adapterType, adapter); | 102 | adapters.add(adapter); |
100 | } | 103 | } |
101 | } | 104 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java index 5bf1b90d..136f2976 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.representation.Symbol; | 8 | import tools.refinery.store.representation.Symbol; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java index c850d334..404be65f 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.internal; | 6 | package tools.refinery.store.model.internal; |
2 | 7 | ||
3 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java deleted file mode 100644 index 331fa294..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | package tools.refinery.store.partial; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterBuilderFactory; | ||
4 | import tools.refinery.store.model.ModelStoreBuilder; | ||
5 | import tools.refinery.store.partial.internal.PartialInterpretationBuilderImpl; | ||
6 | |||
7 | public final class PartialInterpretation extends ModelAdapterBuilderFactory<PartialInterpretationAdapter, | ||
8 | PartialInterpretationStoreAdapter, PartialInterpretationBuilder> { | ||
9 | public static final PartialInterpretation ADAPTER = new PartialInterpretation(); | ||
10 | |||
11 | private PartialInterpretation() { | ||
12 | super(PartialInterpretationAdapter.class, PartialInterpretationStoreAdapter.class, PartialInterpretationBuilder.class); | ||
13 | } | ||
14 | |||
15 | @Override | ||
16 | public PartialInterpretationBuilder createBuilder(ModelStoreBuilder storeBuilder) { | ||
17 | return new PartialInterpretationBuilderImpl(storeBuilder); | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java deleted file mode 100644 index 2c83a200..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | package tools.refinery.store.partial; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapter; | ||
4 | |||
5 | public interface PartialInterpretationAdapter extends ModelAdapter { | ||
6 | @Override | ||
7 | PartialInterpretationStoreAdapter getStoreAdapter(); | ||
8 | } | ||
9 | |||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java deleted file mode 100644 index 0ec13836..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | package tools.refinery.store.partial; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
4 | import tools.refinery.store.model.ModelStore; | ||
5 | |||
6 | public interface PartialInterpretationBuilder extends ModelAdapterBuilder { | ||
7 | @Override | ||
8 | PartialInterpretationStoreAdapter createStoreAdapter(ModelStore store); | ||
9 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java deleted file mode 100644 index d4eb770d..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | package tools.refinery.store.partial; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
4 | import tools.refinery.store.model.Model; | ||
5 | |||
6 | public interface PartialInterpretationStoreAdapter extends ModelStoreAdapter { | ||
7 | @Override | ||
8 | PartialInterpretationAdapter createModelAdapter(Model model); | ||
9 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java deleted file mode 100644 index 4b3977c0..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | package tools.refinery.store.partial.internal; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.partial.PartialInterpretationAdapter; | ||
5 | |||
6 | public class PartialInterpretationAdapterImpl implements PartialInterpretationAdapter { | ||
7 | private final Model model; | ||
8 | private final PartialInterpretationStoreAdapterImpl storeAdapter; | ||
9 | |||
10 | PartialInterpretationAdapterImpl(Model model, PartialInterpretationStoreAdapterImpl storeAdapter) { | ||
11 | this.model = model; | ||
12 | this.storeAdapter = storeAdapter; | ||
13 | } | ||
14 | |||
15 | @Override | ||
16 | public Model getModel() { | ||
17 | return model; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public PartialInterpretationStoreAdapterImpl getStoreAdapter() { | ||
22 | return storeAdapter; | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java deleted file mode 100644 index 4609dc32..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | package tools.refinery.store.partial.internal; | ||
2 | |||
3 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | ||
4 | import tools.refinery.store.model.ModelStore; | ||
5 | import tools.refinery.store.model.ModelStoreBuilder; | ||
6 | import tools.refinery.store.partial.PartialInterpretationBuilder; | ||
7 | |||
8 | public class PartialInterpretationBuilderImpl extends AbstractModelAdapterBuilder implements PartialInterpretationBuilder { | ||
9 | public PartialInterpretationBuilderImpl(ModelStoreBuilder storeBuilder) { | ||
10 | super(storeBuilder); | ||
11 | } | ||
12 | |||
13 | @Override | ||
14 | public PartialInterpretationStoreAdapterImpl createStoreAdapter(ModelStore store) { | ||
15 | return null; | ||
16 | } | ||
17 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java deleted file mode 100644 index 970b802b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java +++ /dev/null | |||
@@ -1,23 +0,0 @@ | |||
1 | package tools.refinery.store.partial.internal; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.model.ModelStore; | ||
5 | import tools.refinery.store.partial.PartialInterpretationStoreAdapter; | ||
6 | |||
7 | public class PartialInterpretationStoreAdapterImpl implements PartialInterpretationStoreAdapter { | ||
8 | private final ModelStore store; | ||
9 | |||
10 | PartialInterpretationStoreAdapterImpl(ModelStore store) { | ||
11 | this.store = store; | ||
12 | } | ||
13 | |||
14 | @Override | ||
15 | public ModelStore getStore() { | ||
16 | return store; | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | public PartialInterpretationAdapterImpl createModelAdapter(Model model) { | ||
21 | return new PartialInterpretationAdapterImpl(model, this); | ||
22 | } | ||
23 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java b/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java deleted file mode 100644 index 95c5d787..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java +++ /dev/null | |||
@@ -1,169 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.atom.DNFAtom; | ||
4 | |||
5 | import java.util.*; | ||
6 | |||
7 | public final class DNF implements RelationLike { | ||
8 | private final String name; | ||
9 | |||
10 | private final String uniqueName; | ||
11 | |||
12 | private final List<Variable> parameters; | ||
13 | |||
14 | private final List<FunctionalDependency<Variable>> functionalDependencies; | ||
15 | |||
16 | private final List<DNFAnd> clauses; | ||
17 | |||
18 | private DNF(String name, List<Variable> parameters, List<FunctionalDependency<Variable>> functionalDependencies, | ||
19 | List<DNFAnd> clauses) { | ||
20 | validateFunctionalDependencies(parameters, functionalDependencies); | ||
21 | this.name = name; | ||
22 | this.uniqueName = DNFUtils.generateUniqueName(name); | ||
23 | this.parameters = parameters; | ||
24 | this.functionalDependencies = functionalDependencies; | ||
25 | this.clauses = clauses; | ||
26 | } | ||
27 | |||
28 | private static void validateFunctionalDependencies( | ||
29 | Collection<Variable> parameters, Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
30 | var parameterSet = new HashSet<>(parameters); | ||
31 | for (var functionalDependency : functionalDependencies) { | ||
32 | validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency); | ||
33 | validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | private static void validateParameters(Collection<Variable> parameters, Set<Variable> parameterSet, | ||
38 | Collection<Variable> toValidate, | ||
39 | FunctionalDependency<Variable> functionalDependency) { | ||
40 | for (var variable : toValidate) { | ||
41 | if (!parameterSet.contains(variable)) { | ||
42 | throw new IllegalArgumentException( | ||
43 | "Variable %s of functional dependency %s does not appear in the parameter list %s" | ||
44 | .formatted(variable, functionalDependency, parameters)); | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public String name() { | ||
51 | return name; | ||
52 | } | ||
53 | |||
54 | public String getUniqueName() { | ||
55 | return uniqueName; | ||
56 | } | ||
57 | |||
58 | public List<Variable> getParameters() { | ||
59 | return parameters; | ||
60 | } | ||
61 | |||
62 | public List<FunctionalDependency<Variable>> getFunctionalDependencies() { | ||
63 | return functionalDependencies; | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public int arity() { | ||
68 | return parameters.size(); | ||
69 | } | ||
70 | |||
71 | public List<DNFAnd> getClauses() { | ||
72 | return clauses; | ||
73 | } | ||
74 | |||
75 | public static Builder builder() { | ||
76 | return builder(null); | ||
77 | } | ||
78 | |||
79 | public static Builder builder(String name) { | ||
80 | return new Builder(name); | ||
81 | } | ||
82 | |||
83 | @SuppressWarnings("UnusedReturnValue") | ||
84 | public static class Builder { | ||
85 | private final String name; | ||
86 | |||
87 | private final List<Variable> parameters = new ArrayList<>(); | ||
88 | |||
89 | private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>(); | ||
90 | |||
91 | private final List<List<DNFAtom>> clauses = new ArrayList<>(); | ||
92 | |||
93 | private Builder(String name) { | ||
94 | this.name = name; | ||
95 | } | ||
96 | |||
97 | public Builder parameter(Variable variable) { | ||
98 | parameters.add(variable); | ||
99 | return this; | ||
100 | } | ||
101 | |||
102 | public Builder parameters(Variable... variables) { | ||
103 | return parameters(List.of(variables)); | ||
104 | } | ||
105 | |||
106 | public Builder parameters(Collection<Variable> variables) { | ||
107 | parameters.addAll(variables); | ||
108 | return this; | ||
109 | } | ||
110 | |||
111 | public Builder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) { | ||
112 | this.functionalDependencies.addAll(functionalDependencies); | ||
113 | return this; | ||
114 | } | ||
115 | |||
116 | public Builder functionalDependency(FunctionalDependency<Variable> functionalDependency) { | ||
117 | functionalDependencies.add(functionalDependency); | ||
118 | return this; | ||
119 | } | ||
120 | |||
121 | public Builder functionalDependency(Set<Variable> forEach, Set<Variable> unique) { | ||
122 | return functionalDependency(new FunctionalDependency<>(forEach, unique)); | ||
123 | } | ||
124 | |||
125 | public Builder clause(DNFAtom... atoms) { | ||
126 | clauses.add(List.of(atoms)); | ||
127 | return this; | ||
128 | } | ||
129 | |||
130 | public Builder clause(Collection<DNFAtom> atoms) { | ||
131 | clauses.add(List.copyOf(atoms)); | ||
132 | return this; | ||
133 | } | ||
134 | |||
135 | public Builder clause(DNFAnd clause) { | ||
136 | return clause(clause.constraints()); | ||
137 | } | ||
138 | |||
139 | public Builder clauses(DNFAnd... clauses) { | ||
140 | for (var clause : clauses) { | ||
141 | this.clause(clause); | ||
142 | } | ||
143 | return this; | ||
144 | } | ||
145 | |||
146 | public Builder clauses(Collection<DNFAnd> clauses) { | ||
147 | for (var clause : clauses) { | ||
148 | this.clause(clause); | ||
149 | } | ||
150 | return this; | ||
151 | } | ||
152 | |||
153 | public DNF build() { | ||
154 | var postProcessedClauses = new ArrayList<DNFAnd>(); | ||
155 | for (var constraints : clauses) { | ||
156 | var variables = new HashSet<Variable>(); | ||
157 | for (var constraint : constraints) { | ||
158 | constraint.collectAllVariables(variables); | ||
159 | } | ||
160 | parameters.forEach(variables::remove); | ||
161 | postProcessedClauses.add(new DNFAnd(Collections.unmodifiableSet(variables), | ||
162 | Collections.unmodifiableList(constraints))); | ||
163 | } | ||
164 | return new DNF(name, Collections.unmodifiableList(parameters), | ||
165 | Collections.unmodifiableList(functionalDependencies), | ||
166 | Collections.unmodifiableList(postProcessedClauses)); | ||
167 | } | ||
168 | } | ||
169 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java b/subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java deleted file mode 100644 index 8c3bf05d..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.query.atom.DNFAtom; | ||
4 | |||
5 | import java.util.List; | ||
6 | import java.util.Set; | ||
7 | |||
8 | public record DNFAnd(Set<Variable> quantifiedVariables, List<DNFAtom> constraints) { | ||
9 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java deleted file mode 100644 index 6a1aeabb..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterType; | ||
4 | |||
5 | public final class ModelQuery extends ModelAdapterType<ModelQueryAdapter, ModelQueryStoreAdapter, ModelQueryBuilder> { | ||
6 | public static final ModelQuery ADAPTER = new ModelQuery(); | ||
7 | |||
8 | private ModelQuery() { | ||
9 | super(ModelQueryAdapter.class, ModelQueryStoreAdapter.class, ModelQueryBuilder.class); | ||
10 | } | ||
11 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java deleted file mode 100644 index 7449e39b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapter; | ||
4 | |||
5 | public interface ModelQueryAdapter extends ModelAdapter { | ||
6 | ModelQueryStoreAdapter getStoreAdapter(); | ||
7 | |||
8 | ResultSet getResultSet(DNF query); | ||
9 | |||
10 | boolean hasPendingChanges(); | ||
11 | |||
12 | void flushChanges(); | ||
13 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java deleted file mode 100644 index 4364d844..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java +++ /dev/null | |||
@@ -1,23 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
4 | import tools.refinery.store.model.ModelStore; | ||
5 | |||
6 | import java.util.Collection; | ||
7 | import java.util.List; | ||
8 | |||
9 | public interface ModelQueryBuilder extends ModelAdapterBuilder { | ||
10 | default ModelQueryBuilder queries(DNF... queries) { | ||
11 | return queries(List.of(queries)); | ||
12 | } | ||
13 | |||
14 | default ModelQueryBuilder queries(Collection<DNF> queries) { | ||
15 | queries.forEach(this::query); | ||
16 | return this; | ||
17 | } | ||
18 | |||
19 | ModelQueryBuilder query(DNF query); | ||
20 | |||
21 | @Override | ||
22 | ModelQueryStoreAdapter createStoreAdapter(ModelStore store); | ||
23 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java deleted file mode 100644 index ef5a4587..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
4 | import tools.refinery.store.model.Model; | ||
5 | import tools.refinery.store.query.view.AnyRelationView; | ||
6 | |||
7 | import java.util.Collection; | ||
8 | |||
9 | public interface ModelQueryStoreAdapter extends ModelStoreAdapter { | ||
10 | Collection<AnyRelationView> getRelationViews(); | ||
11 | |||
12 | Collection<DNF> getQueries(); | ||
13 | |||
14 | @Override | ||
15 | ModelQueryAdapter createModelAdapter(Model model); | ||
16 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java b/subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java deleted file mode 100644 index 8c784d8b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | public interface RelationLike { | ||
4 | String name(); | ||
5 | |||
6 | int arity(); | ||
7 | |||
8 | default boolean invalidIndex(int i) { | ||
9 | return i < 0 || i >= arity(); | ||
10 | } | ||
11 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java deleted file mode 100644 index 3542e252..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import tools.refinery.store.tuple.Tuple; | ||
4 | import tools.refinery.store.tuple.TupleLike; | ||
5 | |||
6 | import java.util.Optional; | ||
7 | import java.util.stream.Stream; | ||
8 | |||
9 | public interface ResultSet { | ||
10 | boolean hasResult(); | ||
11 | |||
12 | boolean hasResult(Tuple parameters); | ||
13 | |||
14 | Optional<TupleLike> oneResult(); | ||
15 | |||
16 | Optional<TupleLike> oneResult(Tuple parameters); | ||
17 | |||
18 | Stream<TupleLike> allResults(); | ||
19 | |||
20 | Stream<TupleLike> allResults(Tuple parameters); | ||
21 | |||
22 | int countResults(); | ||
23 | |||
24 | int countResults(Tuple parameters); | ||
25 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/Variable.java b/subprojects/store/src/main/java/tools/refinery/store/query/Variable.java deleted file mode 100644 index 3632f3c5..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/Variable.java +++ /dev/null | |||
@@ -1,43 +0,0 @@ | |||
1 | package tools.refinery.store.query; | ||
2 | |||
3 | import java.util.Objects; | ||
4 | |||
5 | public class Variable { | ||
6 | private final String name; | ||
7 | private final String uniqueName; | ||
8 | |||
9 | public Variable() { | ||
10 | this(null); | ||
11 | } | ||
12 | |||
13 | public Variable(String name) { | ||
14 | super(); | ||
15 | this.name = name; | ||
16 | this.uniqueName = DNFUtils.generateUniqueName(name); | ||
17 | |||
18 | } | ||
19 | public String getName() { | ||
20 | return name; | ||
21 | } | ||
22 | |||
23 | public String getUniqueName() { | ||
24 | return uniqueName; | ||
25 | } | ||
26 | |||
27 | public boolean isNamed() { | ||
28 | return name != null; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public boolean equals(Object o) { | ||
33 | if (this == o) return true; | ||
34 | if (o == null || getClass() != o.getClass()) return false; | ||
35 | Variable variable = (Variable) o; | ||
36 | return Objects.equals(uniqueName, variable.uniqueName); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public int hashCode() { | ||
41 | return Objects.hash(uniqueName); | ||
42 | } | ||
43 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java deleted file mode 100644 index 47121870..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java +++ /dev/null | |||
@@ -1,80 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | import tools.refinery.store.query.RelationLike; | ||
5 | |||
6 | import java.util.List; | ||
7 | import java.util.Objects; | ||
8 | import java.util.Set; | ||
9 | |||
10 | public abstract class CallAtom<T extends RelationLike> implements DNFAtom { | ||
11 | private final CallPolarity polarity; | ||
12 | private final T target; | ||
13 | private final List<Variable> substitution; | ||
14 | |||
15 | protected CallAtom(CallPolarity polarity, T target, List<Variable> substitution) { | ||
16 | if (substitution.size() != target.arity()) { | ||
17 | throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), | ||
18 | target.arity(), substitution.size())); | ||
19 | } | ||
20 | if (polarity.isTransitive() && target.arity() != 2) { | ||
21 | throw new IllegalArgumentException("Transitive closures can only take binary relations"); | ||
22 | } | ||
23 | this.polarity = polarity; | ||
24 | this.target = target; | ||
25 | this.substitution = substitution; | ||
26 | } | ||
27 | |||
28 | protected CallAtom(CallPolarity polarity, T target, Variable... substitution) { | ||
29 | this(polarity, target, List.of(substitution)); | ||
30 | } | ||
31 | |||
32 | protected CallAtom(boolean positive, T target, List<Variable> substitution) { | ||
33 | this(CallPolarity.fromBoolean(positive), target, substitution); | ||
34 | } | ||
35 | |||
36 | protected CallAtom(boolean positive, T target, Variable... substitution) { | ||
37 | this(positive, target, List.of(substitution)); | ||
38 | } | ||
39 | |||
40 | protected CallAtom(T target, List<Variable> substitution) { | ||
41 | this(true, target, substitution); | ||
42 | } | ||
43 | |||
44 | protected CallAtom(T target, Variable... substitution) { | ||
45 | this(target, List.of(substitution)); | ||
46 | } | ||
47 | |||
48 | public CallPolarity getPolarity() { | ||
49 | return polarity; | ||
50 | } | ||
51 | |||
52 | public T getTarget() { | ||
53 | return target; | ||
54 | } | ||
55 | |||
56 | public List<Variable> getSubstitution() { | ||
57 | return substitution; | ||
58 | } | ||
59 | |||
60 | @Override | ||
61 | public void collectAllVariables(Set<Variable> variables) { | ||
62 | if (polarity.isPositive()) { | ||
63 | variables.addAll(substitution); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public boolean equals(Object o) { | ||
69 | if (this == o) return true; | ||
70 | if (o == null || getClass() != o.getClass()) return false; | ||
71 | CallAtom<?> callAtom = (CallAtom<?>) o; | ||
72 | return polarity == callAtom.polarity && Objects.equals(target, callAtom.target) && Objects.equals(substitution | ||
73 | , callAtom.substitution); | ||
74 | } | ||
75 | |||
76 | @Override | ||
77 | public int hashCode() { | ||
78 | return Objects.hash(polarity, target, substitution); | ||
79 | } | ||
80 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java deleted file mode 100644 index 13dae7d0..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | |||
5 | import java.util.Set; | ||
6 | |||
7 | public record ConstantAtom(Variable variable, int nodeId) implements DNFAtom { | ||
8 | @Override | ||
9 | public void collectAllVariables(Set<Variable> variables) { | ||
10 | variables.add(variable); | ||
11 | } | ||
12 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java deleted file mode 100644 index ebf71236..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | |||
5 | import java.util.Set; | ||
6 | |||
7 | public interface DNFAtom { | ||
8 | void collectAllVariables(Set<Variable> variables); | ||
9 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java deleted file mode 100644 index 3b4f5cd1..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import tools.refinery.store.query.DNF; | ||
4 | import tools.refinery.store.query.Variable; | ||
5 | |||
6 | import java.util.List; | ||
7 | |||
8 | public class DNFCallAtom extends CallAtom<DNF> { | ||
9 | public DNFCallAtom(CallPolarity polarity, DNF target, List<Variable> substitution) { | ||
10 | super(polarity, target, substitution); | ||
11 | } | ||
12 | |||
13 | public DNFCallAtom(CallPolarity polarity, DNF target, Variable... substitution) { | ||
14 | super(polarity, target, substitution); | ||
15 | } | ||
16 | |||
17 | public DNFCallAtom(boolean positive, DNF target, List<Variable> substitution) { | ||
18 | super(positive, target, substitution); | ||
19 | } | ||
20 | |||
21 | public DNFCallAtom(boolean positive, DNF target, Variable... substitution) { | ||
22 | super(positive, target, substitution); | ||
23 | } | ||
24 | |||
25 | public DNFCallAtom(DNF target, List<Variable> substitution) { | ||
26 | super(target, substitution); | ||
27 | } | ||
28 | |||
29 | public DNFCallAtom(DNF target, Variable... substitution) { | ||
30 | super(target, substitution); | ||
31 | } | ||
32 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java deleted file mode 100644 index b1b3a6f7..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | |||
5 | import java.util.Set; | ||
6 | |||
7 | public record EquivalenceAtom(boolean positive, Variable left, Variable right) implements DNFAtom { | ||
8 | public EquivalenceAtom(Variable left, Variable right) { | ||
9 | this(true, left, right); | ||
10 | } | ||
11 | |||
12 | @Override | ||
13 | public void collectAllVariables(Set<Variable> variables) { | ||
14 | variables.add(left); | ||
15 | variables.add(right); | ||
16 | } | ||
17 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java deleted file mode 100644 index e389f563..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import java.util.Locale; | ||
4 | |||
5 | public enum Modality { | ||
6 | MUST, | ||
7 | MAY, | ||
8 | CURRENT; | ||
9 | |||
10 | public Modality negate() { | ||
11 | return switch(this) { | ||
12 | case MUST -> MAY; | ||
13 | case MAY -> MUST; | ||
14 | case CURRENT -> CURRENT; | ||
15 | }; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public String toString() { | ||
20 | return name().toLowerCase(Locale.ROOT); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java deleted file mode 100644 index a2b176c4..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | package tools.refinery.store.query.atom; | ||
2 | |||
3 | import tools.refinery.store.query.Variable; | ||
4 | import tools.refinery.store.query.view.AnyRelationView; | ||
5 | |||
6 | import java.util.List; | ||
7 | |||
8 | public final class RelationViewAtom extends CallAtom<AnyRelationView> { | ||
9 | public RelationViewAtom(CallPolarity polarity, AnyRelationView target, List<Variable> substitution) { | ||
10 | super(polarity, target, substitution); | ||
11 | } | ||
12 | |||
13 | public RelationViewAtom(CallPolarity polarity, AnyRelationView target, Variable... substitution) { | ||
14 | super(polarity, target, substitution); | ||
15 | } | ||
16 | |||
17 | public RelationViewAtom(boolean positive, AnyRelationView target, List<Variable> substitution) { | ||
18 | super(positive, target, substitution); | ||
19 | } | ||
20 | |||
21 | public RelationViewAtom(boolean positive, AnyRelationView target, Variable... substitution) { | ||
22 | super(positive, target, substitution); | ||
23 | } | ||
24 | |||
25 | public RelationViewAtom(AnyRelationView target, List<Variable> substitution) { | ||
26 | super(target, substitution); | ||
27 | } | ||
28 | |||
29 | public RelationViewAtom(AnyRelationView target, Variable... substitution) { | ||
30 | super(target, substitution); | ||
31 | } | ||
32 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java deleted file mode 100644 index 328cde3a..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.query.FunctionalDependency; | ||
5 | import tools.refinery.store.representation.AnySymbol; | ||
6 | import tools.refinery.store.query.RelationLike; | ||
7 | |||
8 | import java.util.Set; | ||
9 | |||
10 | public sealed interface AnyRelationView extends RelationLike permits RelationView { | ||
11 | AnySymbol getSymbol(); | ||
12 | |||
13 | default Set<FunctionalDependency<Integer>> getFunctionalDependencies() { | ||
14 | return Set.of(); | ||
15 | } | ||
16 | |||
17 | default Set<RelationViewImplication> getImpliedRelationViews() { | ||
18 | return Set.of(); | ||
19 | } | ||
20 | |||
21 | boolean get(Model model, Object[] tuple); | ||
22 | |||
23 | Iterable<Object[]> getAll(Model model); | ||
24 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java deleted file mode 100644 index 64c601bb..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java +++ /dev/null | |||
@@ -1,49 +0,0 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.tuple.Tuple; | ||
4 | import tools.refinery.store.representation.Symbol; | ||
5 | |||
6 | import java.util.Objects; | ||
7 | import java.util.function.BiPredicate; | ||
8 | import java.util.function.Predicate; | ||
9 | |||
10 | public class FilteredRelationView<T> extends TuplePreservingRelationView<T> { | ||
11 | private final BiPredicate<Tuple, T> predicate; | ||
12 | |||
13 | public FilteredRelationView(Symbol<T> symbol, String name, BiPredicate<Tuple, T> predicate) { | ||
14 | super(symbol, name); | ||
15 | this.predicate = predicate; | ||
16 | } | ||
17 | |||
18 | public FilteredRelationView(Symbol<T> symbol, BiPredicate<Tuple, T> predicate) { | ||
19 | super(symbol); | ||
20 | this.predicate = predicate; | ||
21 | } | ||
22 | |||
23 | public FilteredRelationView(Symbol<T> symbol, String name, Predicate<T> predicate) { | ||
24 | this(symbol, name, (k, v) -> predicate.test(v)); | ||
25 | } | ||
26 | |||
27 | public FilteredRelationView(Symbol<T> symbol, Predicate<T> predicate) { | ||
28 | this(symbol, (k, v) -> predicate.test(v)); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public boolean filter(Tuple key, T value) { | ||
33 | return this.predicate.test(key, value); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public boolean equals(Object o) { | ||
38 | if (this == o) return true; | ||
39 | if (o == null || getClass() != o.getClass()) return false; | ||
40 | if (!super.equals(o)) return false; | ||
41 | FilteredRelationView<?> that = (FilteredRelationView<?>) o; | ||
42 | return Objects.equals(predicate, that.predicate); | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public int hashCode() { | ||
47 | return Objects.hash(super.hashCode(), predicate); | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java deleted file mode 100644 index 3d278a8b..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java +++ /dev/null | |||
@@ -1,71 +0,0 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.query.FunctionalDependency; | ||
5 | import tools.refinery.store.representation.Symbol; | ||
6 | import tools.refinery.store.tuple.Tuple; | ||
7 | import tools.refinery.store.tuple.Tuple1; | ||
8 | |||
9 | import java.util.Set; | ||
10 | import java.util.stream.Collectors; | ||
11 | import java.util.stream.IntStream; | ||
12 | |||
13 | public final class FunctionalRelationView<T> extends RelationView<T> { | ||
14 | public FunctionalRelationView(Symbol<T> symbol, String name) { | ||
15 | super(symbol, name); | ||
16 | } | ||
17 | |||
18 | public FunctionalRelationView(Symbol<T> symbol) { | ||
19 | super(symbol); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Set<FunctionalDependency<Integer>> getFunctionalDependencies() { | ||
24 | var arity = getSymbol().arity(); | ||
25 | var forEach = IntStream.range(0, arity).boxed().collect(Collectors.toUnmodifiableSet()); | ||
26 | var unique = Set.of(arity); | ||
27 | return Set.of(new FunctionalDependency<>(forEach, unique)); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public Set<RelationViewImplication> getImpliedRelationViews() { | ||
32 | var symbol = getSymbol(); | ||
33 | var impliedIndices = IntStream.range(0, symbol.arity()).boxed().toList(); | ||
34 | var keyOnlyRelationView = new KeyOnlyRelationView<>(symbol); | ||
35 | return Set.of(new RelationViewImplication(this, keyOnlyRelationView, impliedIndices)); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public boolean filter(Tuple key, T value) { | ||
40 | return true; | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public Object[] forwardMap(Tuple key, T value) { | ||
45 | int size = key.getSize(); | ||
46 | Object[] result = new Object[size + 1]; | ||
47 | for (int i = 0; i < size; i++) { | ||
48 | result[i] = Tuple.of(key.get(i)); | ||
49 | } | ||
50 | result[key.getSize()] = value; | ||
51 | return result; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public boolean get(Model model, Object[] tuple) { | ||
56 | int[] content = new int[tuple.length - 1]; | ||
57 | for (int i = 0; i < tuple.length - 1; i++) { | ||
58 | content[i] = ((Tuple1) tuple[i]).value0(); | ||
59 | } | ||
60 | Tuple key = Tuple.of(content); | ||
61 | @SuppressWarnings("unchecked") | ||
62 | T valueInTuple = (T) tuple[tuple.length - 1]; | ||
63 | T valueInMap = model.getInterpretation(getSymbol()).get(key); | ||
64 | return valueInTuple.equals(valueInMap); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public int arity() { | ||
69 | return getSymbol().arity() + 1; | ||
70 | } | ||
71 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java deleted file mode 100644 index bbec1e73..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java +++ /dev/null | |||
@@ -1,62 +0,0 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.map.CursorAsIterator; | ||
4 | import tools.refinery.store.model.Model; | ||
5 | import tools.refinery.store.representation.Symbol; | ||
6 | import tools.refinery.store.tuple.Tuple; | ||
7 | |||
8 | import java.util.Objects; | ||
9 | import java.util.UUID; | ||
10 | |||
11 | /** | ||
12 | * Represents a view of a {@link Symbol} that can be queried. | ||
13 | * | ||
14 | * @param <T> | ||
15 | * @author Oszkar Semerath | ||
16 | */ | ||
17 | public abstract non-sealed class RelationView<T> implements AnyRelationView { | ||
18 | private final Symbol<T> symbol; | ||
19 | |||
20 | private final String name; | ||
21 | |||
22 | protected RelationView(Symbol<T> symbol, String name) { | ||
23 | this.symbol = symbol; | ||
24 | this.name = name; | ||
25 | } | ||
26 | |||
27 | protected RelationView(Symbol<T> representation) { | ||
28 | this(representation, UUID.randomUUID().toString()); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Symbol<T> getSymbol() { | ||
33 | return symbol; | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public String name() { | ||
38 | return symbol.name() + "#" + name; | ||
39 | } | ||
40 | |||
41 | public abstract boolean filter(Tuple key, T value); | ||
42 | |||
43 | public abstract Object[] forwardMap(Tuple key, T value); | ||
44 | |||
45 | @Override | ||
46 | public Iterable<Object[]> getAll(Model model) { | ||
47 | return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter)); | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public boolean equals(Object o) { | ||
52 | if (this == o) return true; | ||
53 | if (o == null || getClass() != o.getClass()) return false; | ||
54 | RelationView<?> that = (RelationView<?>) o; | ||
55 | return Objects.equals(symbol, that.symbol) && Objects.equals(name, that.name); | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public int hashCode() { | ||
60 | return Objects.hash(symbol, name); | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java deleted file mode 100644 index 2ba1fcc4..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import java.util.List; | ||
4 | |||
5 | public record RelationViewImplication(AnyRelationView implyingRelationView, AnyRelationView impliedRelationView, | ||
6 | List<Integer> impliedIndices) { | ||
7 | public RelationViewImplication { | ||
8 | if (impliedIndices.size() != impliedRelationView.arity()) { | ||
9 | throw new IllegalArgumentException("Expected %d implied indices for %s, but %d are provided" | ||
10 | .formatted(impliedRelationView.arity(), impliedRelationView, impliedIndices.size())); | ||
11 | } | ||
12 | for (var index : impliedIndices) { | ||
13 | if (impliedRelationView.invalidIndex(index)) { | ||
14 | throw new IllegalArgumentException("%d is not a valid index for %s".formatted(index, | ||
15 | implyingRelationView)); | ||
16 | } | ||
17 | } | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java b/subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java deleted file mode 100644 index 8cc4986e..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java +++ /dev/null | |||
@@ -1,44 +0,0 @@ | |||
1 | package tools.refinery.store.query.view; | ||
2 | |||
3 | import tools.refinery.store.model.Model; | ||
4 | import tools.refinery.store.tuple.Tuple; | ||
5 | import tools.refinery.store.tuple.Tuple1; | ||
6 | import tools.refinery.store.representation.Symbol; | ||
7 | |||
8 | public abstract class TuplePreservingRelationView<T> extends RelationView<T> { | ||
9 | protected TuplePreservingRelationView(Symbol<T> symbol, String name) { | ||
10 | super(symbol, name); | ||
11 | } | ||
12 | |||
13 | protected TuplePreservingRelationView(Symbol<T> symbol) { | ||
14 | super(symbol); | ||
15 | } | ||
16 | |||
17 | public Object[] forwardMap(Tuple key) { | ||
18 | Object[] result = new Object[key.getSize()]; | ||
19 | for (int i = 0; i < key.getSize(); i++) { | ||
20 | result[i] = Tuple.of(key.get(i)); | ||
21 | } | ||
22 | return result; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public Object[] forwardMap(Tuple key, T value) { | ||
27 | return forwardMap(key); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public boolean get(Model model, Object[] tuple) { | ||
32 | int[] content = new int[tuple.length]; | ||
33 | for (int i = 0; i < tuple.length; i++) { | ||
34 | content[i] = ((Tuple1) tuple[i]).value0(); | ||
35 | } | ||
36 | Tuple key = Tuple.of(content); | ||
37 | T value = model.getInterpretation(getSymbol()).get(key); | ||
38 | return filter(key, value); | ||
39 | } | ||
40 | |||
41 | public int arity() { | ||
42 | return this.getSymbol().arity(); | ||
43 | } | ||
44 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java new file mode 100644 index 00000000..52c740e8 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.representation; | ||
7 | |||
8 | import java.util.Optional; | ||
9 | |||
10 | public non-sealed interface AbstractDomain<A, C> extends AnyAbstractDomain { | ||
11 | @Override | ||
12 | Class<A> abstractType(); | ||
13 | |||
14 | @Override | ||
15 | Class<C> concreteType(); | ||
16 | |||
17 | A toAbstract(C concreteValue); | ||
18 | |||
19 | Optional<C> toConcrete(A abstractValue); | ||
20 | |||
21 | default boolean isConcrete(A abstractValue) { | ||
22 | return toConcrete(abstractValue).isPresent(); | ||
23 | } | ||
24 | |||
25 | boolean isRefinement(A originalValue, A refinedValue); | ||
26 | |||
27 | A commonRefinement(A leftValue, A rightValue); | ||
28 | |||
29 | A commonAncestor(A leftValue, A rightValue); | ||
30 | |||
31 | A unknown(); | ||
32 | |||
33 | boolean isError(A abstractValue); | ||
34 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java new file mode 100644 index 00000000..c354fab7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java | |||
@@ -0,0 +1,12 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.representation; | ||
7 | |||
8 | public sealed interface AnyAbstractDomain permits AbstractDomain { | ||
9 | Class<?> abstractType(); | ||
10 | |||
11 | Class<?> concreteType(); | ||
12 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java b/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java index 20b9eead..b2377905 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation; | 6 | package tools.refinery.store.representation; |
2 | 7 | ||
3 | public sealed interface AnySymbol permits Symbol { | 8 | public sealed interface AnySymbol permits Symbol { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java b/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java index 85ea15f4..cc748180 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java | |||
@@ -1,25 +1,25 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation; | 6 | package tools.refinery.store.representation; |
2 | 7 | ||
3 | import java.util.Objects; | ||
4 | |||
5 | public record Symbol<T>(String name, int arity, Class<T> valueType, T defaultValue) implements AnySymbol { | 8 | public record Symbol<T>(String name, int arity, Class<T> valueType, T defaultValue) implements AnySymbol { |
6 | public boolean isDefaultValue(T value) { | 9 | @Override |
7 | return Objects.equals(defaultValue, value); | 10 | public String toString() { |
11 | return "%s/%d".formatted(name, arity); | ||
8 | } | 12 | } |
9 | 13 | ||
10 | @Override | 14 | public static Symbol<Boolean> of(String name, int arity) { |
11 | public boolean equals(Object o) { | 15 | return of(name, arity, Boolean.class, false); |
12 | return this == o; | ||
13 | } | 16 | } |
14 | 17 | ||
15 | @Override | 18 | public static <T> Symbol<T> of(String name, int arity, Class<T> valueType) { |
16 | public int hashCode() { | 19 | return of(name, arity, valueType, null); |
17 | // Compare by identity to make hash table lookups more efficient. | ||
18 | return System.identityHashCode(this); | ||
19 | } | 20 | } |
20 | 21 | ||
21 | @Override | 22 | public static <T> Symbol<T> of(String name, int arity, Class<T> valueType, T defaultValue) { |
22 | public String toString() { | 23 | return new Symbol<>(name, arity, valueType, defaultValue); |
23 | return "%s/%d".formatted(name, arity); | ||
24 | } | 24 | } |
25 | } | 25 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java index b7893fd3..40baf9a5 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation; | 6 | package tools.refinery.store.representation; |
2 | 7 | ||
3 | public enum TruthValue { | 8 | public enum TruthValue { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java new file mode 100644 index 00000000..89f8dd19 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java | |||
@@ -0,0 +1,65 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.representation; | ||
7 | |||
8 | import java.util.Optional; | ||
9 | |||
10 | public final class TruthValueDomain implements AbstractDomain<TruthValue, Boolean> { | ||
11 | public static final TruthValueDomain INSTANCE = new TruthValueDomain(); | ||
12 | |||
13 | private TruthValueDomain() { | ||
14 | } | ||
15 | |||
16 | @Override | ||
17 | public Class<TruthValue> abstractType() { | ||
18 | return null; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public Class<Boolean> concreteType() { | ||
23 | return null; | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public TruthValue toAbstract(Boolean concreteValue) { | ||
28 | return null; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public Optional<Boolean> toConcrete(TruthValue abstractValue) { | ||
33 | return Optional.empty(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public boolean isConcrete(TruthValue abstractValue) { | ||
38 | return AbstractDomain.super.isConcrete(abstractValue); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public boolean isRefinement(TruthValue originalValue, TruthValue refinedValue) { | ||
43 | return false; | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public TruthValue commonRefinement(TruthValue leftValue, TruthValue rightValue) { | ||
48 | return null; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public TruthValue commonAncestor(TruthValue leftValue, TruthValue rightValue) { | ||
53 | return null; | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public TruthValue unknown() { | ||
58 | return null; | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public boolean isError(TruthValue abstractValue) { | ||
63 | return false; | ||
64 | } | ||
65 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java index 273d0de7..704ca2fc 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval { | 8 | public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java index e1a08bf9..ad16a3e8 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | public final class CardinalityIntervals { | 8 | public final class CardinalityIntervals { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java index ab3ad9d1..49911c29 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | public final class EmptyCardinalityInterval implements CardinalityInterval { | 8 | public final class EmptyCardinalityInterval implements CardinalityInterval { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java index 381c8a57..82afdbbc 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.jetbrains.annotations.NotNull; | 8 | import org.jetbrains.annotations.NotNull; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java index 32b3786f..38bd53bf 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import java.util.function.BinaryOperator; | 8 | import java.util.function.BinaryOperator; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java index 593bc322..a5634020 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.jetbrains.annotations.NotNull; | 8 | import org.jetbrains.annotations.NotNull; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java index d850fdc9..1e18dde0 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | public final class UpperCardinalities { | 8 | public final class UpperCardinalities { |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java index c6e31cb7..5dbaa922 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java +++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality, | 8 | public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality, |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java index bf844c6d..aae7b344 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java | |||
@@ -1,28 +1,60 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.tuple; | 6 | package tools.refinery.store.tuple; |
2 | 7 | ||
3 | public sealed interface Tuple extends TupleLike permits Tuple0, Tuple1, Tuple2, TupleN { | 8 | import org.jetbrains.annotations.NotNull; |
9 | |||
10 | public sealed interface Tuple extends Comparable<Tuple> permits Tuple0, Tuple1, Tuple2, Tuple3, Tuple4, TupleN { | ||
11 | int getSize(); | ||
12 | |||
13 | int get(int element); | ||
14 | |||
4 | @Override | 15 | @Override |
5 | default Tuple toTuple() { | 16 | default int compareTo(@NotNull Tuple other) { |
6 | return this; | 17 | int size = getSize(); |
18 | int compareSize = Integer.compare(size, other.getSize()); | ||
19 | if (compareSize != 0) { | ||
20 | return compareSize; | ||
21 | } | ||
22 | for (int i = 0; i < size; i++) { | ||
23 | int compareElement = Integer.compare(get(i), other.get(i)); | ||
24 | if (compareElement != 0) { | ||
25 | return compareElement; | ||
26 | } | ||
27 | } | ||
28 | return 0; | ||
7 | } | 29 | } |
8 | 30 | ||
9 | static Tuple of() { | 31 | static Tuple0 of() { |
10 | return Tuple0.INSTANCE; | 32 | return Tuple0.INSTANCE; |
11 | } | 33 | } |
12 | 34 | ||
13 | static Tuple of(int value) { | 35 | static Tuple1 of(int value) { |
14 | return Tuple1.Cache.INSTANCE.getOrCreate(value); | 36 | return Tuple1.Cache.INSTANCE.getOrCreate(value); |
15 | } | 37 | } |
16 | 38 | ||
17 | static Tuple of(int value1, int value2) { | 39 | static Tuple2 of(int value1, int value2) { |
18 | return new Tuple2(value1, value2); | 40 | return new Tuple2(value1, value2); |
19 | } | 41 | } |
20 | 42 | ||
43 | static Tuple3 of(int value1, int value2, int value3) { | ||
44 | return new Tuple3(value1, value2, value3); | ||
45 | } | ||
46 | |||
47 | static Tuple4 of(int value1, int value2, int value3, int value4) { | ||
48 | return new Tuple4(value1, value2, value3, value4); | ||
49 | } | ||
50 | |||
21 | static Tuple of(int... values) { | 51 | static Tuple of(int... values) { |
22 | return switch (values.length) { | 52 | return switch (values.length) { |
23 | case 0 -> of(); | 53 | case 0 -> of(); |
24 | case 1 -> of(values[0]); | 54 | case 1 -> of(values[0]); |
25 | case 2 -> of(values[0], values[1]); | 55 | case 2 -> of(values[0], values[1]); |
56 | case 3 -> of(values[0], values[1], values[2]); | ||
57 | case 4 -> of(values[0], values[1], values[2], values[3]); | ||
26 | default -> new TupleN(values); | 58 | default -> new TupleN(values); |
27 | }; | 59 | }; |
28 | } | 60 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java index 8eea5c3a..a9aa9bf2 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java | |||
@@ -1,7 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.tuple; | 6 | package tools.refinery.store.tuple; |
2 | 7 | ||
3 | public record Tuple0() implements Tuple { | 8 | import static tools.refinery.store.tuple.TupleConstants.TUPLE_BEGIN; |
4 | public static Tuple0 INSTANCE = new Tuple0(); | 9 | import static tools.refinery.store.tuple.TupleConstants.TUPLE_END; |
10 | |||
11 | /** | ||
12 | * Singleton implementation to ensure only a single empty tuple exists. | ||
13 | */ | ||
14 | @SuppressWarnings("squid:S6548") | ||
15 | public final class Tuple0 implements Tuple { | ||
16 | public static final Tuple0 INSTANCE = new Tuple0(); | ||
17 | |||
18 | private Tuple0() { | ||
19 | } | ||
5 | 20 | ||
6 | @Override | 21 | @Override |
7 | public int getSize() { | 22 | public int getSize() { |
@@ -14,12 +29,7 @@ public record Tuple0() implements Tuple { | |||
14 | } | 29 | } |
15 | 30 | ||
16 | @Override | 31 | @Override |
17 | public int[] toArray() { | ||
18 | return new int[]{}; | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public String toString() { | 32 | public String toString() { |
23 | return "[]"; | 33 | return TUPLE_BEGIN + TUPLE_END; |
24 | } | 34 | } |
25 | } | 35 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java index 07380966..388ee3a9 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java | |||
@@ -1,10 +1,29 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.tuple; | 6 | package tools.refinery.store.tuple; |
2 | 7 | ||
8 | import org.jetbrains.annotations.NotNull; | ||
3 | import tools.refinery.store.model.TupleHashProvider; | 9 | import tools.refinery.store.model.TupleHashProvider; |
4 | 10 | ||
5 | import java.util.Arrays; | 11 | import java.util.Arrays; |
6 | 12 | ||
7 | public record Tuple1(int value0) implements Tuple { | 13 | import static tools.refinery.store.tuple.TupleConstants.TUPLE_BEGIN; |
14 | import static tools.refinery.store.tuple.TupleConstants.TUPLE_END; | ||
15 | |||
16 | public final class Tuple1 implements Tuple { | ||
17 | private final int value0; | ||
18 | |||
19 | private Tuple1(int value0) { | ||
20 | this.value0 = value0; | ||
21 | } | ||
22 | |||
23 | public int value0() { | ||
24 | return value0; | ||
25 | } | ||
26 | |||
8 | @Override | 27 | @Override |
9 | public int getSize() { | 28 | public int getSize() { |
10 | return 1; | 29 | return 1; |
@@ -19,20 +38,40 @@ public record Tuple1(int value0) implements Tuple { | |||
19 | } | 38 | } |
20 | 39 | ||
21 | @Override | 40 | @Override |
22 | public int[] toArray() { | 41 | public String toString() { |
23 | return new int[]{value0}; | 42 | return TUPLE_BEGIN + value0 + TUPLE_END; |
24 | } | 43 | } |
25 | 44 | ||
26 | @Override | 45 | @Override |
27 | public String toString() { | 46 | public boolean equals(Object o) { |
28 | return "[" + value0 + "]"; | 47 | if (this == o) return true; |
48 | if (o == null || getClass() != o.getClass()) return false; | ||
49 | Tuple1 tuple1 = (Tuple1) o; | ||
50 | return value0 == tuple1.value0; | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public int hashCode() { | ||
55 | return 31 + value0; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public int compareTo(@NotNull Tuple other) { | ||
60 | if (other instanceof Tuple1 other1) { | ||
61 | return Integer.compare(value0, other1.value0); | ||
62 | } | ||
63 | return Tuple.super.compareTo(other); | ||
29 | } | 64 | } |
30 | 65 | ||
31 | /** | 66 | /** |
32 | * This class uses safe double-checked locking, see | 67 | * This class uses safe double-checked locking, see |
33 | * <a href="https://shipilev.net/blog/2014/safe-public-construction/">Safe Publication and Safe Initialization in | 68 | * <a href="https://shipilev.net/blog/2014/safe-public-construction/">Safe Publication and Safe Initialization in |
34 | * Java</a> for details. | 69 | * Java</a> for details. |
70 | * <p> | ||
71 | * This class implements the singleton pattern to ensure only a single cache exists. This is thread-safe because | ||
72 | * of the locking of the cache. | ||
35 | */ | 73 | */ |
74 | @SuppressWarnings("squid:S6548") | ||
36 | public static class Cache { | 75 | public static class Cache { |
37 | private static final int MIN_CACHE_SIZE = 256; | 76 | private static final int MIN_CACHE_SIZE = 256; |
38 | 77 | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java index 0836a32d..6d886fd3 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java | |||
@@ -1,5 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.tuple; | 6 | package tools.refinery.store.tuple; |
2 | 7 | ||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | |||
10 | import static tools.refinery.store.tuple.TupleConstants.*; | ||
11 | |||
3 | public record Tuple2(int value0, int value1) implements Tuple { | 12 | public record Tuple2(int value0, int value1) implements Tuple { |
4 | @Override | 13 | @Override |
5 | public int getSize() { | 14 | public int getSize() { |
@@ -16,12 +25,34 @@ public record Tuple2(int value0, int value1) implements Tuple { | |||
16 | } | 25 | } |
17 | 26 | ||
18 | @Override | 27 | @Override |
19 | public int[] toArray() { | 28 | public String toString() { |
20 | return new int[]{value0, value1}; | 29 | return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_END; |
21 | } | 30 | } |
22 | 31 | ||
23 | @Override | 32 | @Override |
24 | public String toString() { | 33 | public boolean equals(Object o) { |
25 | return "[" + value0 + ", " + value1 + "]"; | 34 | if (this == o) return true; |
35 | if (o == null || getClass() != o.getClass()) return false; | ||
36 | Tuple2 tuple2 = (Tuple2) o; | ||
37 | return value0 == tuple2.value0 && value1 == tuple2.value1; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public int hashCode() { | ||
42 | int hash = 31 + value0; | ||
43 | hash = 31 * hash + value1; | ||
44 | return hash; | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public int compareTo(@NotNull Tuple other) { | ||
49 | if (other instanceof Tuple2 other2) { | ||
50 | int compare0 = Integer.compare(value0, other2.value0); | ||
51 | if (compare0 != 0) { | ||
52 | return compare0; | ||
53 | } | ||
54 | return Integer.compare(value1, other2.value1); | ||
55 | } | ||
56 | return Tuple.super.compareTo(other); | ||
26 | } | 57 | } |
27 | } | 58 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java new file mode 100644 index 00000000..734e45c2 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.tuple; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | |||
10 | import static tools.refinery.store.tuple.TupleConstants.*; | ||
11 | |||
12 | public record Tuple3(int value0, int value1, int value2) implements Tuple { | ||
13 | @Override | ||
14 | public int getSize() { | ||
15 | return 3; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public int get(int element) { | ||
20 | return switch (element) { | ||
21 | case 0 -> value0; | ||
22 | case 1 -> value1; | ||
23 | case 2 -> value2; | ||
24 | default -> throw new ArrayIndexOutOfBoundsException(element); | ||
25 | }; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public String toString() { | ||
30 | return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_SEPARATOR + value2 + TUPLE_END; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public boolean equals(Object o) { | ||
35 | if (this == o) return true; | ||
36 | if (o == null || getClass() != o.getClass()) return false; | ||
37 | Tuple3 tuple3 = (Tuple3) o; | ||
38 | return value0 == tuple3.value0 && value1 == tuple3.value1 && value2 == tuple3.value2; | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public int hashCode() { | ||
43 | int hash = 31 + value0; | ||
44 | hash = 31 * hash + value1; | ||
45 | hash = 31 * hash + value2; | ||
46 | return hash; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public int compareTo(@NotNull Tuple other) { | ||
51 | if (other instanceof Tuple3 other3) { | ||
52 | int compare0 = Integer.compare(value0, other3.value0); | ||
53 | if (compare0 != 0) { | ||
54 | return compare0; | ||
55 | } | ||
56 | int compare1 = Integer.compare(value1, other3.value1); | ||
57 | if (compare1 != 0) { | ||
58 | return compare1; | ||
59 | } | ||
60 | return Integer.compare(value2, other3.value2); | ||
61 | } | ||
62 | return Tuple.super.compareTo(other); | ||
63 | } | ||
64 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java new file mode 100644 index 00000000..e1b93e7b --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java | |||
@@ -0,0 +1,71 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.tuple; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | |||
10 | import static tools.refinery.store.tuple.TupleConstants.*; | ||
11 | |||
12 | public record Tuple4(int value0, int value1, int value2, int value3) implements Tuple { | ||
13 | @Override | ||
14 | public int getSize() { | ||
15 | return 4; | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public int get(int element) { | ||
20 | return switch (element) { | ||
21 | case 0 -> value0; | ||
22 | case 1 -> value1; | ||
23 | case 2 -> value2; | ||
24 | case 3 -> value3; | ||
25 | default -> throw new ArrayIndexOutOfBoundsException(element); | ||
26 | }; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public String toString() { | ||
31 | return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_SEPARATOR + value2 + TUPLE_SEPARATOR + value3 + | ||
32 | TUPLE_END; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public boolean equals(Object o) { | ||
37 | if (this == o) return true; | ||
38 | if (o == null || getClass() != o.getClass()) return false; | ||
39 | Tuple4 tuple4 = (Tuple4) o; | ||
40 | return value0 == tuple4.value0 && value1 == tuple4.value1 && value2 == tuple4.value2 && value3 == tuple4.value3; | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public int hashCode() { | ||
45 | int hash = 31 + value0; | ||
46 | hash = 31 * hash + value1; | ||
47 | hash = 31 * hash + value2; | ||
48 | hash = 31 * hash + value3; | ||
49 | return hash; | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public int compareTo(@NotNull Tuple other) { | ||
54 | if (other instanceof Tuple4 other4) { | ||
55 | int compare0 = Integer.compare(value0, other4.value0); | ||
56 | if (compare0 != 0) { | ||
57 | return compare0; | ||
58 | } | ||
59 | int compare1 = Integer.compare(value1, other4.value1); | ||
60 | if (compare1 != 0) { | ||
61 | return compare1; | ||
62 | } | ||
63 | int compare2 = Integer.compare(value2, other4.value2); | ||
64 | if (compare2 != 0) { | ||
65 | return compare2; | ||
66 | } | ||
67 | return Integer.compare(value3, other4.value3); | ||
68 | } | ||
69 | return Tuple.super.compareTo(other); | ||
70 | } | ||
71 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleConstants.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleConstants.java new file mode 100644 index 00000000..f7d27848 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleConstants.java | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.tuple; | ||
7 | |||
8 | final class TupleConstants { | ||
9 | public static final int MAX_STATIC_ARITY_TUPLE_SIZE = 4; | ||
10 | public static final String TUPLE_BEGIN = "["; | ||
11 | public static final String TUPLE_SEPARATOR = ", "; | ||
12 | public static final String TUPLE_END = "]"; | ||
13 | |||
14 | private TupleConstants() { | ||
15 | throw new IllegalArgumentException("This is a static utility class an should not instantiated directly"); | ||
16 | } | ||
17 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java deleted file mode 100644 index 470ca298..00000000 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | package tools.refinery.store.tuple; | ||
2 | |||
3 | public interface TupleLike { | ||
4 | int getSize(); | ||
5 | |||
6 | int get(int element); | ||
7 | |||
8 | default int[] toArray() { | ||
9 | int size = getSize(); | ||
10 | var array = new int[size]; | ||
11 | for (int i = 0; i < size; i++) { | ||
12 | array[i] = get(i); | ||
13 | } | ||
14 | return array; | ||
15 | } | ||
16 | |||
17 | default Tuple toTuple() { | ||
18 | return switch (getSize()) { | ||
19 | case 0 -> Tuple.of(); | ||
20 | case 1 -> Tuple.of(get(0)); | ||
21 | case 2 -> Tuple.of(get(0), get(1)); | ||
22 | default -> Tuple.of(toArray()); | ||
23 | }; | ||
24 | } | ||
25 | } | ||
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java index 15fd063b..b66af491 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java | |||
@@ -1,14 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.tuple; | 6 | package tools.refinery.store.tuple; |
2 | 7 | ||
3 | import java.util.Arrays; | 8 | import java.util.Arrays; |
4 | import java.util.stream.Collectors; | 9 | import java.util.stream.Collectors; |
5 | 10 | ||
6 | public record TupleN(int[] values) implements Tuple { | 11 | import static tools.refinery.store.tuple.TupleConstants.*; |
7 | static final int CUSTOM_TUPLE_SIZE = 2; | ||
8 | 12 | ||
9 | public TupleN(int[] values) { | 13 | public final class TupleN implements Tuple { |
10 | if (values.length < CUSTOM_TUPLE_SIZE) | 14 | private final int[] values; |
11 | throw new IllegalArgumentException(); | 15 | |
16 | TupleN(int[] values) { | ||
17 | if (values.length < MAX_STATIC_ARITY_TUPLE_SIZE) { | ||
18 | throw new IllegalArgumentException("Tuples of size at most %d must use static arity Tuple classes" | ||
19 | .formatted(MAX_STATIC_ARITY_TUPLE_SIZE)); | ||
20 | } | ||
12 | this.values = Arrays.copyOf(values, values.length); | 21 | this.values = Arrays.copyOf(values, values.length); |
13 | } | 22 | } |
14 | 23 | ||
@@ -23,19 +32,11 @@ public record TupleN(int[] values) implements Tuple { | |||
23 | } | 32 | } |
24 | 33 | ||
25 | @Override | 34 | @Override |
26 | public int[] toArray() { | ||
27 | return values; | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public String toString() { | 35 | public String toString() { |
32 | var valuesString = Arrays.stream(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")); | 36 | var valuesString = Arrays.stream(values) |
33 | return "[" + valuesString + "]"; | 37 | .mapToObj(Integer::toString) |
34 | } | 38 | .collect(Collectors.joining(TUPLE_SEPARATOR)); |
35 | 39 | return TUPLE_BEGIN + valuesString + TUPLE_END; | |
36 | @Override | ||
37 | public int hashCode() { | ||
38 | return Arrays.hashCode(values); | ||
39 | } | 40 | } |
40 | 41 | ||
41 | @Override | 42 | @Override |
@@ -49,4 +50,9 @@ public record TupleN(int[] values) implements Tuple { | |||
49 | TupleN other = (TupleN) obj; | 50 | TupleN other = (TupleN) obj; |
50 | return Arrays.equals(values, other.values); | 51 | return Arrays.equals(values, other.values); |
51 | } | 52 | } |
53 | |||
54 | @Override | ||
55 | public int hashCode() { | ||
56 | return Arrays.hashCode(values); | ||
57 | } | ||
52 | } | 58 | } |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/util/CollectionsUtil.java b/subprojects/store/src/main/java/tools/refinery/store/util/CollectionsUtil.java index 841d0dfa..adecd79b 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/util/CollectionsUtil.java +++ b/subprojects/store/src/main/java/tools/refinery/store/util/CollectionsUtil.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.util; | 6 | package tools.refinery.store.util; |
2 | 7 | ||
3 | import java.util.Iterator; | 8 | import java.util.Iterator; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java b/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java new file mode 100644 index 00000000..78ad2ad7 --- /dev/null +++ b/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java | |||
@@ -0,0 +1,57 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.util; | ||
7 | |||
8 | import java.util.*; | ||
9 | import java.util.function.Function; | ||
10 | import java.util.stream.Collectors; | ||
11 | |||
12 | public class CycleDetectingMapper<T, R> { | ||
13 | private static final String SEPARATOR = " -> "; | ||
14 | |||
15 | private final Function<T, String> getName; | ||
16 | |||
17 | private final Function<T, R> doMap; | ||
18 | |||
19 | private final Set<T> inProgress = new LinkedHashSet<>(); | ||
20 | |||
21 | private final Map<T, R> results = new HashMap<>(); | ||
22 | |||
23 | public CycleDetectingMapper(Function<T, String> getName, Function<T, R> doMap) { | ||
24 | this.getName = getName; | ||
25 | this.doMap = doMap; | ||
26 | } | ||
27 | |||
28 | public R map(T input) { | ||
29 | if (inProgress.contains(input)) { | ||
30 | var path = inProgress.stream().map(getName).collect(Collectors.joining(SEPARATOR)); | ||
31 | throw new IllegalArgumentException("Circular reference %s%s%s detected".formatted(path, SEPARATOR, | ||
32 | getName.apply(input))); | ||
33 | } | ||
34 | // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant | ||
35 | // way, which would cause a ConcurrentModificationException with computeIfAbsent. | ||
36 | @SuppressWarnings("squid:S3824") | ||
37 | var result = results.get(input); | ||
38 | if (result == null) { | ||
39 | inProgress.add(input); | ||
40 | try { | ||
41 | result = doMap.apply(input); | ||
42 | results.put(input, result); | ||
43 | } finally { | ||
44 | inProgress.remove(input); | ||
45 | } | ||
46 | } | ||
47 | return result; | ||
48 | } | ||
49 | |||
50 | public List<T> getInProgress() { | ||
51 | return List.copyOf(inProgress); | ||
52 | } | ||
53 | |||
54 | public R getAlreadyMapped(T input) { | ||
55 | return results.get(input); | ||
56 | } | ||
57 | } | ||
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java index 6889fd07..2be49bd9 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests; | 6 | package tools.refinery.store.map.tests; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java index 7977f772..58206eda 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Tag; | 8 | import org.junit.jupiter.api.Tag; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java index 99e76649..c49911b8 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Tag; | 8 | import org.junit.jupiter.api.Tag; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java index e02448cf..5a4f8038 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Tag; | 8 | import org.junit.jupiter.api.Tag; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java index ea58e1b7..3b55434c 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Tag; | 8 | import org.junit.jupiter.api.Tag; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java index f449ca97..9b2e591a 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import java.util.ArrayList; | 8 | import java.util.ArrayList; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java index 61b17362..fdcd7f9f 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import static org.junit.jupiter.api.Assertions.fail; | 8 | import static org.junit.jupiter.api.Assertions.fail; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java index cee15fe1..420dade6 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import static org.junit.jupiter.api.Assertions.fail; | 8 | import static org.junit.jupiter.api.Assertions.fail; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java index 1661cccb..0b399c3a 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Tag; | 8 | import org.junit.jupiter.api.Tag; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java index 0544687a..680d962d 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz; | 6 | package tools.refinery.store.map.tests.fuzz; |
2 | 7 | ||
3 | import java.util.HashMap; | 8 | import java.util.HashMap; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java index 89c01690..32675635 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz.utils; | 6 | package tools.refinery.store.map.tests.fuzz.utils; |
2 | 7 | ||
3 | import java.util.Arrays; | 8 | import java.util.Arrays; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java index 8c641205..951d6336 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.fuzz.utils; | 6 | package tools.refinery.store.map.tests.fuzz.utils; |
2 | 7 | ||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java index 0e695aaa..e7348370 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java +++ b/subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.map.tests.utils; | 6 | package tools.refinery.store.map.tests.utils; |
2 | 7 | ||
3 | import tools.refinery.store.map.*; | 8 | import tools.refinery.store.map.*; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java index bb083805..4d4f5e26 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.hashtests; | 6 | package tools.refinery.store.model.hashtests; |
2 | 7 | ||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java index 371b5e47..56b75804 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.model.tests; | 6 | package tools.refinery.store.model.tests; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
@@ -9,27 +14,24 @@ import tools.refinery.store.tuple.Tuple; | |||
9 | import static org.junit.jupiter.api.Assertions.*; | 14 | import static org.junit.jupiter.api.Assertions.*; |
10 | 15 | ||
11 | class ModelTest { | 16 | class ModelTest { |
17 | private static final Symbol<Boolean> person = Symbol.of("Person", 1); | ||
18 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
19 | private static final Symbol<Boolean> friend = Symbol.of("friend", 2); | ||
20 | |||
12 | @Test | 21 | @Test |
13 | void modelConstructionTest() { | 22 | void modelConstructionTest() { |
14 | var person = new Symbol<>("Person", 1, Boolean.class, false); | ||
15 | var friend = new Symbol<>("friend", 2, Boolean.class, false); | ||
16 | |||
17 | var store = ModelStore.builder().symbols(person, friend).build(); | 23 | var store = ModelStore.builder().symbols(person, friend).build(); |
18 | var symbols = store.getSymbols(); | 24 | var symbols = store.getSymbols(); |
19 | 25 | ||
20 | assertTrue(symbols.contains(person)); | 26 | assertTrue(symbols.contains(person)); |
21 | assertTrue(symbols.contains(friend)); | 27 | assertTrue(symbols.contains(friend)); |
22 | 28 | ||
23 | var other = new Symbol<>("other", 2, Integer.class, null); | 29 | var other = Symbol.of("other", 2, Integer.class); |
24 | assertFalse(symbols.contains(other)); | 30 | assertFalse(symbols.contains(other)); |
25 | } | 31 | } |
26 | 32 | ||
27 | @Test | 33 | @Test |
28 | void modelBuildingTest() { | 34 | void modelBuildingTest() { |
29 | var person = new Symbol<>("Person", 1, Boolean.class, false); | ||
30 | var age = new Symbol<>("age", 1, Integer.class, null); | ||
31 | var friend = new Symbol<>("friend", 2, Boolean.class, false); | ||
32 | |||
33 | var store = ModelStore.builder().symbols(person, age, friend).build(); | 35 | var store = ModelStore.builder().symbols(person, age, friend).build(); |
34 | var model = store.createEmptyModel(); | 36 | var model = store.createEmptyModel(); |
35 | var personInterpretation = model.getInterpretation(person); | 37 | var personInterpretation = model.getInterpretation(person); |
@@ -49,7 +51,7 @@ class ModelTest { | |||
49 | 51 | ||
50 | assertEquals(3, ageInterpretation.get(Tuple.of(0))); | 52 | assertEquals(3, ageInterpretation.get(Tuple.of(0))); |
51 | assertEquals(1, ageInterpretation.get(Tuple.of(1))); | 53 | assertEquals(1, ageInterpretation.get(Tuple.of(1))); |
52 | assertNull(ageInterpretation.get( Tuple.of(2))); | 54 | assertNull(ageInterpretation.get(Tuple.of(2))); |
53 | 55 | ||
54 | assertTrue(friendInterpretation.get(Tuple.of(0, 1))); | 56 | assertTrue(friendInterpretation.get(Tuple.of(0, 1))); |
55 | assertFalse(friendInterpretation.get(Tuple.of(0, 5))); | 57 | assertFalse(friendInterpretation.get(Tuple.of(0, 5))); |
@@ -57,8 +59,6 @@ class ModelTest { | |||
57 | 59 | ||
58 | @Test | 60 | @Test |
59 | void modelBuildingArityFailTest() { | 61 | void modelBuildingArityFailTest() { |
60 | var person = new Symbol<>("Person", 1, Boolean.class, false); | ||
61 | |||
62 | var store = ModelStore.builder().symbols(person).build(); | 62 | var store = ModelStore.builder().symbols(person).build(); |
63 | var model = store.createEmptyModel(); | 63 | var model = store.createEmptyModel(); |
64 | var personInterpretation = model.getInterpretation(person); | 64 | var personInterpretation = model.getInterpretation(person); |
@@ -70,8 +70,6 @@ class ModelTest { | |||
70 | 70 | ||
71 | @Test | 71 | @Test |
72 | void modelBuildingNullFailTest() { | 72 | void modelBuildingNullFailTest() { |
73 | var age = new Symbol<>("age", 1, Integer.class, null); | ||
74 | |||
75 | var store = ModelStore.builder().symbols(age).build(); | 73 | var store = ModelStore.builder().symbols(age).build(); |
76 | var model = store.createEmptyModel(); | 74 | var model = store.createEmptyModel(); |
77 | var ageInterpretation = model.getInterpretation(age); | 75 | var ageInterpretation = model.getInterpretation(age); |
@@ -84,10 +82,6 @@ class ModelTest { | |||
84 | 82 | ||
85 | @Test | 83 | @Test |
86 | void modelUpdateTest() { | 84 | void modelUpdateTest() { |
87 | var person = new Symbol<>("Person", 1, Boolean.class, false); | ||
88 | var age = new Symbol<>("age", 1, Integer.class, null); | ||
89 | var friend = new Symbol<>("friend", 2, Boolean.class, false); | ||
90 | |||
91 | var store = ModelStore.builder().symbols(person, age, friend).build(); | 85 | var store = ModelStore.builder().symbols(person, age, friend).build(); |
92 | var model = store.createEmptyModel(); | 86 | var model = store.createEmptyModel(); |
93 | var personInterpretation = model.getInterpretation(person); | 87 | var personInterpretation = model.getInterpretation(person); |
@@ -113,9 +107,6 @@ class ModelTest { | |||
113 | 107 | ||
114 | @Test | 108 | @Test |
115 | void restoreTest() { | 109 | void restoreTest() { |
116 | var person = new Symbol<>("Person", 1, Boolean.class, false); | ||
117 | var friend = new Symbol<>("friend", 2, Boolean.class, false); | ||
118 | |||
119 | var store = ModelStore.builder().symbols(person, friend).build(); | 110 | var store = ModelStore.builder().symbols(person, friend).build(); |
120 | var model = store.createEmptyModel(); | 111 | var model = store.createEmptyModel(); |
121 | var personInterpretation = model.getInterpretation(person); | 112 | var personInterpretation = model.getInterpretation(person); |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java index 96fdc49e..6a66fa84 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.params.ParameterizedTest; | 8 | import org.junit.jupiter.params.ParameterizedTest; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java index 4a9ef8da..9fe76159 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java index e8b77b9f..24a788a8 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java index 9a190818..6cf56fae 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java index 90c21759..7c641c47 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java index 3c7c0320..e61f7b36 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java index e87ce29b..10b4dd20 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java +++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.representation.cardinality; | 6 | package tools.refinery.store.representation.cardinality; |
2 | 7 | ||
3 | import org.junit.jupiter.params.ParameterizedTest; | 8 | import org.junit.jupiter.params.ParameterizedTest; |
diff --git a/subprojects/store/src/test/java/tools/refinery/store/util/CollectionsUtilTests.java b/subprojects/store/src/test/java/tools/refinery/store/util/CollectionsUtilTests.java index 171be0e5..8d50fa8a 100644 --- a/subprojects/store/src/test/java/tools/refinery/store/util/CollectionsUtilTests.java +++ b/subprojects/store/src/test/java/tools/refinery/store/util/CollectionsUtilTests.java | |||
@@ -1,3 +1,8 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
1 | package tools.refinery.store.util; | 6 | package tools.refinery.store.util; |
2 | 7 | ||
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; |