aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
authorLibravatar Oszkár Semeráth <semerath@mit.bme.hu>2023-07-24 14:37:16 +0200
committerLibravatar GitHub <noreply@github.com>2023-07-24 14:37:16 +0200
commit8789304690384d19ad829286560aec5ed0917976 (patch)
treeb157e78e8b6a3f2fb3d0eee6e1aa10d2c16e0204 /subprojects
parentdecreasing steps in fast fuzz tests (diff)
parentMerge pull request #27 from kris7t/ordered-result-set (diff)
downloadrefinery-8789304690384d19ad829286560aec5ed0917976.tar.gz
refinery-8789304690384d19ad829286560aec5ed0917976.tar.zst
refinery-8789304690384d19ad829286560aec5ed0917976.zip
Merge branch 'graphs4value:main' into datastructure
Diffstat (limited to 'subprojects')
-rw-r--r--subprojects/frontend/.eslintrc.cjs18
-rw-r--r--subprojects/frontend/assets-src/favicon.svg.license3
-rw-r--r--subprojects/frontend/assets-src/icon.svg.license3
-rw-r--r--subprojects/frontend/assets-src/mask-icon.svg.license3
-rw-r--r--subprojects/frontend/build.gradle131
-rw-r--r--subprojects/frontend/build.gradle.kts144
-rw-r--r--subprojects/frontend/config/backendConfigVitePlugin.ts6
-rw-r--r--subprojects/frontend/config/detectDevModeOptions.ts6
-rw-r--r--subprojects/frontend/config/eslintReport.cjs58
-rw-r--r--subprojects/frontend/config/fetchPackageMetadata.ts6
-rw-r--r--subprojects/frontend/config/manifest.ts8
-rw-r--r--subprojects/frontend/config/minifyHTMLVitePlugin.ts6
-rw-r--r--subprojects/frontend/config/preloadFontsVitePlugin.ts6
-rw-r--r--subprojects/frontend/index.html11
-rw-r--r--subprojects/frontend/package.json99
-rw-r--r--subprojects/frontend/prettier.config.cjs6
-rw-r--r--subprojects/frontend/public/apple-touch-icon.png.license3
-rw-r--r--subprojects/frontend/public/favicon-96x96.png.license3
-rw-r--r--subprojects/frontend/public/favicon.png.license3
-rw-r--r--subprojects/frontend/public/favicon.svg.license3
-rw-r--r--subprojects/frontend/public/icon-192x192.png.license3
-rw-r--r--subprojects/frontend/public/icon-512x512.png.license3
-rw-r--r--subprojects/frontend/public/icon-any.svg.license3
-rw-r--r--subprojects/frontend/public/mask-icon.svg.license3
-rw-r--r--subprojects/frontend/public/robots.txt4
-rw-r--r--subprojects/frontend/src/App.tsx6
-rw-r--r--subprojects/frontend/src/Loading.tsx6
-rw-r--r--subprojects/frontend/src/PWAStore.ts6
-rw-r--r--subprojects/frontend/src/Refinery.tsx7
-rw-r--r--subprojects/frontend/src/RootStore.ts6
-rw-r--r--subprojects/frontend/src/RootStoreProvider.tsx6
-rw-r--r--subprojects/frontend/src/ToggleDarkModeButton.tsx6
-rw-r--r--subprojects/frontend/src/TopBar.tsx6
-rw-r--r--subprojects/frontend/src/UpdateNotification.tsx10
-rw-r--r--subprojects/frontend/src/WindowControlsOverlayColor.tsx6
-rw-r--r--subprojects/frontend/src/editor/AnimatedButton.tsx6
-rw-r--r--subprojects/frontend/src/editor/ConnectButton.tsx6
-rw-r--r--subprojects/frontend/src/editor/ConnectionStatusNotification.tsx6
-rw-r--r--subprojects/frontend/src/editor/DiagnosticValue.ts6
-rw-r--r--subprojects/frontend/src/editor/EditorArea.tsx6
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx6
-rw-r--r--subprojects/frontend/src/editor/EditorPane.tsx6
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts6
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts132
-rw-r--r--subprojects/frontend/src/editor/GenerateButton.tsx6
-rw-r--r--subprojects/frontend/src/editor/LintPanelStore.ts6
-rw-r--r--subprojects/frontend/src/editor/PanelStore.ts6
-rw-r--r--subprojects/frontend/src/editor/SearchPanel.ts6
-rw-r--r--subprojects/frontend/src/editor/SearchPanelPortal.tsx6
-rw-r--r--subprojects/frontend/src/editor/SearchPanelStore.ts6
-rw-r--r--subprojects/frontend/src/editor/SearchToolbar.tsx6
-rw-r--r--subprojects/frontend/src/editor/createEditorState.ts10
-rw-r--r--subprojects/frontend/src/editor/defineDecorationSetExtension.ts6
-rw-r--r--subprojects/frontend/src/editor/exposeDiagnostics.ts6
-rw-r--r--subprojects/frontend/src/editor/findOccurrences.ts6
-rw-r--r--subprojects/frontend/src/editor/indentationMarkerViewPlugin.ts341
-rw-r--r--subprojects/frontend/src/editor/scrollbarViewPlugin.ts358
-rw-r--r--subprojects/frontend/src/editor/semanticHighlighting.ts6
-rw-r--r--subprojects/frontend/src/index.tsx6
-rw-r--r--subprojects/frontend/src/language/folding.ts6
-rw-r--r--subprojects/frontend/src/language/indentation.ts7
-rw-r--r--subprojects/frontend/src/language/problem.grammar6
-rw-r--r--subprojects/frontend/src/language/problemLanguageSupport.ts6
-rw-r--r--subprojects/frontend/src/language/props.ts6
-rw-r--r--subprojects/frontend/src/theme/ThemeProvider.tsx18
-rw-r--r--subprojects/frontend/src/theme/ThemeStore.ts6
-rw-r--r--subprojects/frontend/src/utils/CancelledError.ts6
-rw-r--r--subprojects/frontend/src/utils/PendingTask.ts7
-rw-r--r--subprojects/frontend/src/utils/PriorityMutex.ts6
-rw-r--r--subprojects/frontend/src/utils/TimeoutError.ts6
-rw-r--r--subprojects/frontend/src/utils/getLogger.ts6
-rw-r--r--subprojects/frontend/src/utils/useDelayedSnackbar.ts7
-rw-r--r--subprojects/frontend/src/xtext/BackendConfig.ts6
-rw-r--r--subprojects/frontend/src/xtext/ContentAssistService.ts6
-rw-r--r--subprojects/frontend/src/xtext/HighlightingService.ts6
-rw-r--r--subprojects/frontend/src/xtext/OccurrencesService.ts6
-rw-r--r--subprojects/frontend/src/xtext/UpdateService.ts6
-rw-r--r--subprojects/frontend/src/xtext/UpdateStateTracker.ts6
-rw-r--r--subprojects/frontend/src/xtext/ValidationService.ts6
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts6
-rw-r--r--subprojects/frontend/src/xtext/XtextWebSocketClient.ts6
-rw-r--r--subprojects/frontend/src/xtext/fetchBackendConfig.ts6
-rw-r--r--subprojects/frontend/src/xtext/webSocketMachine.ts17
-rw-r--r--subprojects/frontend/src/xtext/xtextMessages.ts6
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts6
-rw-r--r--subprojects/frontend/tsconfig.base.json38
-rw-r--r--subprojects/frontend/tsconfig.json6
-rw-r--r--subprojects/frontend/tsconfig.node.json6
-rw-r--r--subprojects/frontend/tsconfig.shared.json9
-rw-r--r--subprojects/frontend/types/ImportMeta.d.ts7
-rw-r--r--subprojects/frontend/types/grammar.d.ts7
-rw-r--r--subprojects/frontend/types/node/@lezer-generator-rollup.d.ts7
-rw-r--r--subprojects/frontend/types/windowControlsOverlay.d.ts6
-rw-r--r--subprojects/frontend/vite.config.ts6
-rw-r--r--subprojects/language-ide/build.gradle18
-rw-r--r--subprojects/language-ide/build.gradle.kts18
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeModule.java6
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/ProblemIdeSetup.java6
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/FuzzyMatcher.java5
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java5
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingPartialProblemContentAssistParser.java5
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/TokenSourceInjectingProblemParser.java5
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/antlr/ProblemTokenSource.java6
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java5
-rw-r--r--subprojects/language-model/META-INF/MANIFEST.MF2
-rw-r--r--subprojects/language-model/META-INF/MANIFEST.MF.license3
-rw-r--r--subprojects/language-model/build.gradle55
-rw-r--r--subprojects/language-model/build.gradle.kts65
-rw-r--r--subprojects/language-model/build.properties2
-rw-r--r--subprojects/language-model/plugin.properties2
-rw-r--r--subprojects/language-model/plugin.xml3
-rw-r--r--subprojects/language-model/problem.aird.license3
-rw-r--r--subprojects/language-model/src/main/java/tools/refinery/language/model/GenerateProblemModel.mwe25
-rw-r--r--subprojects/language-model/src/main/resources/model/problem.ecore.license3
-rw-r--r--subprojects/language-model/src/main/resources/model/problem.genmodel.license3
-rw-r--r--subprojects/language-semantics/build.gradle11
-rw-r--r--subprojects/language-semantics/build.gradle.kts17
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java9
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java5
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java5
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeNode.java5
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeValue.java5
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/IntermediateNode.java5
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/TerminalNode.java5
-rw-r--r--subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java5
-rw-r--r--subprojects/language-web/build.gradle87
-rw-r--r--subprojects/language-web/build.gradle.kts68
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java12
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java6
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java12
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/VirtualThreadUtils.java52
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfig.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java7
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/occurrences/ProblemOccurrencesService.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java16
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/PongResult.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandler.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ResponseHandlerException.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/SubscribingServiceContext.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorKind.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebErrorResponse.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebPushMessage.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleServiceContext.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/SimpleSession.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextStatusCode.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java44
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java5
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java60
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java9
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java5
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java5
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java24
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java5
-rw-r--r--subprojects/language/build.gradle73
-rw-r--r--subprojects/language/build.gradle.kts101
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe221
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/Problem.xtext5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/serializer/PreferShortAssertionsProblemSemanticSequencer.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java6
-rw-r--r--subprojects/language/src/main/resources/tools/refinery/language/builtin.problem3
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/rules/RuleParsingTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java5
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemNavigationUtil.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/ProblemParseHelper.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAction.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertion.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAssertionArgument.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedAtom.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedClassDeclaration.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConjunction.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedConsequent.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedEnumDeclaration.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedProblem.java5
-rw-r--r--subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java5
-rw-r--r--subprojects/store-query-viatra/NOTICE.md87
-rw-r--r--subprojects/store-query-viatra/build.gradle16
-rw-r--r--subprojects/store-query-viatra/build.gradle.kts15
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQuery.java21
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryAdapter.java10
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java22
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java18
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java86
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java119
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java44
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperator.java97
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java53
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java54
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java76
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java117
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java35
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java137
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java60
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java28
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java70
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java32
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java52
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java88
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java53
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java115
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java20
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java47
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java80
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java55
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java21
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java223
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java266
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java189
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java72
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java65
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java55
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java)16
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java37
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java19
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java44
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java48
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java65
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java37
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java44
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java)18
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java390
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java483
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/OrderedResultSetTest.java117
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java832
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java357
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorTest.java87
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java239
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java57
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java27
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java21
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java24
-rw-r--r--subprojects/store-query/build.gradle.kts15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java72
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AbstractQueryBuilder.java175
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java324
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java213
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java262
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/DNFUtils.java)11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/FunctionalDependency.java)7
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java99
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java179
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java27
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java66
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback0.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback1Data1.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback2Data2.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data2.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback3Data3.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data0.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data1.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data2.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data3.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/ClauseCallback4Data4.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback0.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback1.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback2.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback3.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/FunctionalQueryCallback4.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback0.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback1.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback2.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback3.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/callback/QueryCallback4.java14
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java78
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java54
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java143
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java138
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java83
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java130
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/atom/CallPolarity.java)15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java65
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java101
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Reduction.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSetListener.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/SubstitutionBuilder.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java41
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java113
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java71
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java108
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java79
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java84
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolAndTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolOrTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolXorTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/ComparisonTerm.java19
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/EqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/GreaterTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/LessTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/comparable/NotEqTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntDivTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMinusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPlusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntPowTerm.java43
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSubTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java40
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java94
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntUnaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealBinaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealDivTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMinusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPlusTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealPowTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSubTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java90
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java94
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealUnaryTerm.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityAddTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityBinaryTerm.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMaxTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMinTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityMulTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java86
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTerms.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java754
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/MapBasedValuation.java22
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java37
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/ValuationBuilder.java40
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java110
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionView.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java)15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustView.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/NodeFunctionView.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/SymbolView.java85
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java82
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ViewImplication.java23
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java210
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderVariableUnificationTest.java325
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java157
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java112
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java428
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java88
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/literal/CallLiteralTest.java94
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java97
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/bool/BoolTermsEvaluateTest.java75
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/int_/IntTermsEvaluateTest.java259
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/real/RealTermEvaluateTest.java238
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorStreamTest.java (renamed from subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/cardinality/UpperCardinalitySumAggregationOperatorStreamTest.java)15
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregatorTest.java80
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalityTermsEvaluateTest.java104
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToRawTest.java159
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java127
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java634
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java68
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java46
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java41
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualToRaw.java51
-rw-r--r--subprojects/store-reasoning/build.gradle.kts13
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java18
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java20
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java25
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java30
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java33
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java43
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java31
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java42
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java128
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java51
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java36
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java36
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java9
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java60
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java17
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java19
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java27
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java159
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java30
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java27
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java93
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java54
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java106
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java40
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java40
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java35
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java141
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java9
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java207
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java51
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java37
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java208
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java205
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java56
-rw-r--r--subprojects/store/build.gradle4
-rw-r--r--subprojects/store/build.gradle.kts10
-rw-r--r--subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutBenchmark.java5
-rw-r--r--subprojects/store/src/jmh/java/tools/refinery/store/map/benchmarks/ImmutablePutExecutionPlan.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/AbstractModelAdapterBuilder.java45
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterList.java97
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/AdapterUtils.java33
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/AnyModelAdapterType.java19
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapter.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilder.java16
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterBuilderFactory.java14
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/ModelAdapterType.java79
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/adapter/ModelStoreAdapter.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/AnyVersionedMap.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/ContentHashCode.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/ContinousHashProvider.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Cursor.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/CursorAsIterator.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java41
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/DiffCursor.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/MapAsIterable.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Versioned.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMap.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStore.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreConfiguration.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/VersionedMapStoreImpl.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/HashClash.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/ImmutableNode.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MapCursor.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MapDiffCursor.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/MutableNode.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/Node.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/OldValueBox.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/VersionedMapImpl.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/InterpretationListener.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Model.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelDiffCursor.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelListener.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelStore.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java13
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProviderBitMagic.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelAction.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java25
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreImpl.java31
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/SymbolEquivalenceClass.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java19
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java24
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/DNF.java169
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java13
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java16
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java25
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/Variable.java43
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java80
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java12
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java32
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java22
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java32
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java24
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java49
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java71
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java62
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java19
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java44
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java34
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java12
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/AnySymbol.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java28
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java65
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java44
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java26
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java49
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java39
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java71
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/TupleConstants.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java25
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java40
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/util/CollectionsUtil.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java57
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/MapUnitTests.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/CommitFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/ContentEqualsFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/DiffCursorFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MultiThreadTestRunnable.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/MutableImmutableCompareFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/RestoreFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/SharedStoreFuzzTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtils.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/fuzz/utils/FuzzTestUtilsTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/map/tests/utils/MapTestEnvironment.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/hashtests/HashEfficiencyTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java31
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/CardinalityIntervalsTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/EmptyCardinalityIntervalTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteCardinalityIntervalTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinalityTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalityTest.java5
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/util/CollectionsUtilTests.java5
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
1const path = require('node:path'); 7const 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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1plugins {
2 id 'refinery-frontend-workspace'
3 id 'refinery-sonarqube'
4}
5
6import org.siouan.frontendgradleplugin.infrastructure.gradle.RunYarn
7
8def viteOutputDir = "${buildDir}/vite"
9def productionResources = file("${viteOutputDir}/production")
10
11frontend {
12 assembleScript = 'run build'
13}
14
15configurations {
16 productionAssets {
17 canBeConsumed = true
18 canBeResolved = false
19 }
20}
21
22def installFrontend = tasks.named('installFrontend')
23
24def sourcesWithoutTypegen = fileTree('src') {
25 exclude '**/*.typegen.ts'
26}
27
28def assembleFrontend = tasks.named('assembleFrontend')
29assembleFrontend.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
39artifacts {
40 productionAssets(productionResources) {
41 builtBy assembleFrontend
42 }
43}
44
45def 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
55def 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
68def 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
87def 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
101tasks.named('check') {
102 dependsOn(typecheckFrontend)
103 dependsOn(lintFrontend)
104}
105
106tasks.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
120tasks.named('clean') {
121 delete 'dev-dist'
122 delete fileTree('src') {
123 include '**/*.typegen.ts'
124 }
125}
126
127sonarqube.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
7import org.siouan.frontendgradleplugin.infrastructure.gradle.RunYarn
8import tools.refinery.gradle.utils.SonarPropertiesUtils
9
10plugins {
11 id("tools.refinery.gradle.frontend-workspace")
12 id("tools.refinery.gradle.sonarqube")
13}
14
15frontend {
16 assembleScript.set("run build")
17}
18
19val viteOutputDir = "$buildDir/vite"
20
21val productionResources = file("$viteOutputDir/production")
22
23val productionAssets: Configuration by configurations.creating {
24 isCanBeConsumed = true
25 isCanBeResolved = false
26}
27
28val sourcesWithoutTypes = fileTree("src") {
29 exclude("**/*.typegen.ts")
30}
31
32val sourcesWithTypes: FileCollection = fileTree("src") + fileTree("types")
33
34val buildScripts: FileCollection = fileTree("config") + files(
35 ".eslintrc.cjs",
36 "prettier.config.cjs",
37 "vite.config.ts",
38)
39
40val installationState = files(
41 rootProject.file("yarn.lock"),
42 rootProject.file("package.json"),
43 "package.json",
44)
45
46val sharedConfigFiles: FileCollection = installationState + files(
47 "tsconfig.json",
48 "tsconfig.base.json",
49 "tsconfig.node.json",
50 "tsconfig.shared.json",
51)
52
53val assembleConfigFiles = sharedConfigFiles + file("vite.config.ts") + fileTree("config") {
54 include("**/*.ts")
55}
56
57val assembleSources = sourcesWithTypes + fileTree("public") + file("index.html")
58
59val assembleFiles = assembleSources + assembleConfigFiles
60
61val lintingFiles: FileCollection = sourcesWithTypes + buildScripts + sharedConfigFiles
62
63tasks {
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
134artifacts {
135 add("productionAssets", productionResources) {
136 builtBy(tasks.assembleFrontend)
137 }
138}
139
140sonarqube.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
1import type { PluginOption } from 'vite'; 7import type { PluginOption } from 'vite';
2 8
3import type BackendConfig from '../src/xtext/BackendConfig'; 9import 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
1import type { PluginOption, ServerOptions } from 'vite'; 7import type { PluginOption, ServerOptions } from 'vite';
2 8
3import backendConfigVitePlugin, { 9import 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
7const { writeFile } = require('node:fs/promises');
8const path = require('node:path');
9const { Readable } = require('node:stream');
10const { pipeline } = require('node:stream/promises');
11
12const { ESLint } = require('eslint');
13
14const 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 */
23async 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 */
38async 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
45async 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
58createReport().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
1import { readFile } from 'node:fs/promises'; 7import { readFile } from 'node:fs/promises';
2import path from 'node:path'; 8import 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
1import type { ManifestOptions } from 'vite-plugin-pwa'; 7import type { ManifestOptions } from 'vite-plugin-pwa';
2 8
3const manifest: Partial<ManifestOptions> = { 9const 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
1import { minify, type Options as TerserOptions } from 'html-minifier-terser'; 7import { minify, type Options as TerserOptions } from 'html-minifier-terser';
2import type { PluginOption } from 'vite'; 8import 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
1import micromatch from 'micromatch'; 7import micromatch from 'micromatch';
2import type { PluginOption } from 'vite'; 8import 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} */
2module.exports = { 8module.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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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
1User-agent: * 5User-agent: *
2Allow: / 6Allow: /
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
1import Box from '@mui/material/Box'; 7import Box from '@mui/material/Box';
2import CssBaseline from '@mui/material/CssBaseline'; 8import CssBaseline from '@mui/material/CssBaseline';
3import { throttle } from 'lodash-es'; 9import { 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
1import CircularProgress from '@mui/material/CircularProgress'; 7import CircularProgress from '@mui/material/CircularProgress';
2import { styled } from '@mui/material/styles'; 8import { 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
1import { makeAutoObservable, observable } from 'mobx'; 7import { makeAutoObservable, observable } from 'mobx';
2import ms from 'ms'; 8import 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
1import Grow from '@mui/material/Grow'; 7import Grow from '@mui/material/Grow';
2import Stack from '@mui/material/Stack'; 8import Stack from '@mui/material/Stack';
3import { SnackbarProvider } from 'notistack'; 9import { SnackbarProvider } from 'notistack';
@@ -8,7 +14,6 @@ import EditorPane from './editor/EditorPane';
8 14
9export default function Refinery(): JSX.Element { 15export 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
1import { getLogger } from 'loglevel'; 7import { getLogger } from 'loglevel';
2import { makeAutoObservable, runInAction } from 'mobx'; 8import { 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
1import { type ReactNode, createContext, useContext } from 'react'; 7import { type ReactNode, createContext, useContext } from 'react';
2 8
3import type RootStore from './RootStore'; 9import 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
1import DarkModeIcon from '@mui/icons-material/DarkMode'; 7import DarkModeIcon from '@mui/icons-material/DarkMode';
2import LightModeIcon from '@mui/icons-material/LightMode'; 8import LightModeIcon from '@mui/icons-material/LightMode';
3import IconButton from '@mui/material/IconButton'; 9import 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
1import GitHubIcon from '@mui/icons-material/GitHub'; 7import GitHubIcon from '@mui/icons-material/GitHub';
2import AppBar from '@mui/material/AppBar'; 8import AppBar from '@mui/material/AppBar';
3import Button from '@mui/material/Button'; 9import 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
1import Button from '@mui/material/Button'; 7import Button from '@mui/material/Button';
2import { observer } from 'mobx-react-lite'; 8import { observer } from 'mobx-react-lite';
3import { useEffect } from 'react'; 9import { 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
1import { useTheme } from '@mui/material/styles'; 7import { useTheme } from '@mui/material/styles';
2import { useEffect } from 'react'; 8import { 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
1import Box from '@mui/material/Box'; 7import Box from '@mui/material/Box';
2import Button from '@mui/material/Button'; 8import Button from '@mui/material/Button';
3import { styled, type SxProps, type Theme } from '@mui/material/styles'; 9import { 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
1import CloudIcon from '@mui/icons-material/Cloud'; 7import CloudIcon from '@mui/icons-material/Cloud';
2import CloudOffIcon from '@mui/icons-material/CloudOff'; 8import CloudOffIcon from '@mui/icons-material/CloudOff';
3import SyncIcon from '@mui/icons-material/Sync'; 9import 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
1import Button from '@mui/material/Button'; 7import Button from '@mui/material/Button';
2import { observer } from 'mobx-react-lite'; 8import { observer } from 'mobx-react-lite';
3import { useEffect } from 'react'; 9import { 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
1import type { Diagnostic } from '@codemirror/lint'; 7import type { Diagnostic } from '@codemirror/lint';
2import { RangeValue } from '@codemirror/state'; 8import { 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
1import Box from '@mui/material/Box'; 7import Box from '@mui/material/Box';
2import { useTheme } from '@mui/material/styles'; 8import { useTheme } from '@mui/material/styles';
3import { observer } from 'mobx-react-lite'; 9import { 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
1import type { Diagnostic } from '@codemirror/lint'; 7import type { Diagnostic } from '@codemirror/lint';
2import CheckIcon from '@mui/icons-material/Check'; 8import CheckIcon from '@mui/icons-material/Check';
3import ErrorIcon from '@mui/icons-material/Error'; 9import 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
1import Box from '@mui/material/Box'; 7import Box from '@mui/material/Box';
2import Skeleton from '@mui/material/Skeleton'; 8import Skeleton from '@mui/material/Skeleton';
3import Stack from '@mui/material/Stack'; 9import 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
1import type { 7import 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
1import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; 7import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw';
2import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; 8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw';
3import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; 9import 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
11function 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
37export default styled('div', { 17export 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
1import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; 7import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined';
2import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 8import PlayArrowIcon from '@mui/icons-material/PlayArrow';
3import Button from '@mui/material/Button'; 9import 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
1import { closeLintPanel, openLintPanel } from '@codemirror/lint'; 7import { closeLintPanel, openLintPanel } from '@codemirror/lint';
2 8
3import type EditorStore from './EditorStore'; 9import 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
1import type { Command } from '@codemirror/view'; 7import type { Command } from '@codemirror/view';
2import { action, makeObservable, observable } from 'mobx'; 8import { 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
1import { 7import {
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
1import Portal from '@mui/material/Portal'; 7import Portal from '@mui/material/Portal';
2import { observer } from 'mobx-react-lite'; 8import { 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
1import { 7import {
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
1import CloseIcon from '@mui/icons-material/Close'; 7import CloseIcon from '@mui/icons-material/Close';
2import FindReplaceIcon from '@mui/icons-material/FindReplace'; 8import FindReplaceIcon from '@mui/icons-material/FindReplace';
3import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; 9import 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
1import { 7import {
2 closeBrackets, 8 closeBrackets,
3 closeBracketsKeymap, 9 closeBracketsKeymap,
@@ -38,8 +44,6 @@ import type EditorStore from './EditorStore';
38import SearchPanel from './SearchPanel'; 44import SearchPanel from './SearchPanel';
39import exposeDiagnostics from './exposeDiagnostics'; 45import exposeDiagnostics from './exposeDiagnostics';
40import findOccurrences from './findOccurrences'; 46import findOccurrences from './findOccurrences';
41import indentationMarkerViewPlugin from './indentationMarkerViewPlugin';
42import scrollbarViewPlugin from './scrollbarViewPlugin';
43import semanticHighlighting from './semanticHighlighting'; 47import semanticHighlighting from './semanticHighlighting';
44 48
45export default function createEditorState( 49export 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
1import { StateEffect, StateField, TransactionSpec } from '@codemirror/state'; 7import { StateEffect, StateField, TransactionSpec } from '@codemirror/state';
2import { EditorView, Decoration, DecorationSet } from '@codemirror/view'; 8import { 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
1import { setDiagnosticsEffect } from '@codemirror/lint'; 7import { setDiagnosticsEffect } from '@codemirror/lint';
2import { 8import {
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
1import { 7import {
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
15import { getIndentUnit } from '@codemirror/language';
16import { Text, RangeSet, EditorState } from '@codemirror/state';
17import {
18 ViewPlugin,
19 Decoration,
20 EditorView,
21 WidgetType,
22 PluginValue,
23} from '@codemirror/view';
24
25export const INDENTATION_MARKER_CLASS = 'cm-indentation-marker';
26
27export const INDENTATION_MARKER_ACTIVE_CLASS = 'active';
28
29const indentationMark = Decoration.mark({
30 class: INDENTATION_MARKER_CLASS,
31 tagName: 'span',
32});
33
34const 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 */
42class 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 */
87function 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 */
137function 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 */
154function 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
177interface 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 */
187function 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 */
221function 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
323export 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 @@
1import { EditorSelection } from '@codemirror/state';
2import {
3 type EditorView,
4 type PluginValue,
5 ViewPlugin,
6} from '@codemirror/view';
7import { reaction } from 'mobx';
8
9import type EditorStore from './EditorStore';
10import { getDiagnostics } from './exposeDiagnostics';
11import findOccurrences from './findOccurrences';
12
13export const HOLDER_CLASS = 'cm-scroller-holder';
14export const SPACER_CLASS = 'cm-scroller-spacer';
15export const TRACK_CLASS = 'cm-scroller-track';
16export const THUMB_CLASS = 'cm-scroller-thumb';
17export const THUMB_ACTIVE_CLASS = 'active';
18export const GUTTER_DECORATION_CLASS = 'cm-scroller-gutter-decoration';
19export const TOP_DECORATION_CLASS = 'cm-scroller-top-decoration';
20export const ANNOTATION_SELECTION_CLASS = 'cm-scroller-selection';
21export const ANNOTATION_DIAGNOSTIC_CLASS = 'cm-scroller-diagnostic';
22export const ANNOTATION_OCCURRENCE_CLASS = 'cm-scroller-occurrence';
23export const SHADOW_WIDTH = 10;
24export const SCROLLBAR_WIDTH = 12;
25export const ANNOTATION_WIDTH = SCROLLBAR_WIDTH / 2;
26export const MIN_ANNOTATION_HEIGHT = 1;
27
28function 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
81function 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
156export 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
1import { RangeSet, type TransactionSpec } from '@codemirror/state'; 7import { RangeSet, type TransactionSpec } from '@codemirror/state';
2import { Decoration } from '@codemirror/view'; 8import { 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
1import { configure } from 'mobx'; 7import { configure } from 'mobx';
2import { type Root, createRoot } from 'react-dom/client'; 8import { 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
1import type { EditorState } from '@codemirror/state'; 7import type { EditorState } from '@codemirror/state';
2import type { SyntaxNode } from '@lezer/common'; 8import 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
1import type { TreeIndentContext } from '@codemirror/language'; 8import 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
1import { 7import {
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
3import { NodeProp } from '@lezer/common'; 9import { 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
1import { 7import {
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';
12import { observer } from 'mobx-react-lite'; 17import { 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
1import { makeAutoObservable } from 'mobx'; 7import { makeAutoObservable } from 'mobx';
2 8
3export enum ThemePreference { 9export 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
1export default class CancelledError extends Error { 7export 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
1import TimeoutError from './TimeoutError'; 7import TimeoutError from './TimeoutError';
2import getLogger from './getLogger'; 8import 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
1import CancelledError from './CancelledError'; 7import CancelledError from './CancelledError';
2import PendingTask from './PendingTask'; 8import PendingTask from './PendingTask';
3import getLogger from './getLogger'; 9import 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
1export default class TimeoutError extends Error { 7export 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
1import styles, { type CSPair } from 'ansi-styles'; 7import styles, { type CSPair } from 'ansi-styles';
2import log from 'loglevel'; 8import log from 'loglevel';
3import prefix from 'loglevel-plugin-prefix'; 9import 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
1import { 7import {
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
3import { z } from 'zod'; 9import { 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
1import type { 7import 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
1import type EditorStore from '../editor/EditorStore'; 7import type EditorStore from '../editor/EditorStore';
2import type { IHighlightRange } from '../editor/semanticHighlighting'; 8import 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
1import type { Transaction } from '@codemirror/state'; 7import type { Transaction } from '@codemirror/state';
2import { debounce } from 'lodash-es'; 8import { debounce } from 'lodash-es';
3import ms from 'ms'; 9import 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
1import type { ChangeDesc, Transaction } from '@codemirror/state'; 7import type { ChangeDesc, Transaction } from '@codemirror/state';
2import { debounce } from 'lodash-es'; 8import { debounce } from 'lodash-es';
3import { nanoid } from 'nanoid'; 9import { 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
1import { 7import {
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
1import type { Diagnostic } from '@codemirror/lint'; 7import type { Diagnostic } from '@codemirror/lint';
2 8
3import type EditorStore from '../editor/EditorStore'; 9import 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
1import type { 7import 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
1import { createAtom, makeAutoObservable, observable } from 'mobx'; 7import { createAtom, makeAutoObservable, observable } from 'mobx';
2import ms from 'ms'; 8import ms from 'ms';
3import { nanoid } from 'nanoid'; 9import { 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
1import BackendConfig, { ENDPOINT } from './BackendConfig'; 7import BackendConfig, { ENDPOINT } from './BackendConfig';
2 8
3export default async function fetchBackendConfig(): Promise<BackendConfig> { 9export 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
1import ms from 'ms'; 7import ms from 'ms';
2import { actions, assign, createMachine, RaiseAction } from 'xstate'; 8import { actions, assign, createMachine } from 'xstate';
3 9
4const { raise } = actions; 10const { 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
3import { z } from 'zod'; 9import { 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
3import { z } from 'zod'; 9import { 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
1interface ImportMeta { 8interface 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
1declare module '*.grammar' { 8declare 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"`.
3declare module '@lezer/generator/rollup' { 10declare 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
1interface WindowControlsOverlayGeometryChangeEvent extends Event { 7interface 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
1import path from 'node:path'; 7import path from 'node:path';
2import { fileURLToPath } from 'node:url'; 8import { 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 @@
1plugins {
2 id 'refinery-java-library'
3 id 'refinery-xtext-conventions'
4}
5
6dependencies {
7 api project(':refinery-language')
8 api libs.xtext.ide
9 api libs.xtext.xbase.ide
10}
11
12def generateXtextLanguage = project(':refinery-language').tasks.named('generateXtextLanguage')
13
14for (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
7plugins {
8 id("tools.refinery.gradle.java-library")
9 id("tools.refinery.gradle.xtext-generated")
10}
11
12dependencies {
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 */
4package tools.refinery.language.ide; 10package 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 */
4package tools.refinery.language.ide; 10package 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 */
1package tools.refinery.language.ide.contentassist; 6package tools.refinery.language.ide.contentassist;
2 7
3import org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher; 8import 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 */
1package tools.refinery.language.ide.contentassist; 6package tools.refinery.language.ide.contentassist;
2 7
3import java.util.Objects; 8import 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 */
1package tools.refinery.language.ide.contentassist; 6package tools.refinery.language.ide.contentassist;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.ide.contentassist; 6package tools.refinery.language.ide.contentassist;
2 7
3import com.google.inject.Inject; 8import 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 */
4package tools.refinery.language.ide.contentassist.antlr; 10package 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 */
1package tools.refinery.language.ide.syntaxcoloring; 6package tools.refinery.language.ide.syntaxcoloring;
2 7
3import com.google.common.collect.ImmutableList; 8import 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
7Bundle-ClassPath: . 7Bundle-ClassPath: .
8Bundle-Vendor: %providerName 8Bundle-Vendor: %providerName
9Bundle-Localization: plugin 9Bundle-Localization: plugin
10Bundle-RequiredExecutionEnvironment: JavaSE-17 10Bundle-RequiredExecutionEnvironment: J2SE-1.5
11Export-Package: tools.refinery.language.model, 11Export-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1plugins {
2 id 'refinery-java-library'
3 id 'refinery-mwe2'
4 id 'refinery-sonarqube'
5}
6
7dependencies {
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
17sourceSets {
18 main {
19 java.srcDirs += ['src/main/emf-gen']
20 }
21}
22
23def 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
35for (taskName in ['compileJava', 'processResources', 'generateEclipseSourceFolders']) {
36 tasks.named(taskName) {
37 dependsOn generateEPackage
38 }
39}
40
41tasks.named('clean') {
42 delete 'src/main/emf-gen'
43}
44
45sonarqube.properties {
46 properties['sonar.exclusions'] += [
47 'src/main/emf-gen/**',
48 ]
49}
50
51eclipse.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
7import tools.refinery.gradle.utils.SonarPropertiesUtils
8
9plugins {
10 id("tools.refinery.gradle.java-library")
11 id("tools.refinery.gradle.mwe2")
12 id("tools.refinery.gradle.sonarqube")
13}
14
15dependencies {
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
25sourceSets {
26 main {
27 java.srcDir("src/main/emf-gen")
28 }
29}
30
31tasks {
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
57sonarqube.properties {
58 SonarPropertiesUtils.addToList(properties, "sonar.exclusions", "src/main/emf-gen/**")
59}
60
61eclipse.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
3bin.includes = .,\ 5bin.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
3pluginName = tools.refinery.language.model 5pluginName = tools.refinery.language.model
4providerName = refinery.tools 6providerName = 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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 */
1module tools.refinery.language.model.GenerateProblemModel 6module tools.refinery.language.model.GenerateProblemModel
2 7
3Workflow { 8Workflow {
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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
2
3SPDX-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 @@
1plugins {
2 id 'refinery-java-library'
3}
4
5dependencies {
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
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
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 */
1package tools.refinery.language.semantics.model; 6package tools.refinery.language.semantics.model;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.semantics.model.internal; 6package tools.refinery.language.semantics.model.internal;
2 7
3import org.eclipse.collections.api.factory.primitive.IntObjectMaps; 8import 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 */
1package tools.refinery.language.semantics.model.internal; 6package tools.refinery.language.semantics.model.internal;
2 7
3import tools.refinery.store.map.Cursor; 8import 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 */
1package tools.refinery.language.semantics.model.internal; 6package tools.refinery.language.semantics.model.internal;
2 7
3import org.eclipse.collections.api.LazyIntIterable; 8import 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 */
1package tools.refinery.language.semantics.model.internal; 6package tools.refinery.language.semantics.model.internal;
2 7
3import tools.refinery.store.representation.TruthValue; 8import 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 */
1package tools.refinery.language.semantics.model.internal; 6package tools.refinery.language.semantics.model.internal;
2 7
3import org.eclipse.collections.api.LazyIntIterable; 8import 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 */
1package tools.refinery.language.semantics.model.internal; 6package tools.refinery.language.semantics.model.internal;
2 7
3import org.eclipse.collections.api.LazyIntIterable; 8import 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 */
1package tools.refinery.language.semantics.model.tests; 6package tools.refinery.language.semantics.model.tests;
2 7
3import org.junit.jupiter.api.Test; 8import 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 @@
1plugins {
2 id 'refinery-java-application'
3 id 'refinery-xtext-conventions'
4}
5
6configurations {
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
18dependencies {
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
33def generateXtextLanguage = project(':refinery-language').tasks.named('generateXtextLanguage')
34
35for (taskName in ['compileJava', 'processResources']) {
36 tasks.named(taskName) {
37 dependsOn generateXtextLanguage
38 }
39}
40
41mainClassName = 'tools.refinery.language.web.ServerLauncher'
42
43// Enable JDK 19 preview features for virtual thread support.
44application {
45 applicationDefaultJvmArgs += '--enable-preview'
46}
47tasks.withType(JavaCompile) {
48 options.release = 19
49 options.compilerArgs += '--enable-preview'
50}
51tasks.withType(Test) {
52 jvmArgs += '--enable-preview'
53}
54
55tasks.named('jar') {
56 dependsOn project.configurations.webapp
57 from(project.configurations.webapp) {
58 into 'webapp'
59 }
60}
61
62tasks.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
75tasks.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
7plugins {
8 id("tools.refinery.gradle.java-application")
9 id("tools.refinery.gradle.xtext-generated")
10}
11
12val webapp: Configuration by configurations.creating {
13 isCanBeConsumed = false
14 isCanBeResolved = true
15}
16
17dependencies {
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
32application {
33 mainClass.set("tools.refinery.language.web.ServerLauncher")
34}
35
36tasks {
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 */
1package tools.refinery.language.web; 6package tools.refinery.language.web;
2 7
3import jakarta.servlet.*; 8import 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 */
4package tools.refinery.language.web; 10package tools.refinery.language.web;
5 11
6import org.eclipse.xtext.ide.ExecutorServiceProvider;
7import org.eclipse.xtext.web.server.XtextServiceDispatcher; 12import org.eclipse.xtext.web.server.XtextServiceDispatcher;
8import org.eclipse.xtext.web.server.model.IWebDocumentProvider; 13import org.eclipse.xtext.web.server.model.IWebDocumentProvider;
9import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; 14import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
10import org.eclipse.xtext.web.server.occurrences.OccurrencesService; 15import org.eclipse.xtext.web.server.occurrences.OccurrencesService;
11import tools.refinery.language.web.occurrences.ProblemOccurrencesService; 16import tools.refinery.language.web.occurrences.ProblemOccurrencesService;
12import tools.refinery.language.web.xtext.VirtualThreadExecutorServiceProvider;
13import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; 17import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher;
14import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; 18import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess;
15import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; 19import 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 */
4package tools.refinery.language.web; 10package 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 */
1package tools.refinery.language.web; 6package tools.refinery.language.web;
2 7
3import org.eclipse.xtext.util.DisposableRegistry; 8import 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 */
1package tools.refinery.language.web; 6package tools.refinery.language.web;
2 7
3import jakarta.servlet.*; 8import 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 */
4package tools.refinery.language.web; 10package tools.refinery.language.web;
@@ -13,6 +19,7 @@ import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletConta
13import org.eclipse.jetty.server.Server; 19import org.eclipse.jetty.server.Server;
14import org.eclipse.jetty.util.resource.Resource; 20import org.eclipse.jetty.util.resource.Resource;
15import org.eclipse.jetty.util.resource.ResourceFactory; 21import org.eclipse.jetty.util.resource.ResourceFactory;
22import org.eclipse.jetty.util.thread.QueuedThreadPool;
16import org.slf4j.Logger; 23import org.slf4j.Logger;
17import org.slf4j.LoggerFactory; 24import org.slf4j.LoggerFactory;
18import tools.refinery.language.web.config.BackendConfigServlet; 25import 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 @@
1package tools.refinery.language.web;
2
3import org.eclipse.jetty.server.Server;
4import org.eclipse.jetty.server.ServerConnector;
5import org.eclipse.jetty.util.thread.QueuedThreadPool;
6import org.eclipse.jetty.util.thread.ThreadPool;
7
8import java.net.InetSocketAddress;
9import java.time.Duration;
10import java.util.concurrent.ExecutorService;
11import java.util.concurrent.Executors;
12
13public 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 */
1package tools.refinery.language.web.config; 6package tools.refinery.language.web.config;
2 7
3import com.google.gson.annotations.SerializedName; 8import 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 */
1package tools.refinery.language.web.config; 6package tools.refinery.language.web.config;
2 7
3import com.google.gson.Gson; 8import 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 */
1package tools.refinery.language.web.occurrences; 6package tools.refinery.language.web.occurrences;
2 7
3import org.eclipse.emf.ecore.EObject; 8import 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 @@
1package tools.refinery.language.web.xtext;
2
3import org.eclipse.xtext.ide.ExecutorServiceProvider;
4import tools.refinery.language.web.VirtualThreadUtils;
5
6import java.util.concurrent.ExecutorService;
7
8public 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 */
1package tools.refinery.language.web.xtext.server; 6package tools.refinery.language.web.xtext.server;
2 7
3import java.util.Objects; 8import 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 */
1package tools.refinery.language.web.xtext.server; 6package tools.refinery.language.web.xtext.server;
2 7
3import tools.refinery.language.web.xtext.server.message.XtextWebResponse; 8import 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 */
1package tools.refinery.language.web.xtext.server; 6package tools.refinery.language.web.xtext.server;
2 7
3import java.io.Serial; 8import 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 */
1package tools.refinery.language.web.xtext.server; 6package tools.refinery.language.web.xtext.server;
2 7
3import java.util.Set; 8import 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 */
1package tools.refinery.language.web.xtext.server; 6package tools.refinery.language.web.xtext.server;
2 7
3import com.google.common.base.Strings; 8import 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 */
1package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
2 7
3import com.google.gson.annotations.SerializedName; 8import 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 */
1package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
2 7
3import java.util.Objects; 8import 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 */
1package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
2 7
3import java.util.Objects; 8import 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 */
1package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
2 7
3import java.util.Objects; 8import 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 */
1package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
2 7
3import java.util.Map; 8import 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 */
1package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
2 7
3public sealed interface XtextWebResponse permits XtextWebOkResponse,XtextWebErrorResponse,XtextWebPushMessage { 8public 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 */
1package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
2 7
3import org.eclipse.xtext.web.server.IServiceResult; 8import 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 */
1package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
2 7
3import org.eclipse.xtext.web.server.IServiceContext; 8import 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 */
1package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
2 7
3import java.util.ArrayList; 8import 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 */
1package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
2 7
3import org.eclipse.xtext.service.OperationCanceledManager; 8import 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 */
1package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
2 7
3import org.eclipse.xtext.web.server.IServiceContext; 8import 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 */
1package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
2 7
3import java.util.Map; 8import 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 */
1package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
2 7
3import java.util.HashMap; 8import 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 */
1package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
2 7
3public final class XtextStatusCode { 8public 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 */
1package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
2 7
3import com.google.gson.Gson; 8import com.google.gson.Gson;
4import com.google.gson.JsonIOException; 9import com.google.gson.JsonIOException;
5import com.google.gson.JsonParseException; 10import com.google.gson.JsonParseException;
6import org.eclipse.jetty.ee10.websocket.api.Session; 11import org.eclipse.jetty.websocket.api.Callback;
7import org.eclipse.jetty.ee10.websocket.api.StatusCode; 12import org.eclipse.jetty.websocket.api.Session;
8import org.eclipse.jetty.ee10.websocket.api.WriteCallback; 13import org.eclipse.jetty.websocket.api.StatusCode;
9import org.eclipse.jetty.ee10.websocket.api.annotations.*; 14import org.eclipse.jetty.websocket.api.annotations.*;
10import org.eclipse.xtext.resource.IResourceServiceProvider; 15import org.eclipse.xtext.resource.IResourceServiceProvider;
11import org.eclipse.xtext.web.server.ISession; 16import org.eclipse.xtext.web.server.ISession;
12import org.slf4j.Logger; 17import org.slf4j.Logger;
@@ -20,7 +25,7 @@ import tools.refinery.language.web.xtext.server.message.XtextWebResponse;
20import java.io.Reader; 25import java.io.Reader;
21 26
22@WebSocket 27@WebSocket
23public class XtextWebSocket implements WriteCallback, ResponseHandler { 28public 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 */
1package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
2 7
3import jakarta.servlet.ServletConfig; 8import 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 */
1package tools.refinery.language.web; 6package tools.refinery.language.web;
2 7
3import org.eclipse.jetty.ee10.servlet.ServletContextHandler; 8import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
4import org.eclipse.jetty.ee10.servlet.ServletHolder; 9import org.eclipse.jetty.ee10.servlet.ServletHolder;
5import org.eclipse.jetty.ee10.websocket.api.Session;
6import org.eclipse.jetty.ee10.websocket.api.StatusCode;
7import org.eclipse.jetty.ee10.websocket.api.annotations.WebSocket;
8import org.eclipse.jetty.ee10.websocket.api.exceptions.UpgradeException;
9import org.eclipse.jetty.ee10.websocket.client.ClientUpgradeRequest;
10import org.eclipse.jetty.ee10.websocket.client.WebSocketClient;
11import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; 10import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
12import org.eclipse.jetty.http.HttpHeader; 11import org.eclipse.jetty.http.HttpHeader;
13import org.eclipse.jetty.http.HttpStatus; 12import org.eclipse.jetty.http.HttpStatus;
14import org.eclipse.jetty.server.Server; 13import org.eclipse.jetty.server.Server;
14import org.eclipse.jetty.util.thread.QueuedThreadPool;
15import org.eclipse.jetty.websocket.api.Callback;
16import org.eclipse.jetty.websocket.api.Session;
17import org.eclipse.jetty.websocket.api.StatusCode;
18import org.eclipse.jetty.websocket.api.annotations.WebSocket;
19import org.eclipse.jetty.websocket.api.exceptions.UpgradeException;
20import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
21import org.eclipse.jetty.websocket.client.WebSocketClient;
15import org.eclipse.xtext.testing.GlobalRegistries; 22import org.eclipse.xtext.testing.GlobalRegistries;
16import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; 23import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento;
17import org.junit.jupiter.api.*; 24import org.junit.jupiter.api.AfterEach;
25import org.junit.jupiter.api.BeforeEach;
26import org.junit.jupiter.api.Test;
27import org.junit.jupiter.api.TestInfo;
18import org.junit.jupiter.params.ParameterizedTest; 28import org.junit.jupiter.params.ParameterizedTest;
19import org.junit.jupiter.params.provider.ValueSource; 29import org.junit.jupiter.params.provider.ValueSource;
20import tools.refinery.language.web.tests.WebSocketIntegrationTestClient; 30import 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 */
1package tools.refinery.language.web.tests; 6package tools.refinery.language.web.tests;
2 7
3import com.google.inject.Singleton; 8import com.google.inject.Singleton;
4import tools.refinery.language.web.xtext.VirtualThreadExecutorServiceProvider; 9import org.eclipse.xtext.ide.ExecutorServiceProvider;
5 10
6import java.util.ArrayList; 11import java.util.ArrayList;
7import java.util.List; 12import java.util.List;
8import java.util.concurrent.ExecutorService; 13import java.util.concurrent.ExecutorService;
9 14
10@Singleton 15@Singleton
11public class AwaitTerminationExecutorServiceProvider extends VirtualThreadExecutorServiceProvider { 16public 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 */
1package tools.refinery.language.web.tests; 6package tools.refinery.language.web.tests;
2 7
3import org.eclipse.xtext.ide.ExecutorServiceProvider; 8import 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 */
1package tools.refinery.language.web.tests; 6package tools.refinery.language.web.tests;
2 7
3import com.google.inject.Provider; 8import 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 */
1package tools.refinery.language.web.tests; 6package tools.refinery.language.web.tests;
2 7
3import org.eclipse.jetty.ee10.websocket.api.Session; 8import org.eclipse.jetty.websocket.api.Session;
4import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketClose; 9import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
5import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketConnect; 10import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
6import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketError; 11import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
7import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketMessage; 12import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
8 13
9import java.io.IOException;
10import java.time.Duration; 14import java.time.Duration;
11import java.util.ArrayList; 15import java.util.ArrayList;
12import java.util.List; 16import java.util.List;
@@ -14,7 +18,7 @@ import java.util.List;
14import static org.junit.jupiter.api.Assertions.fail; 18import static org.junit.jupiter.api.Assertions.fail;
15 19
16public abstract class WebSocketIntegrationTestClient { 20public 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 */
1package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
2 7
3import com.google.inject.Inject; 8import 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 @@
1plugins {
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
9dependencies {
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
20sourceSets {
21 testFixtures {
22 java.srcDirs += ['src/testFixtures/xtext-gen']
23 resources.srcDirs += ['src/testFixtures/xtext-gen']
24 }
25}
26
27tasks.named('jar') {
28 from(sourceSets.main.allSource) {
29 include '**/*.xtext'
30 }
31}
32
33def 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
47for (taskName in [
48 'compileJava',
49 'processResources',
50 'processTestFixturesResources',
51 'generateEclipseSourceFolders'
52 ]) {
53 tasks.named(taskName) {
54 dependsOn generateXtextLanguage
55 }
56}
57
58tasks.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
65sonarqube.properties {
66 properties['sonar.exclusions'] += [
67 'src/testFixtures/xtext-gen/**',
68 ]
69}
70
71eclipse.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
7import tools.refinery.gradle.utils.SonarPropertiesUtils
8
9plugins {
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
17val generatedIdeSources: Configuration by configurations.creating {
18 isCanBeConsumed = true
19 isCanBeResolved = false
20}
21
22val generatedWebSources: Configuration by configurations.creating {
23 isCanBeConsumed = true
24 isCanBeResolved = false
25}
26
27dependencies {
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
38sourceSets {
39 testFixtures {
40 java.srcDir("src/testFixtures/xtext-gen")
41 resources.srcDir("src/testFixtures/xtext-gen")
42 }
43}
44
45val 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
59tasks {
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
87artifacts {
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
97sonarqube.properties {
98 SonarPropertiesUtils.addToList(properties, "sonar.exclusions", "src/textFixtures/xtext-gen/**")
99}
100
101eclipse.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 */
1module tools.refinery.language.GenerateProblem 6module tools.refinery.language.GenerateProblem
2 7
3import org.eclipse.xtext.xtext.generator.* 8import 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 */
1grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals 6grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals
2 7
3import "http://www.eclipse.org/emf/2002/Ecore" as ecore 8import "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 */
4package tools.refinery.language; 10package 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 */
4package tools.refinery.language; 10package 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 */
1package tools.refinery.language.conversion; 6package tools.refinery.language.conversion;
2 7
3import org.eclipse.xtext.common.services.DefaultTerminalConverters; 8import 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 */
1package tools.refinery.language.conversion; 6package tools.refinery.language.conversion;
2 7
3import org.eclipse.xtext.conversion.ValueConverterException; 8import 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 */
4package tools.refinery.language.formatting2; 10package 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 */
1package tools.refinery.language.naming; 6package tools.refinery.language.naming;
2 7
3import java.util.regex.Pattern; 8import 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 */
1package tools.refinery.language.naming; 6package tools.refinery.language.naming;
2 7
3import org.eclipse.xtext.naming.IQualifiedNameConverter; 8import 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 */
1package tools.refinery.language.parser.antlr; 6package tools.refinery.language.parser.antlr;
2 7
3import com.google.inject.Inject; 8import 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 */
4package tools.refinery.language.parser.antlr; 10package 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 */
1package tools.refinery.language.parser.antlr; 6package tools.refinery.language.parser.antlr;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import org.eclipse.emf.ecore.EObject; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import com.google.common.collect.ImmutableSet; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import org.eclipse.emf.ecore.EObject; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import org.eclipse.emf.ecore.EObject; 8import 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 */
1package tools.refinery.language.resource; 6package tools.refinery.language.resource;
2 7
3import java.util.HashMap; 8import 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 */
1package tools.refinery.language.scoping; 6package tools.refinery.language.scoping;
2 7
3import java.util.LinkedHashSet; 8import 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 */
1package tools.refinery.language.scoping; 6package tools.refinery.language.scoping;
2 7
3import java.util.List; 8import 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 */
4package tools.refinery.language.scoping; 10package 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 */
1package tools.refinery.language.serializer; 6package tools.refinery.language.serializer;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import tools.refinery.language.model.problem.*; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import java.util.Map; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import tools.refinery.language.model.problem.PredicateKind; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3public record NodeInfo(String name, boolean individual) { 8public 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import org.eclipse.emf.common.util.URI; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import tools.refinery.language.model.problem.*; 8import 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 */
1package tools.refinery.language.utils; 6package tools.refinery.language.utils;
2 7
3import com.google.inject.Inject; 8import 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 */
4package tools.refinery.language.validation; 10package 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
1problem builtin. 4problem builtin.
2 5
3abstract class node { 6abstract 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 */
1package tools.refinery.language.tests; 6package tools.refinery.language.tests;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.formatting2; 6package tools.refinery.language.tests.formatting2;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.parser.antlr; 6package tools.refinery.language.tests.parser.antlr;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.parser.antlr; 6package tools.refinery.language.tests.parser.antlr;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.parser.antlr; 6package tools.refinery.language.tests.parser.antlr;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.rules; 6package tools.refinery.language.tests.rules;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.scoping; 6package tools.refinery.language.tests.scoping;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.serializer; 6package tools.refinery.language.tests.serializer;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.tests.utils; 6package tools.refinery.language.tests.utils;
2 7
3import com.google.inject.Inject; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import java.util.List; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import org.eclipse.xtext.testing.util.ParseHelper; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Action; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.*; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Assertion; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.AssertionArgument; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Atom; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.ClassDeclaration; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Conjunction; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Consequent; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.EnumDeclaration; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Atom; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.Parameter; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.PredicateDefinition; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import org.eclipse.emf.ecore.resource.Resource.Diagnostic; 8import 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 */
1package tools.refinery.language.model.tests.utils; 6package tools.refinery.language.model.tests.utils;
2 7
3import tools.refinery.language.model.problem.RuleDefinition; 8import 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
8This 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
10We 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
14This 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
20Eclipse VIATRA, and VIATRA are trademarks of the Eclipse Foundation.
21
22## Copyright
23
24All content is the property of the respective authors or their employers. For
25more information regarding authorship of content, please consult the listed
26source code repository logs.
27
28## Declared Project Licenses
29
30This program and the accompanying materials are made available under the terms
31of the Eclipse Public License v. 2.0 which is available at
32http://www.eclipse.org/legal/epl-v20.html.
33
34SPDX-License-Identifier: EPL-2.0
35
36## Source Code
37
38The 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
47This project leverages the following third party content.
48
49ANTLR Runtime only: (3.2)
50
51* License: New BSD license
52
53Apache Commons Language Library (2.1)
54
55* License: Apache License, 2.0
56
57Google Guice / Inject Core API (3.0.0)
58
59* License: Apache License, 2.0
60
61Google Guice / Inject Core API (3.0.0)
62
63* License: Apache License 2.0
64
65Guava (10.0.1)
66
67* License: Apache License, 2.0
68
69Guice (2.0)
70
71* License: Apache License, 2.0
72
73guice-multibindings (3.0.0)
74
75* License: Apache License, 2.0
76
77log4j (1.2.15)
78
79* License: Apache License 2.0
80
81LPG Java Runtime (lpgjavaruntime.jar) (1.1)
82
83* License: Eclipse Public License
84
85mockito (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 @@
1plugins {
2 id 'refinery-java-library'
3}
4
5configurations.testRuntimeClasspath {
6 // VIATRA requires log4j 1.x, but we use log4j-over-slf4j instead
7 exclude group: 'log4j', module: 'log4j'
8}
9
10dependencies {
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
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
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 @@
1package tools.refinery.store.query.viatra;
2
3import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
4import tools.refinery.store.model.ModelStoreBuilder;
5import tools.refinery.store.query.ModelQuery;
6import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl;
7
8public 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 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
3import tools.refinery.store.query.ModelQueryAdapter; 8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.viatra.internal.ViatraModelQueryBuilderImpl;
4 10
5public interface ViatraModelQueryAdapter extends ModelQueryAdapter { 11public 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 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
3import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 8import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
4import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 9import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
5import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 10import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
6import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
7import tools.refinery.store.query.DNF; 12import tools.refinery.store.query.dnf.AnyQuery;
13import tools.refinery.store.query.dnf.Dnf;
8import tools.refinery.store.query.ModelQueryBuilder; 14import tools.refinery.store.query.ModelQueryBuilder;
9 15
10import java.util.Collection; 16import 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 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
3import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 8import 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 @@
1package tools.refinery.store.query.viatra;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import tools.refinery.store.tuple.Tuple1;
5import tools.refinery.store.tuple.TupleLike;
6
7public 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 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.apache.log4j.Logger; 8import 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 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; 8import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
@@ -7,62 +12,92 @@ import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl;
7import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; 12import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 13import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
9import tools.refinery.store.model.Model; 14import tools.refinery.store.model.Model;
10import tools.refinery.store.query.DNF; 15import tools.refinery.store.model.ModelListener;
11import tools.refinery.store.query.ResultSet; 16import tools.refinery.store.query.resultset.AnyResultSet;
17import tools.refinery.store.query.resultset.EmptyResultSet;
18import tools.refinery.store.query.resultset.ResultSet;
19import tools.refinery.store.query.dnf.AnyQuery;
20import tools.refinery.store.query.dnf.FunctionalQuery;
21import tools.refinery.store.query.dnf.Query;
22import tools.refinery.store.query.dnf.RelationalQuery;
12import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 23import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
24import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher;
25import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
26import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher;
13 27
14import java.lang.invoke.MethodHandle; 28import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles; 29import java.lang.invoke.MethodHandles;
16import java.util.Collection; 30import java.util.Collection;
17import java.util.Collections; 31import java.util.Collections;
18import java.util.HashMap; 32import java.util.LinkedHashMap;
19import java.util.Map; 33import java.util.Map;
20 34
21public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { 35public 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 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.eclipse.viatra.query.runtime.api.IQuerySpecification; 8import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
4import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 9import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
5import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; 10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions;
6import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 11import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
7import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 12import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
8import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; 13import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory;
9import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 14import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
10import tools.refinery.store.model.ModelStore; 15import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.model.ModelStoreBuilder; 16import tools.refinery.store.model.ModelStoreBuilder;
12import tools.refinery.store.query.DNF; 17import tools.refinery.store.query.dnf.AnyQuery;
18import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; 19import tools.refinery.store.query.viatra.ViatraModelQueryBuilder;
14import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; 20import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction;
15import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; 21import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory;
22import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
23import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery;
16 24
17import java.util.Collections; 25import java.util.*;
18import java.util.LinkedHashMap;
19import java.util.Map;
20import java.util.function.Function; 26import java.util.function.Function;
21 27
22public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { 28public 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 */
1package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
2 7
3import org.eclipse.viatra.query.runtime.api.IQuerySpecification; 8import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
@@ -5,27 +10,34 @@ import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
5import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
6import tools.refinery.store.model.Model; 11import tools.refinery.store.model.Model;
7import tools.refinery.store.model.ModelStore; 12import tools.refinery.store.model.ModelStore;
8import tools.refinery.store.query.DNF; 13import tools.refinery.store.query.dnf.AnyQuery;
9import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; 14import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter;
10import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; 15import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
11import tools.refinery.store.query.view.AnyRelationView; 16import tools.refinery.store.query.view.AnySymbolView;
12 17
13import java.util.Collection; 18import java.util.*;
14import java.util.Map;
15 19
16public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { 20public 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 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
4import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
5import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
6import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality;
7import tools.refinery.store.representation.cardinality.UpperCardinalities;
8import tools.refinery.store.representation.cardinality.UpperCardinality;
9
10import java.util.stream.Stream;
11
12public 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 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; 8import 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 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; 8import 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 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; 8import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext;
4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 9import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
5import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; 10import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication;
6import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; 11import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey;
7import tools.refinery.store.query.view.AnyRelationView; 12import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper;
13import tools.refinery.store.query.view.AnySymbolView;
8 14
9import java.util.*; 15import 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 */
14public class RelationalQueryMetaContext extends AbstractQueryMetaContext { 20public 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 */
1package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.*; 8import org.eclipse.viatra.query.runtime.matchers.context.*;
@@ -8,9 +13,9 @@ import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
8import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; 13import org.eclipse.viatra.query.runtime.matchers.util.Accuracy;
9import tools.refinery.store.model.Model; 14import tools.refinery.store.model.Model;
10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 15import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
11import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; 16import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper;
12import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener; 17import tools.refinery.store.query.viatra.internal.update.ModelUpdateListener;
13import tools.refinery.store.query.view.AnyRelationView; 18import tools.refinery.store.query.view.AnySymbolView;
14 19
15import java.lang.reflect.InvocationTargetException; 20import java.lang.reflect.InvocationTargetException;
16import java.util.Iterator; 21import 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 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor;
14
15import 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 */
22abstract 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 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation;
14import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
15import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
16import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
17import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
18import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
19import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
20
21import java.util.Iterator;
22import java.util.List;
23import java.util.function.Function;
24
25/**
26 * @author Grill Balázs
27 * @since 1.4
28 *
29 */
30public 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 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
9import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
12import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
13import org.eclipse.viatra.query.runtime.matchers.util.Accuracy;
14
15import java.util.Optional;
16
17public 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 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation;
14import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
15import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
16import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple;
19import org.eclipse.viatra.query.runtime.matchers.util.Preconditions;
20
21import java.util.*;
22import java.util.function.Function;
23import java.util.stream.Collectors;
24
25/**
26 * @author Zoltan Ujhelyi
27 * @since 1.7
28 */
29public 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 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
9import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
11import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
12import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider;
13import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability;
14import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
15import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
16import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
17import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
18import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
19
20public 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 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
9import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
11import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
12import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler;
13import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
14import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
16
17class 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 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue;
9import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
10import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler;
11import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
12import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext;
13import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
14import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
15import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
16import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18
19import java.util.*;
20
21public 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
9import org.eclipse.viatra.query.runtime.matchers.backend.IUpdateable;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AbstractResultSet;
12import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
13
14public 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14class 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
10import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.query.dnf.FunctionalQuery;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
15import 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 */
27public 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.rete.index.Indexer;
10import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo;
13
14import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles;
16import java.lang.invoke.MethodType;
17
18final 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
10import org.jetbrains.annotations.Nullable;
11import tools.refinery.store.tuple.*;
12
13import java.util.Iterator;
14
15final 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher;
9import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
10import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
11
12public 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Iterator;
13
14class 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
10import org.eclipse.viatra.query.runtime.rete.index.Indexer;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.map.Cursors;
14import tools.refinery.store.query.dnf.RelationalQuery;
15import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
16import 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 */
28public 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12import 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 */
19class 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.Term;
10
11class 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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
5import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
6import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
7import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
8import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality;
9import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
14import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
15import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
17import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
19import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
20import tools.refinery.store.query.DNF;
21import tools.refinery.store.query.DNFAnd;
22import tools.refinery.store.query.DNFUtils;
23import tools.refinery.store.query.Variable;
24import tools.refinery.store.query.atom.*;
25import tools.refinery.store.query.view.AnyRelationView;
26
27import java.util.*;
28import java.util.function.Function;
29import java.util.stream.Collectors;
30
31public 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
9import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
11import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
12import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
13import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
14import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
16import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*;
17import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
18import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
19import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
20import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
21import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
22import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameterDirection;
23import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
24import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
25import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
26import tools.refinery.store.query.dnf.Dnf;
27import tools.refinery.store.query.dnf.DnfClause;
28import tools.refinery.store.query.dnf.SymbolicParameter;
29import tools.refinery.store.query.literal.*;
30import tools.refinery.store.query.term.ConstantTerm;
31import tools.refinery.store.query.term.StatefulAggregator;
32import tools.refinery.store.query.term.StatelessAggregator;
33import tools.refinery.store.query.term.Variable;
34import tools.refinery.store.query.view.AnySymbolView;
35import tools.refinery.store.util.CycleDetectingMapper;
36
37import java.util.*;
38import java.util.function.Function;
39import java.util.stream.Collectors;
40
41public 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
10import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
14import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
17import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
19import tools.refinery.store.query.Constraint;
20import tools.refinery.store.query.dnf.Dnf;
21import tools.refinery.store.query.dnf.DnfUtils;
22import tools.refinery.store.query.literal.AbstractCallLiteral;
23import tools.refinery.store.query.term.ParameterDirection;
24import tools.refinery.store.query.term.Variable;
25import tools.refinery.store.query.view.AnySymbolView;
26import tools.refinery.store.query.view.SymbolView;
27import tools.refinery.store.util.CycleDetectingMapper;
28
29import java.util.*;
30import java.util.function.ToIntFunction;
31
32class 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 */
1package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
2 7
3import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; 8import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
@@ -9,6 +14,7 @@ import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery;
9import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; 14import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; 15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
11import tools.refinery.store.query.viatra.internal.RelationalScope; 16import tools.refinery.store.query.viatra.internal.RelationalScope;
17import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
12 18
13import java.util.LinkedHashSet; 19import java.util.LinkedHashSet;
14import java.util.List; 20import 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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher;
4import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
5import tools.refinery.store.query.ResultSet;
6import tools.refinery.store.query.viatra.ViatraTupleLike;
7import tools.refinery.store.tuple.Tuple;
8import tools.refinery.store.tuple.TupleLike;
9
10import java.util.Optional;
11import java.util.stream.Stream;
12
13public 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatefulAggregate;
10import tools.refinery.store.query.term.StatefulAggregator;
11
12import java.util.stream.Stream;
13
14record 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatelessAggregator;
10
11import java.util.stream.Stream;
12
13record 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 */
1package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; 8import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper;
4import tools.refinery.store.query.view.AnyRelationView; 9import tools.refinery.store.query.view.AnySymbolView;
5 10
6public class RelationViewWrapper extends BaseInputKeyWrapper<AnyRelationView> { 11public 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator;
9import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
10import tools.refinery.store.query.term.Term;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.stream.Collectors;
14
15class 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 */
6package tools.refinery.store.query.viatra.internal.pquery;
7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.valuation.Valuation;
11
12public 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 */
1package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
4import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; 9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
5import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
6import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 11import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
7import tools.refinery.store.query.view.AnyRelationView; 12import tools.refinery.store.query.view.AnySymbolView;
8import tools.refinery.store.query.view.RelationView; 13import tools.refinery.store.query.view.SymbolView;
9 14
10import java.util.HashMap; 15import java.util.HashMap;
11import java.util.Map; 16import java.util.Map;
12 17
13public class ModelUpdateListener { 18public 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 */
1package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
2 7
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import 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 @@
1package tools.refinery.store.query.viatra.internal.update;
2
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
4import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
5import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
6import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
7import tools.refinery.store.model.InterpretationListener;
8import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
9import tools.refinery.store.query.view.RelationView;
10import tools.refinery.store.query.view.TuplePreservingRelationView;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public 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 */
6package tools.refinery.store.query.viatra.internal.update;
7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
11import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.model.InterpretationListener;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
15import tools.refinery.store.query.view.SymbolView;
16import tools.refinery.store.query.view.TuplePreservingView;
17
18import java.util.ArrayList;
19import java.util.List;
20
21public 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 @@
1package tools.refinery.store.query.viatra.internal.update;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
4import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
5import tools.refinery.store.query.view.RelationView;
6import tools.refinery.store.tuple.Tuple;
7
8import java.util.Arrays;
9
10public 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 */
6package tools.refinery.store.query.viatra.internal.update;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
11import tools.refinery.store.query.view.SymbolView;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Arrays;
15
16public 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 */
1package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
2 7
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 8import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation;
4import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
5import tools.refinery.store.query.view.TuplePreservingRelationView; 11import tools.refinery.store.query.view.TuplePreservingView;
6import tools.refinery.store.tuple.Tuple; 12import tools.refinery.store.tuple.Tuple;
7 13
8public class TuplePreservingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { 14public 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 */
6package tools.refinery.store.query.viatra;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.viatra.tests.QueryEngineTest;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.FunctionView;
16import tools.refinery.store.query.view.KeyOnlyView;
17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.List;
21import java.util.Map;
22import java.util.Optional;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
26import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
27import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
28
29class 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 */
6package tools.refinery.store.query.viatra;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.viatra.tests.QueryEngineTest;
15import tools.refinery.store.query.view.AnySymbolView;
16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
19import tools.refinery.store.representation.Symbol;
20import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.tuple.Tuple;
22
23import java.util.List;
24import java.util.Map;
25import java.util.Optional;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.is;
29import static org.hamcrest.Matchers.nullValue;
30import static org.junit.jupiter.api.Assertions.assertAll;
31import static org.junit.jupiter.api.Assertions.assertThrows;
32import static tools.refinery.store.query.literal.Literals.assume;
33import static tools.refinery.store.query.term.int_.IntTerms.*;
34import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
35import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
36
37class 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 */
6package tools.refinery.store.query.viatra;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.resultset.OrderedResultSet;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.is;
21
22class 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 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
3import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.DNF; 11import tools.refinery.store.query.ModelQueryAdapter;
6import tools.refinery.store.query.ModelQuery; 12import tools.refinery.store.query.dnf.Query;
7import tools.refinery.store.query.Variable; 13import tools.refinery.store.query.term.Variable;
8import tools.refinery.store.query.atom.*; 14import tools.refinery.store.query.viatra.tests.QueryEngineTest;
9import tools.refinery.store.query.view.FilteredRelationView; 15import tools.refinery.store.query.view.AnySymbolView;
10import tools.refinery.store.query.view.KeyOnlyRelationView; 16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
11import tools.refinery.store.representation.Symbol; 19import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.representation.TruthValue; 20import tools.refinery.store.representation.TruthValue;
13import tools.refinery.store.tuple.Tuple; 21import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.TupleLike;
15 22
16import java.util.HashSet; 23import java.util.List;
17import java.util.Set; 24import java.util.Map;
18import java.util.stream.Stream;
19 25
20import static org.junit.jupiter.api.Assertions.assertEquals; 26import static tools.refinery.store.query.literal.Literals.assume;
27import static tools.refinery.store.query.literal.Literals.not;
28import static tools.refinery.store.query.term.int_.IntTerms.constant;
29import static tools.refinery.store.query.term.int_.IntTerms.greaterEq;
30import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
21 31
22class QueryTest { 32class 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 */
1package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
2 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Disabled;
3import org.junit.jupiter.api.Test; 10import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.DNF; 12import tools.refinery.store.query.ModelQueryAdapter;
6import tools.refinery.store.query.ModelQuery; 13import tools.refinery.store.query.dnf.Query;
7import tools.refinery.store.query.Variable; 14import tools.refinery.store.query.dnf.RelationalQuery;
8import tools.refinery.store.query.atom.RelationViewAtom; 15import tools.refinery.store.query.view.AnySymbolView;
9import tools.refinery.store.query.view.KeyOnlyRelationView; 16import tools.refinery.store.query.view.FilteredView;
17import tools.refinery.store.query.view.FunctionView;
18import tools.refinery.store.query.view.KeyOnlyView;
10import tools.refinery.store.representation.Symbol; 19import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple; 20import tools.refinery.store.tuple.Tuple;
12 21
13import static org.junit.jupiter.api.Assertions.*; 22import java.util.Map;
23import java.util.Optional;
24
25import static org.junit.jupiter.api.Assertions.assertFalse;
26import static org.junit.jupiter.api.Assertions.assertTrue;
27import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
28import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
14 29
15class QueryTransactionTest { 30class 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 @@
1package tools.refinery.store.query.viatra.internal.cardinality;
2
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.Test;
5import tools.refinery.store.representation.cardinality.UpperCardinalities;
6import tools.refinery.store.representation.cardinality.UpperCardinality;
7
8import static org.hamcrest.MatcherAssert.assertThat;
9import static org.hamcrest.Matchers.equalTo;
10
11class 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 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.*;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.tuple.*;
12
13import java.util.List;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.*;
17import static org.junit.jupiter.api.Assertions.assertThrows;
18
19class 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 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.junit.jupiter.api.function.Executable;
9import tools.refinery.store.query.resultset.ResultSet;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.*;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16import static org.hamcrest.Matchers.nullValue;
17import static org.junit.jupiter.api.Assertions.assertAll;
18
19public 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 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9
10/**
11 * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names.
12 */
13class 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 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.ArgumentsSource;
10
11import java.lang.annotation.ElementType;
12import java.lang.annotation.Retention;
13import java.lang.annotation.RetentionPolicy;
14import java.lang.annotation.Target;
15
16@ParameterizedTest(name = "backend = {0}")
17@ArgumentsSource(QueryEvaluationHintSource.class)
18@Target(ElementType.METHOD)
19@Retention(RetentionPolicy.RUNTIME)
20public @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 */
6package tools.refinery.store.query.viatra.tests;
7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.extension.ExtensionContext;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.ArgumentsProvider;
12
13import java.util.stream.Stream;
14
15public 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
7plugins {
8 id("tools.refinery.gradle.java-library")
9 id("tools.refinery.gradle.java-test-fixtures")
10}
11
12dependencies {
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 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.literal.*;
10import tools.refinery.store.query.term.*;
11
12import java.util.List;
13
14public 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 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.query.dnf.AnyQuery;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AnyResultSet;
12import tools.refinery.store.query.resultset.ResultSet;
13
14public 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 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.AnyQuery;
11
12import java.util.Collection;
13import java.util.List;
14
15@SuppressWarnings("UnusedReturnValue")
16public 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 */
6package tools.refinery.store.query;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.view.AnySymbolView;
12
13import java.util.Collection;
14
15public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Set;
17
18public 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 */
6package tools.refinery.store.query.dnf;
7
8public 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 */
6package tools.refinery.store.query.dnf;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.query.literal.BooleanLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.MapBasedSubstitution;
13import tools.refinery.store.query.substitution.StatelessSubstitution;
14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.NodeVariable;
16import tools.refinery.store.query.term.ParameterDirection;
17import tools.refinery.store.query.term.Variable;
18
19import java.util.*;
20import java.util.function.Function;
21
22class 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.Reduction;
10import tools.refinery.store.query.equality.DnfEqualityChecker;
11import tools.refinery.store.query.equality.LiteralEqualityHelper;
12import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.Collection;
16import java.util.Collections;
17import java.util.List;
18import java.util.Set;
19import java.util.function.Consumer;
20import java.util.stream.Collectors;
21
22public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.*;
16
17@SuppressWarnings("UnusedReturnValue")
18public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.List;
13import java.util.Set;
14
15public 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 @@
1package 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 */
6package tools.refinery.store.query.dnf;
2 7
3import java.util.UUID; 8import java.util.UUID;
4 9
5public final class DNFUtils { 10public 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 @@
1package 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 */
6package tools.refinery.store.query.dnf;
2 7
3import java.util.HashSet; 8import java.util.HashSet;
4import java.util.Set; 9import 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.term.Aggregator;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.ArrayList;
15import java.util.List;
16import java.util.Objects;
17
18public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.DataVariable;
9
10public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Objects;
13
14public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.DataVariable;
9
10public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.literal.CallLiteral;
9import tools.refinery.store.query.literal.CallPolarity;
10import tools.refinery.store.query.term.AssignedValue;
11import tools.refinery.store.query.term.NodeVariable;
12
13import java.util.Collections;
14import java.util.List;
15
16public 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 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Objects;
13
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9
10import java.util.Collection;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12import java.util.Collection;
13
14@FunctionalInterface
15public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.literal.Literal;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collection;
12
13@FunctionalInterface
14public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10
11@FunctionalInterface
12public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.FunctionalQueryBuilder;
9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.term.NodeVariable;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9
10@FunctionalInterface
11public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public 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 */
6package tools.refinery.store.query.dnf.callback;
7
8import tools.refinery.store.query.dnf.QueryBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public 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 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.util.CycleDetectingMapper;
13
14import java.util.List;
15
16public 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 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9
10@FunctionalInterface
11public 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 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15
16public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.*;
15
16public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.*;
12
13import java.util.List;
14import java.util.Objects;
15import java.util.Set;
16
17public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collections;
15import java.util.Objects;
16import java.util.Set;
17
18public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.Collections;
15import java.util.Objects;
16import java.util.Set;
17
18public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
13
14public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.*;
15
16public 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 @@
1package 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 */
6package tools.refinery.store.query.literal;
2 7
3public enum CallPolarity { 8public 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 */
6package tools.refinery.store.query.literal;
7
8public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.Objects;
14import java.util.Set;
15
16public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14
15import java.util.List;
16import java.util.Objects;
17import java.util.Set;
18
19public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable;
12
13import java.util.Objects;
14import java.util.Set;
15
16public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Set;
13
14public 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 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.term.Term;
9
10public 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 */
6package tools.refinery.store.query.literal;
7
8public 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 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.ArrayList;
13import java.util.List;
14
15public 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 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.dnf.AnyQuery;
10
11public 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 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.tuple.Tuple;
13
14public 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 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.ModelQueryAdapter;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.utils.OrderStatisticTree;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16public 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 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.query.resultset;
7
8import tools.refinery.store.tuple.Tuple;
9
10@FunctionalInterface
11public 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 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Map;
11
12public 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 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.HashMap;
11import java.util.Map;
12
13public 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 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.Variable;
9
10public 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 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.DataVariable;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11
12@FunctionalInterface
13public 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 */
6package tools.refinery.store.query.substitution;
7
8import tools.refinery.store.query.term.DataVariable;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17@SuppressWarnings("UnusedReturnValue")
18public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9
10import java.util.Objects;
11
12public 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 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public 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 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10
11import java.util.Optional;
12import java.util.Set;
13
14public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10
11import java.util.Set;
12
13public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.literal.Literal;
9
10@FunctionalInterface
11public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.Objects;
15import java.util.Set;
16
17public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Objects;
13import java.util.Set;
14
15public 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 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.valuation.Valuation;
13
14import java.util.Objects;
15
16public 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 */
6package tools.refinery.store.query.term;
7
8import java.util.Comparator;
9import java.util.Objects;
10import java.util.SortedMap;
11import java.util.TreeMap;
12
13public 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 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.literal.ConstantLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral;
11
12import java.util.Optional;
13
14public 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 */
6package tools.refinery.store.query.term;
7
8import java.util.Objects;
9import java.util.Optional;
10
11public 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 */
6package tools.refinery.store.query.term;
7
8public 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 */
6package tools.refinery.store.query.term;
7
8public 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 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public 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 */
6package tools.refinery.store.query.term;
7
8import java.util.stream.Stream;
9
10public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.literal.AssignLiteral;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.valuation.Valuation;
12
13public 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 */
6package tools.refinery.store.query.term;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation;
11
12import java.util.Objects;
13import java.util.Set;
14
15public 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 */
6package tools.refinery.store.query.term;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.dnf.DnfUtils;
10
11import java.util.Objects;
12import java.util.Optional;
13
14public 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 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public 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 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.term.ConstantTerm;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.bool;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.comparable;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.StatelessAggregator;
9
10public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13
14import java.util.Comparator;
15
16public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.UnaryTerm;
10
11public 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 */
6package tools.refinery.store.query.term.int_;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.query.term.UnaryTerm;
11
12public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10
11public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.StatefulAggregate;
9import tools.refinery.store.query.term.StatefulAggregator;
10
11import java.util.Map;
12import java.util.TreeMap;
13
14public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13
14import java.util.Comparator;
15
16public 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 */
6package tools.refinery.store.query.term.real;
7
8import tools.refinery.store.query.term.Term;
9import tools.refinery.store.query.term.UnaryTerm;
10
11public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.BinaryTerm;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.Term;
10import tools.refinery.store.representation.cardinality.UpperCardinality;
11
12public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.StatefulAggregate;
9import tools.refinery.store.query.term.StatefulAggregator;
10import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
11import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality;
12import tools.refinery.store.representation.cardinality.UpperCardinalities;
13import tools.refinery.store.representation.cardinality.UpperCardinality;
14
15public 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import tools.refinery.store.query.term.Aggregator;
9import tools.refinery.store.query.term.ConstantTerm;
10import tools.refinery.store.query.term.ExtremeValueAggregator;
11import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.comparable.*;
13import tools.refinery.store.representation.cardinality.UpperCardinalities;
14import tools.refinery.store.representation.cardinality.UpperCardinality;
15
16import java.util.Comparator;
17
18public 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 */
7package tools.refinery.store.query.utils;
8
9import 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 */
25public 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 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Map;
12
13record 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 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Set;
12
13public 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 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.query.term.DataVariable;
10
11public 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 */
6package tools.refinery.store.query.valuation;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.AnyDataVariable;
11import tools.refinery.store.query.term.DataVariable;
12
13import java.util.Map;
14import java.util.Set;
15
16public 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 */
6package tools.refinery.store.query.valuation;
7
8import tools.refinery.store.query.term.AnyDataVariable;
9import tools.refinery.store.query.term.DataVariable;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.Map;
14
15public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.query.term.Parameter;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13import tools.refinery.store.tuple.Tuple1;
14
15import java.util.Arrays;
16import java.util.List;
17import java.util.Objects;
18import java.util.Set;
19import java.util.stream.Collectors;
20import java.util.stream.IntStream;
21
22public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.representation.AnySymbol;
11import tools.refinery.store.query.Constraint;
12
13import java.util.Set;
14
15public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.Objects;
12import java.util.function.BiPredicate;
13import java.util.function.Predicate;
14
15public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.query.term.*;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.ArrayList;
12import java.util.List;
13
14public 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 */
1package tools.refinery.store.query.view; 6package tools.refinery.store.query.view;
2 7
3import tools.refinery.store.representation.Symbol; 8import tools.refinery.store.representation.Symbol;
@@ -5,19 +10,19 @@ import tools.refinery.store.tuple.Tuple;
5 10
6import java.util.Objects; 11import java.util.Objects;
7 12
8public final class KeyOnlyRelationView<T> extends TuplePreservingRelationView<T> { 13public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple1;
11
12public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.map.CursorAsIterator;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.Objects;
14import 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 */
22public 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 */
6package tools.refinery.store.query.view;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Parameter;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12import tools.refinery.store.tuple.Tuple1;
13
14import java.util.Arrays;
15import java.util.List;
16import java.util.Objects;
17
18public 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 */
6package tools.refinery.store.query.view;
7
8import java.util.List;
9
10public 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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.store.query.literal.BooleanLiteral;
12import tools.refinery.store.query.term.NodeVariable;
13import tools.refinery.store.query.term.ParameterDirection;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.query.term.bool.BoolTerms;
16import tools.refinery.store.query.view.KeyOnlyView;
17import tools.refinery.store.query.view.SymbolView;
18import tools.refinery.store.representation.Symbol;
19
20import java.util.List;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static tools.refinery.store.query.literal.Literals.assume;
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
26
27class 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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable;
11import tools.refinery.store.query.view.KeyOnlyView;
12import tools.refinery.store.query.view.SymbolView;
13import tools.refinery.store.representation.Symbol;
14
15import java.util.List;
16
17import static org.hamcrest.MatcherAssert.assertThat;
18import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
19
20class 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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.view.AnySymbolView;
13import tools.refinery.store.query.view.KeyOnlyView;
14import tools.refinery.store.representation.Symbol;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.is;
18import static tools.refinery.store.query.literal.Literals.not;
19
20class 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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.view.AnySymbolView;
13import tools.refinery.store.query.view.KeyOnlyView;
14import tools.refinery.store.representation.Symbol;
15
16import java.util.List;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20import static tools.refinery.store.query.literal.Literals.not;
21import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
22
23class 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 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11import tools.refinery.store.query.literal.BooleanLiteral;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.NodeVariable;
15import tools.refinery.store.query.term.ParameterDirection;
16import tools.refinery.store.query.term.Variable;
17import tools.refinery.store.query.view.AnySymbolView;
18import tools.refinery.store.query.view.FunctionView;
19import tools.refinery.store.query.view.KeyOnlyView;
20import tools.refinery.store.representation.Symbol;
21
22import java.util.ArrayList;
23import java.util.List;
24import java.util.stream.Stream;
25
26import static org.hamcrest.MatcherAssert.assertThat;
27import static org.hamcrest.Matchers.hasItem;
28import static org.hamcrest.Matchers.not;
29import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
30import static org.junit.jupiter.api.Assertions.assertThrows;
31import static tools.refinery.store.query.literal.Literals.assume;
32import static tools.refinery.store.query.literal.Literals.not;
33import static tools.refinery.store.query.term.int_.IntTerms.*;
34
35class 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 */
6package tools.refinery.store.query.literal;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.term.*;
12
13import java.util.List;
14import java.util.Set;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.containsInAnyOrder;
18import static org.hamcrest.Matchers.empty;
19import static org.junit.jupiter.api.Assertions.assertAll;
20import static org.junit.jupiter.api.Assertions.assertThrows;
21import static tools.refinery.store.query.literal.Literals.not;
22import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
23import static tools.refinery.store.query.term.int_.IntTerms.constant;
24
25class 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 */
6package tools.refinery.store.query.literal;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Parameter;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.List;
16import java.util.Set;
17
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.hamcrest.Matchers.containsInAnyOrder;
20import static org.hamcrest.Matchers.empty;
21import static org.junit.jupiter.api.Assertions.assertAll;
22import static tools.refinery.store.query.literal.Literals.not;
23
24class 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 */
6package tools.refinery.store.query.term;
7
8import org.junit.jupiter.api.Assertions;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.equality.LiteralEqualityHelper;
14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.bool.BoolTerms;
16import tools.refinery.store.query.term.int_.IntTerms;
17import tools.refinery.store.query.term.real.RealTerms;
18import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms;
19import tools.refinery.store.representation.cardinality.UpperCardinality;
20
21import java.util.List;
22import java.util.stream.Stream;
23
24class 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 */
6package tools.refinery.store.query.term.bool;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.CsvSource;
10import tools.refinery.store.query.valuation.Valuation;
11
12import static org.hamcrest.MatcherAssert.assertThat;
13import static org.hamcrest.Matchers.is;
14
15class 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 */
6package tools.refinery.store.query.term.int_;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.CsvSource;
11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.store.query.term.real.RealTerms;
13import tools.refinery.store.query.valuation.Valuation;
14
15import java.util.stream.Stream;
16
17import static org.hamcrest.Matchers.is;
18import static org.hamcrest.MatcherAssert.assertThat;
19
20class 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 */
6package tools.refinery.store.query.term.real;
7
8import org.hamcrest.Matcher;
9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.CsvSource;
11import tools.refinery.store.query.term.int_.IntTerms;
12import tools.refinery.store.query.valuation.Valuation;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.*;
16
17class 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 @@
1package 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 */
6package tools.refinery.store.query.term.uppercardinality;
2 7
3import org.junit.jupiter.params.ParameterizedTest; 8import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.Arguments; 9import org.junit.jupiter.params.provider.Arguments;
@@ -10,14 +15,14 @@ import java.util.List;
10import java.util.stream.Stream; 15import java.util.stream.Stream;
11 16
12import static org.hamcrest.MatcherAssert.assertThat; 17import static org.hamcrest.MatcherAssert.assertThat;
13import static org.hamcrest.Matchers.equalTo; 18import static org.hamcrest.Matchers.is;
14 19
15class UpperCardinalitySumAggregationOperatorStreamTest { 20class 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.query.term.StatefulAggregate;
11import tools.refinery.store.representation.cardinality.UpperCardinalities;
12import tools.refinery.store.representation.cardinality.UpperCardinality;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16
17class 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 */
6package tools.refinery.store.query.term.uppercardinality;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11import tools.refinery.store.query.valuation.Valuation;
12import tools.refinery.store.representation.cardinality.UpperCardinalities;
13import tools.refinery.store.representation.cardinality.UpperCardinality;
14
15import java.util.stream.Stream;
16
17import static org.hamcrest.MatcherAssert.assertThat;
18import static org.hamcrest.Matchers.is;
19
20class 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 */
6package tools.refinery.store.query.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17
18import java.util.List;
19
20import static org.hamcrest.CoreMatchers.containsString;
21import static org.hamcrest.MatcherAssert.assertThat;
22import static org.hamcrest.Matchers.allOf;
23import static org.junit.jupiter.api.Assertions.assertThrows;
24import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
25
26class 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 */
6package tools.refinery.store.query.tests;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.view.AnySymbolView;
14import tools.refinery.store.query.view.KeyOnlyView;
15import tools.refinery.store.representation.Symbol;
16
17import static org.hamcrest.CoreMatchers.containsString;
18import static org.hamcrest.MatcherAssert.assertThat;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
21
22class 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 */
7package tools.refinery.store.query.utils;
8
9import org.junit.jupiter.api.BeforeEach;
10import org.junit.jupiter.api.Test;
11import org.junit.jupiter.params.ParameterizedTest;
12import org.junit.jupiter.params.provider.Arguments;
13import org.junit.jupiter.params.provider.MethodSource;
14
15import java.util.*;
16import java.util.stream.Stream;
17
18import 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 */
33class 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 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
12import tools.refinery.store.query.literal.Literal;
13
14import java.util.List;
15
16class 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 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Matcher;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.SymbolicParameter;
11import tools.refinery.store.query.literal.Literal;
12
13import java.util.List;
14
15public 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 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
12
13public 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 */
6package tools.refinery.store.query.tests;
7
8import org.hamcrest.Description;
9import org.hamcrest.TypeSafeMatcher;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.dnf.SymbolicParameter;
12import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
13import tools.refinery.store.query.literal.Literal;
14
15import java.util.List;
16
17public 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
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
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 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
9
10public 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 */
6package tools.refinery.store.reasoning;
7
8public 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 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.reasoning.representation.PartialSymbol;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.query.resultset.ResultSet;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.representation.PartialSymbol;
14
15public 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 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.reasoning.literal.Modality;
11import tools.refinery.store.query.dnf.Dnf;
12
13import java.util.Collection;
14import java.util.List;
15
16@SuppressWarnings("UnusedReturnValue")
17public 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 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11import tools.refinery.store.query.dnf.Dnf;
12
13import java.util.Collection;
14
15public 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 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.PartialInterpretation;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.resultset.ResultSet;
14
15public 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 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.reasoning.ReasoningBuilder;
12import tools.refinery.store.reasoning.literal.Modality;
13
14public 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 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.reasoning.ReasoningStoreAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
12import tools.refinery.store.query.dnf.Dnf;
13
14import java.util.Collection;
15
16public 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 */
6package tools.refinery.store.reasoning.lifting;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.DnfBuilder;
11import tools.refinery.store.query.dnf.DnfClause;
12import tools.refinery.store.query.literal.CallLiteral;
13import tools.refinery.store.query.literal.CallPolarity;
14import tools.refinery.store.query.literal.Literal;
15import tools.refinery.store.query.term.NodeVariable;
16import tools.refinery.store.query.term.Variable;
17import tools.refinery.store.reasoning.ReasoningAdapter;
18import tools.refinery.store.reasoning.literal.ModalConstraint;
19import tools.refinery.store.reasoning.literal.Modality;
20import tools.refinery.store.reasoning.literal.PartialLiterals;
21import tools.refinery.store.util.CycleDetectingMapper;
22
23import java.util.ArrayList;
24import java.util.LinkedHashSet;
25import java.util.List;
26
27public 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 */
6package tools.refinery.store.reasoning.lifting;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.reasoning.literal.Modality;
10
11record 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Reduction;
11import tools.refinery.store.query.term.Parameter;
12
13import java.util.List;
14
15public 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.literal.CallPolarity;
9
10import java.util.Locale;
11
12public 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.literal.CallLiteral;
9
10public 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 */
6package tools.refinery.store.reasoning.representation;
7
8public 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 */
6package tools.refinery.store.reasoning.representation;
7
8import tools.refinery.store.representation.AnyAbstractDomain;
9
10public 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 */
6package tools.refinery.store.reasoning.representation;
7
8import tools.refinery.store.representation.AbstractDomain;
9
10public 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 */
6package tools.refinery.store.reasoning.representation;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.term.Parameter;
10import tools.refinery.store.representation.AbstractDomain;
11import tools.refinery.store.representation.TruthValue;
12import tools.refinery.store.representation.TruthValueDomain;
13
14import java.util.Arrays;
15import java.util.List;
16
17public 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 */
6package tools.refinery.store.reasoning.representation;
7
8import tools.refinery.store.representation.AbstractDomain;
9
10public 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 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.tuple.Tuple;
10
11public 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 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.tuple.Tuple;
10
11public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.literal.Literal;
13
14import java.util.*;
15
16public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.representation.TruthValue;
9
10public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.literal.CallPolarity;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.literal.Modality;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.representation.TruthValue;
16
17import java.util.List;
18
19public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningBuilder;
10
11import java.util.Collection;
12
13public 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 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.resultset.ResultSet;
11import tools.refinery.store.reasoning.MergeResult;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.ReasoningAdapter;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.tuple.Tuple;
17
18public 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 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.seed.Seed;
11import tools.refinery.store.reasoning.seed.UniformSeed;
12import tools.refinery.store.reasoning.translator.TranslatedRelation;
13import tools.refinery.store.reasoning.translator.TranslationUnit;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16
17import java.util.Collection;
18import java.util.List;
19
20public 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 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.literal.CallPolarity;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.ReasoningBuilder;
14import tools.refinery.store.reasoning.literal.Modality;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.translator.Advice;
17import tools.refinery.store.reasoning.translator.TranslatedRelation;
18import tools.refinery.store.representation.Symbol;
19import tools.refinery.store.representation.TruthValue;
20
21import java.util.List;
22
23class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10record 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10
11import java.util.HashSet;
12import java.util.LinkedHashSet;
13import java.util.Objects;
14import java.util.Set;
15
16final 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.query.view.TuplePreservingView;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Objects;
13
14class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.query.view.TuplePreservingView;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Objects;
13
14class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10import java.util.Collections;
11import java.util.Set;
12
13record 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.*;
12
13final 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8sealed 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10import java.util.*;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslatedRelation;
11import tools.refinery.store.reasoning.translator.TranslationUnit;
12import tools.refinery.store.representation.Symbol;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Map;
17
18public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10import java.util.*;
11
12public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10
11import java.util.Set;
12
13import static org.hamcrest.MatcherAssert.assertThat;
14import static org.hamcrest.Matchers.is;
15
16class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.representation.TruthValue;
12
13import java.util.LinkedHashMap;
14import java.util.Set;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.is;
18import static org.junit.jupiter.api.Assertions.assertAll;
19
20class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.TruthValue;
11
12import java.util.LinkedHashMap;
13import java.util.Set;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.is;
17import static org.junit.jupiter.api.Assertions.assertAll;
18import static org.junit.jupiter.api.Assertions.assertThrows;
19
20class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10import static org.hamcrest.MatcherAssert.assertThat;
11import static org.hamcrest.Matchers.*;
12import static org.hamcrest.Matchers.is;
13
14class 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 @@
1plugins {
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
7plugins {
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 */
1package tools.refinery.store.map.benchmarks; 6package tools.refinery.store.map.benchmarks;
2 7
3import java.util.ArrayList; 8import 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 */
1package tools.refinery.store.map.benchmarks; 6package tools.refinery.store.map.benchmarks;
2 7
3import java.util.Random; 8import 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 @@
1package tools.refinery.store.adapter; 1package 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 */
3import tools.refinery.store.model.ModelStore; 7import tools.refinery.store.model.ModelStore;
4import tools.refinery.store.model.ModelStoreBuilder; 8import tools.refinery.store.model.ModelStoreBuilder;
5 9
6public abstract class AbstractModelAdapterBuilder implements ModelAdapterBuilder { 10public 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 @@
1package tools.refinery.store.adapter;
2
3import org.jetbrains.annotations.NotNull;
4
5import java.util.*;
6import java.util.function.Consumer;
7
8public 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 */
6package tools.refinery.store.adapter;
7
8import java.util.Collection;
9import java.util.Optional;
10
11public 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 @@
1package tools.refinery.store.adapter;
2
3import java.util.Collection;
4
5public 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 */
1package tools.refinery.store.adapter; 6package tools.refinery.store.adapter;
2 7
3import tools.refinery.store.model.Model; 8import 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 */
1package tools.refinery.store.adapter; 6package tools.refinery.store.adapter;
2 7
3import tools.refinery.store.model.ModelStore; 8import tools.refinery.store.model.ModelStore;
4import tools.refinery.store.model.ModelStoreBuilder; 9import tools.refinery.store.model.ModelStoreBuilder;
5 10
6public interface ModelAdapterBuilder { 11public 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 @@
1package tools.refinery.store.adapter;
2
3import tools.refinery.store.model.ModelStoreBuilder;
4
5public 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 @@
1package tools.refinery.store.adapter;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.model.ModelStore;
5
6import java.lang.reflect.Method;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.Set;
11
12public 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 */
1package tools.refinery.store.adapter; 6package tools.refinery.store.adapter;
2 7
3import tools.refinery.store.model.Model; 8import 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3public sealed interface AnyVersionedMap extends Versioned permits VersionedMap { 8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3public enum ContentHashCode { 8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3import tools.refinery.store.map.internal.Node; 8import 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3import java.util.Set; 8import 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3import java.util.Iterator; 8import 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 */
6package tools.refinery.store.map;
7
8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3public interface DiffCursor<K, V> extends Cursor<K,V> { 8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3import java.util.Iterator; 8import 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3public interface Versioned { 8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3public non-sealed interface VersionedMap<K, V> extends AnyVersionedMap { 8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3import tools.refinery.store.map.internal.VersionedMapStoreFactoryBuilderImpl; 8import 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3public class VersionedMapStoreConfiguration { 8public 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 */
1package tools.refinery.store.map; 6package tools.refinery.store.map;
2 7
3import tools.refinery.store.map.internal.*; 8import 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3enum HashClash { 8enum 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3import java.util.Arrays; 8import 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3import tools.refinery.store.map.AnyVersionedMap; 8import 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3import tools.refinery.store.map.AnyVersionedMap; 8import 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
8import tools.refinery.store.map.ContinousHashProvider;
9
3import java.util.Arrays; 10import java.util.Arrays;
4import java.util.Map; 11import java.util.Map;
5 12
6import tools.refinery.store.map.ContinousHashProvider;
7
8public class MutableNode<K, V> extends Node<K, V> { 13public 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3import java.util.Map; 8import 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3public class OldValueBox<V>{ 8public 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 */
1package tools.refinery.store.map.internal; 6package tools.refinery.store.map.internal;
2 7
3import tools.refinery.store.map.*; 8import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.representation.AnySymbol; 8import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.map.Cursor; 8import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.tuple.Tuple; 8import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.adapter.ModelAdapter; 8import tools.refinery.store.adapter.ModelAdapter;
4import tools.refinery.store.adapter.ModelAdapterType;
5import tools.refinery.store.map.Versioned; 9import tools.refinery.store.map.Versioned;
6import tools.refinery.store.representation.AnySymbol; 10import tools.refinery.store.representation.AnySymbol;
7import tools.refinery.store.representation.Symbol; 11import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.map.DiffCursor; 8import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3public interface ModelListener { 8public 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.adapter.ModelAdapterType;
4import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
5import tools.refinery.store.model.internal.ModelStoreBuilderImpl; 9import tools.refinery.store.model.internal.ModelStoreBuilderImpl;
6import tools.refinery.store.representation.AnySymbol; 10import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.adapter.ModelAdapterBuilder; 8import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
5import tools.refinery.store.adapter.ModelAdapterType;
6import tools.refinery.store.representation.AnySymbol; 9import tools.refinery.store.representation.AnySymbol;
7import tools.refinery.store.representation.Symbol; 10import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.map.ContinousHashProvider; 8import 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 */
1package tools.refinery.store.model; 6package tools.refinery.store.model;
2 7
3import tools.refinery.store.map.ContinousHashProvider; 8import 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 */
1package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
2 7
3public enum ModelAction { 8public 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 */
1package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
2 7
3import tools.refinery.store.adapter.AdapterList; 8import tools.refinery.store.adapter.AdapterUtils;
4import tools.refinery.store.adapter.AnyModelAdapterType;
5import tools.refinery.store.adapter.ModelAdapter; 9import tools.refinery.store.adapter.ModelAdapter;
6import tools.refinery.store.adapter.ModelAdapterType;
7import tools.refinery.store.map.DiffCursor; 10import tools.refinery.store.map.DiffCursor;
8import tools.refinery.store.model.*; 11import tools.refinery.store.model.*;
9import tools.refinery.store.representation.AnySymbol; 12import 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 */
1package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
2 7
3import tools.refinery.store.adapter.AdapterList; 8import tools.refinery.store.adapter.AdapterUtils;
4import tools.refinery.store.adapter.ModelAdapterBuilder; 9import tools.refinery.store.adapter.ModelAdapterBuilder;
5import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
6import tools.refinery.store.adapter.ModelAdapterType;
7import tools.refinery.store.map.VersionedMapStore; 10import tools.refinery.store.map.VersionedMapStore;
8import tools.refinery.store.map.VersionedMapStoreImpl; 11import tools.refinery.store.map.VersionedMapStoreImpl;
9import tools.refinery.store.model.ModelStore; 12import tools.refinery.store.model.ModelStore;
@@ -18,7 +21,7 @@ import java.util.*;
18public class ModelStoreBuilderImpl implements ModelStoreBuilder { 21public 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 */
1package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
2 7
3import tools.refinery.store.adapter.AdapterList; 8import tools.refinery.store.adapter.AdapterUtils;
4import tools.refinery.store.adapter.AnyModelAdapterType;
5import tools.refinery.store.adapter.ModelAdapterType;
6import tools.refinery.store.adapter.ModelStoreAdapter; 9import tools.refinery.store.adapter.ModelStoreAdapter;
7import tools.refinery.store.map.DiffCursor; 10import tools.refinery.store.map.DiffCursor;
8import tools.refinery.store.map.VersionedMapStore; 11import tools.refinery.store.map.VersionedMapStore;
@@ -16,11 +19,11 @@ import java.util.*;
16 19
17public class ModelStoreImpl implements ModelStore { 20public 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 */
1package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
2 7
3import tools.refinery.store.representation.Symbol; 8import 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 */
1package tools.refinery.store.model.internal; 6package tools.refinery.store.model.internal;
2 7
3import tools.refinery.store.map.Cursor; 8import 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 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
4import tools.refinery.store.model.ModelStoreBuilder;
5import tools.refinery.store.partial.internal.PartialInterpretationBuilderImpl;
6
7public 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 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelAdapter;
4
5public 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 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5
6public 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 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelStoreAdapter;
4import tools.refinery.store.model.Model;
5
6public 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 @@
1package tools.refinery.store.partial.internal;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.partial.PartialInterpretationAdapter;
5
6public 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 @@
1package tools.refinery.store.partial.internal;
2
3import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.model.ModelStoreBuilder;
6import tools.refinery.store.partial.PartialInterpretationBuilder;
7
8public 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 @@
1package tools.refinery.store.partial.internal;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.partial.PartialInterpretationStoreAdapter;
6
7public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.atom.DNFAtom;
4
5import java.util.*;
6
7public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.atom.DNFAtom;
4
5import java.util.List;
6import java.util.Set;
7
8public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapterType;
4
5public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapter;
4
5public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5
6import java.util.Collection;
7import java.util.List;
8
9public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.adapter.ModelStoreAdapter;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.query.view.AnyRelationView;
6
7import java.util.Collection;
8
9public 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 @@
1package tools.refinery.store.query;
2
3public 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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.tuple.Tuple;
4import tools.refinery.store.tuple.TupleLike;
5
6import java.util.Optional;
7import java.util.stream.Stream;
8
9public 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 @@
1package tools.refinery.store.query;
2
3import java.util.Objects;
4
5public 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 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4import tools.refinery.store.query.RelationLike;
5
6import java.util.List;
7import java.util.Objects;
8import java.util.Set;
9
10public 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 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Set;
6
7public 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 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Set;
6
7public 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 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.DNF;
4import tools.refinery.store.query.Variable;
5
6import java.util.List;
7
8public 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 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Set;
6
7public 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 @@
1package tools.refinery.store.query.atom;
2
3import java.util.Locale;
4
5public 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 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4import tools.refinery.store.query.view.AnyRelationView;
5
6import java.util.List;
7
8public 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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.FunctionalDependency;
5import tools.refinery.store.representation.AnySymbol;
6import tools.refinery.store.query.RelationLike;
7
8import java.util.Set;
9
10public 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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.tuple.Tuple;
4import tools.refinery.store.representation.Symbol;
5
6import java.util.Objects;
7import java.util.function.BiPredicate;
8import java.util.function.Predicate;
9
10public 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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.FunctionalDependency;
5import tools.refinery.store.representation.Symbol;
6import tools.refinery.store.tuple.Tuple;
7import tools.refinery.store.tuple.Tuple1;
8
9import java.util.Set;
10import java.util.stream.Collectors;
11import java.util.stream.IntStream;
12
13public 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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.map.CursorAsIterator;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.representation.Symbol;
6import tools.refinery.store.tuple.Tuple;
7
8import java.util.Objects;
9import 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 */
17public 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 @@
1package tools.refinery.store.query.view;
2
3import java.util.List;
4
5public 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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.tuple.Tuple;
5import tools.refinery.store.tuple.Tuple1;
6import tools.refinery.store.representation.Symbol;
7
8public 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 */
6package tools.refinery.store.representation;
7
8import java.util.Optional;
9
10public 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 */
6package tools.refinery.store.representation;
7
8public 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 */
1package tools.refinery.store.representation; 6package tools.refinery.store.representation;
2 7
3public sealed interface AnySymbol permits Symbol { 8public 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 */
1package tools.refinery.store.representation; 6package tools.refinery.store.representation;
2 7
3import java.util.Objects;
4
5public record Symbol<T>(String name, int arity, Class<T> valueType, T defaultValue) implements AnySymbol { 8public 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 */
1package tools.refinery.store.representation; 6package tools.refinery.store.representation;
2 7
3public enum TruthValue { 8public 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 */
6package tools.refinery.store.representation;
7
8import java.util.Optional;
9
10public 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval, EmptyCardinalityInterval { 8public 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3public final class CardinalityIntervals { 8public 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3public final class EmptyCardinalityInterval implements CardinalityInterval { 8public 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.jetbrains.annotations.NotNull; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import java.util.function.BinaryOperator; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.jetbrains.annotations.NotNull; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3public final class UpperCardinalities { 8public 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality, 8public 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 */
1package tools.refinery.store.tuple; 6package tools.refinery.store.tuple;
2 7
3public sealed interface Tuple extends TupleLike permits Tuple0, Tuple1, Tuple2, TupleN { 8import org.jetbrains.annotations.NotNull;
9
10public 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 */
1package tools.refinery.store.tuple; 6package tools.refinery.store.tuple;
2 7
3public record Tuple0() implements Tuple { 8import static tools.refinery.store.tuple.TupleConstants.TUPLE_BEGIN;
4 public static Tuple0 INSTANCE = new Tuple0(); 9import 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")
15public 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 */
1package tools.refinery.store.tuple; 6package tools.refinery.store.tuple;
2 7
8import org.jetbrains.annotations.NotNull;
3import tools.refinery.store.model.TupleHashProvider; 9import tools.refinery.store.model.TupleHashProvider;
4 10
5import java.util.Arrays; 11import java.util.Arrays;
6 12
7public record Tuple1(int value0) implements Tuple { 13import static tools.refinery.store.tuple.TupleConstants.TUPLE_BEGIN;
14import static tools.refinery.store.tuple.TupleConstants.TUPLE_END;
15
16public 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 */
1package tools.refinery.store.tuple; 6package tools.refinery.store.tuple;
2 7
8import org.jetbrains.annotations.NotNull;
9
10import static tools.refinery.store.tuple.TupleConstants.*;
11
3public record Tuple2(int value0, int value1) implements Tuple { 12public 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 */
6package tools.refinery.store.tuple;
7
8import org.jetbrains.annotations.NotNull;
9
10import static tools.refinery.store.tuple.TupleConstants.*;
11
12public 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 */
6package tools.refinery.store.tuple;
7
8import org.jetbrains.annotations.NotNull;
9
10import static tools.refinery.store.tuple.TupleConstants.*;
11
12public 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 */
6package tools.refinery.store.tuple;
7
8final 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 @@
1package tools.refinery.store.tuple;
2
3public 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 */
1package tools.refinery.store.tuple; 6package tools.refinery.store.tuple;
2 7
3import java.util.Arrays; 8import java.util.Arrays;
4import java.util.stream.Collectors; 9import java.util.stream.Collectors;
5 10
6public record TupleN(int[] values) implements Tuple { 11import static tools.refinery.store.tuple.TupleConstants.*;
7 static final int CUSTOM_TUPLE_SIZE = 2;
8 12
9 public TupleN(int[] values) { 13public 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 */
1package tools.refinery.store.util; 6package tools.refinery.store.util;
2 7
3import java.util.Iterator; 8import 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 */
6package tools.refinery.store.util;
7
8import java.util.*;
9import java.util.function.Function;
10import java.util.stream.Collectors;
11
12public 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 */
1package tools.refinery.store.map.tests; 6package tools.refinery.store.map.tests;
2 7
3import org.junit.jupiter.api.Test; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import org.junit.jupiter.api.Tag; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import org.junit.jupiter.api.Tag; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import org.junit.jupiter.api.Tag; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import org.junit.jupiter.api.Tag; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import java.util.ArrayList; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import static org.junit.jupiter.api.Assertions.fail; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import static org.junit.jupiter.api.Assertions.fail; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import org.junit.jupiter.api.Tag; 8import 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 */
1package tools.refinery.store.map.tests.fuzz; 6package tools.refinery.store.map.tests.fuzz;
2 7
3import java.util.HashMap; 8import 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 */
1package tools.refinery.store.map.tests.fuzz.utils; 6package tools.refinery.store.map.tests.fuzz.utils;
2 7
3import java.util.Arrays; 8import 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 */
1package tools.refinery.store.map.tests.fuzz.utils; 6package tools.refinery.store.map.tests.fuzz.utils;
2 7
3import static org.junit.jupiter.api.Assertions.assertEquals; 8import 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 */
1package tools.refinery.store.map.tests.utils; 6package tools.refinery.store.map.tests.utils;
2 7
3import tools.refinery.store.map.*; 8import 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 */
1package tools.refinery.store.model.hashtests; 6package tools.refinery.store.model.hashtests;
2 7
3import static org.junit.jupiter.api.Assertions.assertEquals; 8import 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 */
1package tools.refinery.store.model.tests; 6package tools.refinery.store.model.tests;
2 7
3import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
@@ -9,27 +14,24 @@ import tools.refinery.store.tuple.Tuple;
9import static org.junit.jupiter.api.Assertions.*; 14import static org.junit.jupiter.api.Assertions.*;
10 15
11class ModelTest { 16class 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.params.ParameterizedTest; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.api.Test; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.api.Test; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.api.Test; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.api.Test; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.api.Test; 8import 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 */
1package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
2 7
3import org.junit.jupiter.params.ParameterizedTest; 8import 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 */
1package tools.refinery.store.util; 6package tools.refinery.store.util;
2 7
3import static org.junit.jupiter.api.Assertions.assertEquals; 8import static org.junit.jupiter.api.Assertions.assertEquals;